Add half precision floating point support to StreamPeer

Closes godotengine/godot-proposals#5983

Adds put/get methods to `StreamPeer` that handles half precision
floating point values.
Adds endode/decode half precision floating point to `marshalls`.
Adds `get_half` and `store_half` to `FileAccess`

Co-Authored-By: "Alfonso J. Ramos" <theraot@gmail.com>
This commit is contained in:
Pablo Andres Fuente 2024-10-01 23:41:13 -03:00 committed by Pablo Andres Fuente
parent ec6a1c0e79
commit eb86670f94
15 changed files with 218 additions and 2 deletions

View file

@ -39,7 +39,7 @@ namespace TestFileAccess {
TEST_CASE("[FileAccess] CSV read") {
Ref<FileAccess> f = FileAccess::open(TestUtils::get_data_path("testdata.csv"), FileAccess::READ);
REQUIRE(!f.is_null());
REQUIRE(f.is_valid());
Vector<String> header = f->get_csv_line(); // Default delimiter: ",".
REQUIRE(header.size() == 4);
@ -107,6 +107,98 @@ TEST_CASE("[FileAccess] Get as UTF-8 String") {
CHECK(s_cr == "Hello darkness\rMy old friend\rI've come to talk\rWith you again\r");
CHECK(s_cr_nocr == "Hello darknessMy old friendI've come to talkWith you again");
}
TEST_CASE("[FileAccess] Get/Store floating point values") {
// BigEndian Hex: 0x40490E56
// LittleEndian Hex: 0x560E4940
float value = 3.1415f;
SUBCASE("Little Endian") {
const String file_path = TestUtils::get_data_path("floating_point_little_endian.bin");
const String file_path_new = TestUtils::get_data_path("floating_point_little_endian_new.bin");
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
REQUIRE(f.is_valid());
CHECK_EQ(f->get_float(), value);
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
REQUIRE(fw.is_valid());
fw->store_float(value);
fw->close();
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
DirAccess::remove_file_or_error(file_path_new);
}
SUBCASE("Big Endian") {
const String file_path = TestUtils::get_data_path("floating_point_big_endian.bin");
const String file_path_new = TestUtils::get_data_path("floating_point_big_endian_new.bin");
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
REQUIRE(f.is_valid());
f->set_big_endian(true);
CHECK_EQ(f->get_float(), value);
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
REQUIRE(fw.is_valid());
fw->set_big_endian(true);
fw->store_float(value);
fw->close();
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
DirAccess::remove_file_or_error(file_path_new);
}
}
TEST_CASE("[FileAccess] Get/Store floating point half precision values") {
// IEEE 754 half-precision binary floating-point format:
// sign exponent (5 bits) fraction (10 bits)
// 0 01101 0101010101
// BigEndian Hex: 0x3555
// LittleEndian Hex: 0x5535
float value = 0.33325195f;
SUBCASE("Little Endian") {
const String file_path = TestUtils::get_data_path("half_precision_floating_point_little_endian.bin");
const String file_path_new = TestUtils::get_data_path("half_precision_floating_point_little_endian_new.bin");
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
REQUIRE(f.is_valid());
CHECK_EQ(f->get_half(), value);
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
REQUIRE(fw.is_valid());
fw->store_half(value);
fw->close();
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
DirAccess::remove_file_or_error(file_path_new);
}
SUBCASE("Big Endian") {
const String file_path = TestUtils::get_data_path("half_precision_floating_point_big_endian.bin");
const String file_path_new = TestUtils::get_data_path("half_precision_floating_point_big_endian_new.bin");
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::READ);
REQUIRE(f.is_valid());
f->set_big_endian(true);
CHECK_EQ(f->get_half(), value);
Ref<FileAccess> fw = FileAccess::open(file_path_new, FileAccess::WRITE);
REQUIRE(fw.is_valid());
fw->set_big_endian(true);
fw->store_half(value);
fw->close();
CHECK_EQ(FileAccess::get_sha256(file_path_new), FileAccess::get_sha256(file_path));
DirAccess::remove_file_or_error(file_path_new);
}
}
} // namespace TestFileAccess
#endif // TEST_FILE_ACCESS_H

View file

@ -90,6 +90,20 @@ TEST_CASE("[Marshalls] Unsigned 64 bit integer decoding") {
CHECK(decode_uint64(arr) == 0x0f123456789abcdef);
}
TEST_CASE("[Marshalls] Floating point half precision encoding") {
uint8_t arr[2];
// Decimal: 0.33325195
// IEEE 754 half-precision binary floating-point format:
// sign exponent (5 bits) fraction (10 bits)
// 0 01101 0101010101
// Hexadecimal: 0x3555
unsigned int actual_size = encode_half(0.33325195f, arr);
CHECK(actual_size == sizeof(uint16_t));
CHECK(arr[0] == 0x55);
CHECK(arr[1] == 0x35);
}
TEST_CASE("[Marshalls] Floating point single precision encoding") {
uint8_t arr[4];
@ -126,6 +140,13 @@ TEST_CASE("[Marshalls] Floating point double precision encoding") {
CHECK(arr[7] == 0x3f);
}
TEST_CASE("[Marshalls] Floating point half precision decoding") {
uint8_t arr[] = { 0x55, 0x35 };
// See floating point half precision encoding test case for details behind expected values.
CHECK(decode_half(arr) == 0.33325195f);
}
TEST_CASE("[Marshalls] Floating point single precision decoding") {
uint8_t arr[] = { 0x00, 0x00, 0x20, 0x3e };

View file

@ -127,6 +127,17 @@ TEST_CASE("[StreamPeer] Get and sets through StreamPeerBuffer") {
CHECK_EQ(spb->get_u64(), value);
}
SUBCASE("A half-precision float value") {
float value = 3.1415927f;
float expected = 3.14062f;
spb->clear();
spb->put_half(value);
spb->seek(0);
CHECK(spb->get_half() == doctest::Approx(expected));
}
SUBCASE("A float value") {
float value = 42.0f;
@ -255,6 +266,17 @@ TEST_CASE("[StreamPeer] Get and sets big endian through StreamPeerBuffer") {
CHECK_EQ(spb->get_float(), value);
}
SUBCASE("A half-precision float value") {
float value = 3.1415927f;
float expected = 3.14062f;
spb->clear();
spb->put_half(value);
spb->seek(0);
CHECK(spb->get_half() == doctest::Approx(expected));
}
SUBCASE("A double value") {
double value = 42.0;