/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2023 Blender Foundation. All rights reserved. */ /** \file * \ingroup gpu */ #include "vk_data_conversion.hh" #include "Imath/half.h" namespace blender::gpu { /* -------------------------------------------------------------------- */ /** \name Conversion types * \{ */ enum class ConversionType { /** No conversion needed, result can be directly read back to host memory. */ PASS_THROUGH, FLOAT_TO_UNORM8, UNORM8_TO_FLOAT, FLOAT_TO_SNORM8, SNORM8_TO_FLOAT, FLOAT_TO_UNORM16, UNORM16_TO_FLOAT, FLOAT_TO_SNORM16, SNORM16_TO_FLOAT, UI32_TO_UI16, UI16_TO_UI32, UI32_TO_UI8, UI8_TO_UI32, I32_TO_I16, I16_TO_I32, I32_TO_I8, I8_TO_I32, /** Convert device 16F to floats. */ HALF_TO_FLOAT, FLOAT_TO_HALF, /** * The requested conversion isn't supported. */ UNSUPPORTED, }; static ConversionType type_of_conversion_float(eGPUTextureFormat device_format) { switch (device_format) { case GPU_RGBA32F: case GPU_RG32F: case GPU_R32F: case GPU_DEPTH_COMPONENT32F: return ConversionType::PASS_THROUGH; case GPU_RGBA16F: case GPU_RG16F: case GPU_R16F: case GPU_RGB16F: return ConversionType::FLOAT_TO_HALF; case GPU_RGBA8: case GPU_RG8: case GPU_R8: return ConversionType::FLOAT_TO_UNORM8; case GPU_RGBA8_SNORM: case GPU_RGB8_SNORM: case GPU_RG8_SNORM: case GPU_R8_SNORM: return ConversionType::FLOAT_TO_SNORM8; case GPU_RGBA16: case GPU_RG16: case GPU_R16: return ConversionType::FLOAT_TO_UNORM16; case GPU_RGBA16_SNORM: case GPU_RGB16_SNORM: case GPU_RG16_SNORM: case GPU_R16_SNORM: return ConversionType::FLOAT_TO_SNORM16; case GPU_RGB32F: /* GPU_RGB32F Not supported by vendors. */ case GPU_RGBA8UI: case GPU_RGBA8I: case GPU_RGBA16UI: case GPU_RGBA16I: case GPU_RGBA32UI: case GPU_RGBA32I: case GPU_RG8UI: case GPU_RG8I: case GPU_RG16UI: case GPU_RG16I: case GPU_RG32UI: case GPU_RG32I: case GPU_R8UI: case GPU_R8I: case GPU_R16UI: case GPU_R16I: case GPU_R32UI: case GPU_R32I: case GPU_RGB10_A2: case GPU_RGB10_A2UI: case GPU_R11F_G11F_B10F: case GPU_DEPTH32F_STENCIL8: case GPU_DEPTH24_STENCIL8: case GPU_SRGB8_A8: case GPU_RGB8UI: case GPU_RGB8I: case GPU_RGB8: case GPU_RGB16UI: case GPU_RGB16I: case GPU_RGB16: case GPU_RGB32UI: case GPU_RGB32I: case GPU_SRGB8_A8_DXT1: case GPU_SRGB8_A8_DXT3: case GPU_SRGB8_A8_DXT5: case GPU_RGBA8_DXT1: case GPU_RGBA8_DXT3: case GPU_RGBA8_DXT5: case GPU_SRGB8: case GPU_RGB9_E5: case GPU_DEPTH_COMPONENT24: case GPU_DEPTH_COMPONENT16: return ConversionType::UNSUPPORTED; } return ConversionType::UNSUPPORTED; } static ConversionType type_of_conversion_int(eGPUTextureFormat device_format) { switch (device_format) { case GPU_RGBA32I: case GPU_RG32I: case GPU_R32I: return ConversionType::PASS_THROUGH; case GPU_RGBA16I: case GPU_RG16I: case GPU_R16I: return ConversionType::I32_TO_I16; case GPU_RGBA8I: case GPU_RG8I: case GPU_R8I: return ConversionType::I32_TO_I8; case GPU_RGBA8UI: case GPU_RGBA8: case GPU_RGBA16UI: case GPU_RGBA16F: case GPU_RGBA16: case GPU_RGBA32UI: case GPU_RGBA32F: case GPU_RG8UI: case GPU_RG8: case GPU_RG16UI: case GPU_RG16F: case GPU_RG32UI: case GPU_RG32F: case GPU_RG16: case GPU_R8UI: case GPU_R8: case GPU_R16UI: case GPU_R16F: case GPU_R16: case GPU_R32UI: case GPU_R32F: case GPU_RGB10_A2: case GPU_RGB10_A2UI: case GPU_R11F_G11F_B10F: case GPU_DEPTH32F_STENCIL8: case GPU_DEPTH24_STENCIL8: case GPU_SRGB8_A8: case GPU_RGBA8_SNORM: case GPU_RGBA16_SNORM: case GPU_RGB8UI: case GPU_RGB8I: case GPU_RGB8: case GPU_RGB8_SNORM: case GPU_RGB16UI: case GPU_RGB16I: case GPU_RGB16F: case GPU_RGB16: case GPU_RGB16_SNORM: case GPU_RGB32UI: case GPU_RGB32I: case GPU_RGB32F: case GPU_RG8_SNORM: case GPU_RG16_SNORM: case GPU_R8_SNORM: case GPU_R16_SNORM: case GPU_SRGB8_A8_DXT1: case GPU_SRGB8_A8_DXT3: case GPU_SRGB8_A8_DXT5: case GPU_RGBA8_DXT1: case GPU_RGBA8_DXT3: case GPU_RGBA8_DXT5: case GPU_SRGB8: case GPU_RGB9_E5: case GPU_DEPTH_COMPONENT32F: case GPU_DEPTH_COMPONENT24: case GPU_DEPTH_COMPONENT16: return ConversionType::UNSUPPORTED; } return ConversionType::UNSUPPORTED; } static ConversionType type_of_conversion_uint(eGPUTextureFormat device_format) { switch (device_format) { case GPU_RGBA32UI: case GPU_RG32UI: case GPU_R32UI: return ConversionType::PASS_THROUGH; case GPU_RGBA16UI: case GPU_RG16UI: case GPU_R16UI: case GPU_RGB16UI: return ConversionType::UI32_TO_UI16; case GPU_RGBA8UI: case GPU_RG8UI: case GPU_R8UI: return ConversionType::UI32_TO_UI8; case GPU_RGBA8I: case GPU_RGBA8: case GPU_RGBA16I: case GPU_RGBA16F: case GPU_RGBA16: case GPU_RGBA32I: case GPU_RGBA32F: case GPU_RG8I: case GPU_RG8: case GPU_RG16I: case GPU_RG16F: case GPU_RG16: case GPU_RG32I: case GPU_RG32F: case GPU_R8I: case GPU_R8: case GPU_R16I: case GPU_R16F: case GPU_R16: case GPU_R32I: case GPU_R32F: case GPU_RGB10_A2: case GPU_RGB10_A2UI: case GPU_R11F_G11F_B10F: case GPU_DEPTH32F_STENCIL8: case GPU_DEPTH24_STENCIL8: case GPU_SRGB8_A8: case GPU_RGBA8_SNORM: case GPU_RGBA16_SNORM: case GPU_RGB8UI: case GPU_RGB8I: case GPU_RGB8: case GPU_RGB8_SNORM: case GPU_RGB16I: case GPU_RGB16F: case GPU_RGB16: case GPU_RGB16_SNORM: case GPU_RGB32UI: case GPU_RGB32I: case GPU_RGB32F: case GPU_RG8_SNORM: case GPU_RG16_SNORM: case GPU_R8_SNORM: case GPU_R16_SNORM: case GPU_SRGB8_A8_DXT1: case GPU_SRGB8_A8_DXT3: case GPU_SRGB8_A8_DXT5: case GPU_RGBA8_DXT1: case GPU_RGBA8_DXT3: case GPU_RGBA8_DXT5: case GPU_SRGB8: case GPU_RGB9_E5: case GPU_DEPTH_COMPONENT32F: case GPU_DEPTH_COMPONENT24: case GPU_DEPTH_COMPONENT16: return ConversionType::UNSUPPORTED; } return ConversionType::UNSUPPORTED; } static ConversionType type_of_conversion_half(eGPUTextureFormat device_format) { switch (device_format) { case GPU_RGBA16F: case GPU_RG16F: case GPU_R16F: return ConversionType::PASS_THROUGH; case GPU_RGBA8UI: case GPU_RGBA8I: case GPU_RGBA8: case GPU_RGBA16UI: case GPU_RGBA16I: case GPU_RGBA16: case GPU_RGBA32UI: case GPU_RGBA32I: case GPU_RGBA32F: case GPU_RG8UI: case GPU_RG8I: case GPU_RG8: case GPU_RG16UI: case GPU_RG16I: case GPU_RG16: case GPU_RG32UI: case GPU_RG32I: case GPU_RG32F: case GPU_R8UI: case GPU_R8I: case GPU_R8: case GPU_R16UI: case GPU_R16I: case GPU_R16: case GPU_R32UI: case GPU_R32I: case GPU_R32F: case GPU_RGB10_A2: case GPU_RGB10_A2UI: case GPU_R11F_G11F_B10F: case GPU_DEPTH32F_STENCIL8: case GPU_DEPTH24_STENCIL8: case GPU_SRGB8_A8: case GPU_RGBA8_SNORM: case GPU_RGBA16_SNORM: case GPU_RGB8UI: case GPU_RGB8I: case GPU_RGB8: case GPU_RGB8_SNORM: case GPU_RGB16UI: case GPU_RGB16I: case GPU_RGB16F: case GPU_RGB16: case GPU_RGB16_SNORM: case GPU_RGB32UI: case GPU_RGB32I: case GPU_RGB32F: case GPU_RG8_SNORM: case GPU_RG16_SNORM: case GPU_R8_SNORM: case GPU_R16_SNORM: case GPU_SRGB8_A8_DXT1: case GPU_SRGB8_A8_DXT3: case GPU_SRGB8_A8_DXT5: case GPU_RGBA8_DXT1: case GPU_RGBA8_DXT3: case GPU_RGBA8_DXT5: case GPU_SRGB8: case GPU_RGB9_E5: case GPU_DEPTH_COMPONENT32F: case GPU_DEPTH_COMPONENT24: case GPU_DEPTH_COMPONENT16: return ConversionType::UNSUPPORTED; } return ConversionType::UNSUPPORTED; } static ConversionType type_of_conversion_ubyte(eGPUTextureFormat device_format) { switch (device_format) { case GPU_RGBA8UI: case GPU_RGBA8: case GPU_RG8UI: case GPU_RG8: case GPU_R8UI: case GPU_R8: return ConversionType::PASS_THROUGH; case GPU_RGBA8I: case GPU_RGBA16UI: case GPU_RGBA16I: case GPU_RGBA16F: case GPU_RGBA16: case GPU_RGBA32UI: case GPU_RGBA32I: case GPU_RGBA32F: case GPU_RG8I: case GPU_RG16UI: case GPU_RG16I: case GPU_RG16F: case GPU_RG16: case GPU_RG32UI: case GPU_RG32I: case GPU_RG32F: case GPU_R8I: case GPU_R16UI: case GPU_R16I: case GPU_R16F: case GPU_R16: case GPU_R32UI: case GPU_R32I: case GPU_R32F: case GPU_RGB10_A2: case GPU_RGB10_A2UI: case GPU_R11F_G11F_B10F: case GPU_DEPTH32F_STENCIL8: case GPU_DEPTH24_STENCIL8: case GPU_SRGB8_A8: case GPU_RGBA8_SNORM: case GPU_RGBA16_SNORM: case GPU_RGB8UI: case GPU_RGB8I: case GPU_RGB8: case GPU_RGB8_SNORM: case GPU_RGB16UI: case GPU_RGB16I: case GPU_RGB16F: case GPU_RGB16: case GPU_RGB16_SNORM: case GPU_RGB32UI: case GPU_RGB32I: case GPU_RGB32F: case GPU_RG8_SNORM: case GPU_RG16_SNORM: case GPU_R8_SNORM: case GPU_R16_SNORM: case GPU_SRGB8_A8_DXT1: case GPU_SRGB8_A8_DXT3: case GPU_SRGB8_A8_DXT5: case GPU_RGBA8_DXT1: case GPU_RGBA8_DXT3: case GPU_RGBA8_DXT5: case GPU_SRGB8: case GPU_RGB9_E5: case GPU_DEPTH_COMPONENT32F: case GPU_DEPTH_COMPONENT24: case GPU_DEPTH_COMPONENT16: return ConversionType::UNSUPPORTED; } return ConversionType::UNSUPPORTED; } static ConversionType type_of_conversion_r11g11b10(eGPUTextureFormat device_format) { if (device_format == GPU_R11F_G11F_B10F) { return ConversionType::PASS_THROUGH; } return ConversionType::UNSUPPORTED; } static ConversionType type_of_conversion_r10g10b10a2(eGPUTextureFormat device_format) { if (ELEM(device_format, GPU_RGB10_A2, GPU_RGB10_A2UI)) { return ConversionType::PASS_THROUGH; } return ConversionType::UNSUPPORTED; } static ConversionType host_to_device(eGPUDataFormat host_format, eGPUTextureFormat device_format) { BLI_assert(validate_data_format(device_format, host_format)); switch (host_format) { case GPU_DATA_FLOAT: return type_of_conversion_float(device_format); case GPU_DATA_UINT: return type_of_conversion_uint(device_format); case GPU_DATA_INT: return type_of_conversion_int(device_format); case GPU_DATA_HALF_FLOAT: return type_of_conversion_half(device_format); case GPU_DATA_UBYTE: return type_of_conversion_ubyte(device_format); case GPU_DATA_10_11_11_REV: return type_of_conversion_r11g11b10(device_format); case GPU_DATA_2_10_10_10_REV: return type_of_conversion_r10g10b10a2(device_format); case GPU_DATA_UINT_24_8: return ConversionType::UNSUPPORTED; } return ConversionType::UNSUPPORTED; } static ConversionType reversed(ConversionType type) { #define CASE_SINGLE(a, b) \ case ConversionType::a##_TO_##b: \ return ConversionType::b##_TO_##a; #define CASE_PAIR(a, b) \ CASE_SINGLE(a, b) \ CASE_SINGLE(b, a) switch (type) { case ConversionType::PASS_THROUGH: return ConversionType::PASS_THROUGH; CASE_PAIR(FLOAT, UNORM8) CASE_PAIR(FLOAT, SNORM8) CASE_PAIR(FLOAT, UNORM16) CASE_PAIR(FLOAT, SNORM16) CASE_PAIR(UI32, UI16) CASE_PAIR(I32, I16) CASE_PAIR(UI32, UI8) CASE_PAIR(I32, I8) CASE_PAIR(FLOAT, HALF) case ConversionType::UNSUPPORTED: return ConversionType::UNSUPPORTED; } #undef CASE_PAIR #undef CASE_SINGLE return ConversionType::UNSUPPORTED; } /* \} */ /* -------------------------------------------------------------------- */ /** \name Data Conversion * \{ */ template struct SignedNormalized { static_assert(std::is_same() || std::is_same()); InnerType value; static constexpr int32_t scalar() { return (1 << (sizeof(InnerType) * 8 - 1)); } static constexpr int32_t delta() { return (1 << (sizeof(InnerType) * 8 - 1)) - 1; } static constexpr int32_t max() { return ((1 << (sizeof(InnerType) * 8)) - 1); } }; template struct UnsignedNormalized { static_assert(std::is_same() || std::is_same()); InnerType value; static constexpr int32_t scalar() { return (1 << (sizeof(InnerType) * 8)) - 1; } static constexpr int32_t max() { return ((1 << (sizeof(InnerType) * 8)) - 1); } }; template struct ComponentValue { InnerType value; }; using UI8 = ComponentValue; using UI16 = ComponentValue; using UI32 = ComponentValue; using I8 = ComponentValue; using I16 = ComponentValue; using I32 = ComponentValue; using F32 = ComponentValue; using F16 = ComponentValue; template void convert_component(SignedNormalized &dst, const F32 &src) { static constexpr int32_t scalar = SignedNormalized::scalar(); static constexpr int32_t delta = SignedNormalized::delta(); static constexpr int32_t max = SignedNormalized::max(); dst.value = (clamp_i((src.value * scalar + delta), 0, max)); } template void convert_component(F32 &dst, const SignedNormalized &src) { static constexpr int32_t scalar = SignedNormalized::scalar(); static constexpr int32_t delta = SignedNormalized::delta(); dst.value = float(int32_t(src.value) - delta) / scalar; } template void convert_component(UnsignedNormalized &dst, const F32 &src) { static constexpr int32_t scalar = UnsignedNormalized::scalar(); static constexpr int32_t max = scalar; dst.value = (clamp_i((src.value * scalar), 0, max)); } template void convert_component(F32 &dst, const UnsignedNormalized &src) { static constexpr int32_t scalar = UnsignedNormalized::scalar(); dst.value = float(src.value) / scalar; } /* Copy the contents of src to dst with out performing any actual conversion.*/ template void convert_component(DestinationType &dst, const SourceType &src) { static_assert(std::is_same() || std::is_same() || std::is_same() || std::is_same() || std::is_same() || std::is_same()); static_assert(std::is_same() || std::is_same() || std::is_same() || std::is_same() || std::is_same() || std::is_same()); static_assert(!std::is_same()); dst.value = src.value; } static void convert_component(F16 &dst, const F32 &src) { dst.value = imath_float_to_half(src.value); } static void convert_component(F32 &dst, const F16 &src) { dst.value = imath_half_to_float(src.value); } /* \} */ template void convert_per_component(MutableSpan dst, Span src) { BLI_assert(src.size() == dst.size()); for (int64_t index : IndexRange(src.size())) { convert_component(dst[index], src[index]); } } template void convert_per_component(void *dst_memory, const void *src_memory, size_t buffer_size, eGPUTextureFormat device_format) { size_t total_components = to_component_len(device_format) * buffer_size; Span src = Span(static_cast(src_memory), total_components); MutableSpan dst = MutableSpan( static_cast(dst_memory), total_components); convert_per_component(dst, src); } static void convert_buffer(void *dst_memory, const void *src_memory, size_t buffer_size, eGPUTextureFormat device_format, ConversionType type) { switch (type) { case ConversionType::UNSUPPORTED: return; case ConversionType::PASS_THROUGH: memcpy(dst_memory, src_memory, buffer_size * to_bytesize(device_format)); return; case ConversionType::UI32_TO_UI16: convert_per_component(dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::UI16_TO_UI32: convert_per_component(dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::UI32_TO_UI8: convert_per_component(dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::UI8_TO_UI32: convert_per_component(dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::I32_TO_I16: convert_per_component(dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::I16_TO_I32: convert_per_component(dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::I32_TO_I8: convert_per_component(dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::I8_TO_I32: convert_per_component(dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::FLOAT_TO_SNORM8: convert_per_component, F32>( dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::SNORM8_TO_FLOAT: convert_per_component>( dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::FLOAT_TO_SNORM16: convert_per_component, F32>( dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::SNORM16_TO_FLOAT: convert_per_component>( dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::FLOAT_TO_UNORM8: convert_per_component, F32>( dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::UNORM8_TO_FLOAT: convert_per_component>( dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::FLOAT_TO_UNORM16: convert_per_component, F32>( dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::UNORM16_TO_FLOAT: convert_per_component>( dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::FLOAT_TO_HALF: convert_per_component(dst_memory, src_memory, buffer_size, device_format); break; case ConversionType::HALF_TO_FLOAT: convert_per_component(dst_memory, src_memory, buffer_size, device_format); break; } } /* -------------------------------------------------------------------- */ /** \name API * \{ */ void convert_host_to_device(void *dst_buffer, const void *src_buffer, size_t buffer_size, eGPUDataFormat host_format, eGPUTextureFormat device_format) { ConversionType conversion_type = host_to_device(host_format, device_format); BLI_assert(conversion_type != ConversionType::UNSUPPORTED); convert_buffer(dst_buffer, src_buffer, buffer_size, device_format, conversion_type); } void convert_device_to_host(void *dst_buffer, const void *src_buffer, size_t buffer_size, eGPUDataFormat host_format, eGPUTextureFormat device_format) { ConversionType conversion_type = reversed(host_to_device(host_format, device_format)); BLI_assert(conversion_type != ConversionType::UNSUPPORTED); convert_buffer(dst_buffer, src_buffer, buffer_size, device_format, conversion_type); } /* \} */ } // namespace blender::gpu