This PR adds conversion template to convert between Low Precision float formats. These include Binary32 floats and lower. It also adds support to convert between unsigned and signed float formats and float formats with different mantissa and exponents. Additionally overflows (values that don't fit in the target float format) will be clamped to the maximum value. **Reasoning**: Up to now the Vulkan backend only supported float and half float formats, but to support workbench, 11 and 10 unsigned floats have to be supported as well. The available libraries that support those float formats targets scientific applications. Where the final code couldn't be optimized that well by the compiler. Data conversion for color pixels have different requirements about clamping and sign, what could eliminate some clamping code in other areas in Blender as well. Also could fix some undesired overflow when using pixels with high intensity that didn't fit in the texture format leading to known artifects in Eevee and slow-down in the image editor. **Future** In the future we might want to move this to the public part of the GPU module so we can use this as well in other areas (Metal backend), Imbuf clamping See 3c658d2c2e69e9cf97dfaa7a3c164262aefb9e76 for a commit that uses this and improves image editor massively as it doesn't need to reiterate over the image buffer to clamp the values into a known range. Pull Request: https://projects.blender.org/blender/blender/pulls/108168
100 lines
3.9 KiB
C++
100 lines
3.9 KiB
C++
/* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#include "testing/testing.h"
|
|
|
|
#include "vk_data_conversion.hh"
|
|
|
|
namespace blender::gpu::tests {
|
|
static void test_f32_f16(uint32_t f32_in, uint32_t f16_expected)
|
|
{
|
|
const uint32_t f16 = convert_float_formats<FormatF16, FormatF32>(f32_in);
|
|
EXPECT_EQ(f16, f16_expected);
|
|
const uint32_t f32_reverse = convert_float_formats<FormatF32, FormatF16>(f16);
|
|
EXPECT_EQ(f32_reverse, f32_in);
|
|
}
|
|
|
|
TEST(VulkanDataConversion, ConvertF32F16)
|
|
{
|
|
/* 0.0 */
|
|
test_f32_f16(0b00000000000000000000000000000000, 0b0000000000000000);
|
|
/* 0.125 */
|
|
test_f32_f16(0b00111110000000000000000000000000, 0b0011000000000000);
|
|
/* 2.0 */
|
|
test_f32_f16(0b01000000000000000000000000000000, 0b0100000000000000);
|
|
/* 3.0 */
|
|
test_f32_f16(0b01000000010000000000000000000000, 0b0100001000000000);
|
|
/* 4.0 */
|
|
test_f32_f16(0b01000000100000000000000000000000, 0b0100010000000000);
|
|
}
|
|
|
|
TEST(VulkanDataConversion, clamp_negative_to_zero)
|
|
{
|
|
const uint32_t f32_2 = 0b11000000000000000000000000000000;
|
|
const uint32_t f32_inf_min = 0b11111111100000000000000000000000;
|
|
const uint32_t f32_inf_max = 0b01111111100000000000000000000000;
|
|
const uint32_t f32_nan = 0b11111111111111111111111111111111;
|
|
|
|
/* F32(-2) fits in F16. */
|
|
const uint32_t f16_2_expected = 0b1100000000000000;
|
|
const uint32_t f16_2a = convert_float_formats<FormatF16, FormatF32, true>(f32_2);
|
|
EXPECT_EQ(f16_2a, f16_2_expected);
|
|
|
|
const uint32_t f16_2b = convert_float_formats<FormatF16, FormatF32, false>(f32_2);
|
|
EXPECT_EQ(f16_2b, f16_2_expected);
|
|
|
|
/* F32(-2) doesn't fit in F11 as F11 only supports unsigned values. Clamp to zero. */
|
|
const uint32_t f11_0_expected = 0b00000000000;
|
|
const uint32_t f11_2_expected = 0b10000000000;
|
|
const uint32_t f11_inf_expected = 0b11111000000;
|
|
const uint32_t f11_nan_expected = 0b11111111111;
|
|
{
|
|
const uint32_t f11_0 = convert_float_formats<FormatF11, FormatF32, true>(f32_2);
|
|
EXPECT_EQ(f11_0, f11_0_expected);
|
|
const uint32_t f11_0b = convert_float_formats<FormatF11, FormatF32, true>(f32_inf_min);
|
|
EXPECT_EQ(f11_0b, f11_0_expected);
|
|
const uint32_t f11_inf = convert_float_formats<FormatF11, FormatF32, true>(f32_inf_max);
|
|
EXPECT_EQ(f11_inf, f11_inf_expected);
|
|
const uint32_t f11_nan = convert_float_formats<FormatF11, FormatF32, true>(f32_nan);
|
|
EXPECT_EQ(f11_nan, f11_nan_expected);
|
|
}
|
|
|
|
/* F32(-2) doesn't fit in F11 as F11 only supports unsigned values. Make absolute. */
|
|
{
|
|
const uint32_t f11_2 = convert_float_formats<FormatF11, FormatF32, false>(f32_2);
|
|
EXPECT_EQ(f11_2, f11_2_expected);
|
|
const uint32_t f11_inf = convert_float_formats<FormatF11, FormatF32, false>(f32_inf_min);
|
|
EXPECT_EQ(f11_inf, f11_inf_expected);
|
|
const uint32_t f11_infb = convert_float_formats<FormatF11, FormatF32, false>(f32_inf_max);
|
|
EXPECT_EQ(f11_infb, f11_inf_expected);
|
|
const uint32_t f11_nan = convert_float_formats<FormatF11, FormatF32, false>(f32_nan);
|
|
EXPECT_EQ(f11_nan, f11_nan_expected);
|
|
}
|
|
}
|
|
|
|
TEST(VulkanDataConversion, infinity_upper)
|
|
{
|
|
const uint32_t f32_inf = 0b01111111100000000000000000000000;
|
|
|
|
const uint32_t f16_inf_expected = 0b0111110000000000;
|
|
const uint32_t f16_inf = convert_float_formats<FormatF16, FormatF32, true>(f32_inf);
|
|
EXPECT_EQ(f16_inf, f16_inf_expected);
|
|
|
|
const uint32_t f11_inf_expected = 0b11111000000;
|
|
const uint32_t f11_inf = convert_float_formats<FormatF11, FormatF32, true>(f32_inf);
|
|
EXPECT_EQ(f11_inf, f11_inf_expected);
|
|
|
|
const uint32_t f10_inf_expected = 0b1111100000;
|
|
const uint32_t f10_inf = convert_float_formats<FormatF10, FormatF32, true>(f32_inf);
|
|
EXPECT_EQ(f10_inf, f10_inf_expected);
|
|
}
|
|
|
|
TEST(VulkanDataConversion, infinity_lower)
|
|
{
|
|
const uint32_t f32_inf = 0b11111111100000000000000000000000;
|
|
|
|
const uint32_t f16_inf_expected = 0b1111110000000000;
|
|
const uint32_t f16_inf = convert_float_formats<FormatF16, FormatF32, true>(f32_inf);
|
|
EXPECT_EQ(f16_inf, f16_inf_expected);
|
|
}
|
|
|
|
} // namespace blender::gpu::tests
|