// -*- tab-width: 4; -*- // vi: set sw=2 ts=4 expandtab: // Copyright 2023-2024 The Khronos Group Inc. // Copyright 2023-2024 RasterGrid Kft. // SPDX-License-Identifier: Apache-2.0 //! //! @internal //! @~English //! @file //! //! @brief Internal Image Codec class //! #pragma once #include #include #include #include #include #include #include #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4201) #endif #include #ifdef _MSC_VER #pragma warning(pop) #endif #include "imageio_utility.h" #include "vkformat_enum.h" class ImageCodec { public: struct TexelBlockCodec { }; ImageCodec() { flags.valid = false; flags.isBlockCompressed = false; flags.isPacked = false; flags.isFloat = false; flags.isFloatHalf = false; flags.isSigned = false; flags.isNormalized = false; } ImageCodec(VkFormat vkFormat, uint32_t typeSize, const uint32_t* dfd) { flags.valid = true; flags.isBlockCompressed = false; flags.isPacked = false; flags.isFloat = false; flags.isFloatHalf = false; flags.isSigned = false; flags.isNormalized = false; const auto* bdfd = dfd + 1; const auto model = khr_df_model_e(KHR_DFDVAL(bdfd, MODEL)); texelBlockDimensions[0] = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION0) + 1; texelBlockDimensions[1] = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION1) + 1; texelBlockDimensions[2] = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION2) + 1; texelBlockDimensions[3] = KHR_DFDVAL(bdfd, TEXELBLOCKDIMENSION3) + 1; packedElementByteSize = typeSize; // Packed element size must always be 1, 2, or 4 currently // (for block compressed formats the element size is considered 1 by convention) switch (packedElementByteSize) { case 1: codec.getPackedElement = getPackedElement; break; case 2: codec.getPackedElement = getPackedElement; break; case 4: codec.getPackedElement = getPackedElement; break; default: flags.valid = false; return; } // We initialize the packed element count here based on the first plane's size and the element size const auto firstPlaneBytes = KHR_DFDVAL(bdfd, BYTESPLANE0); packedElementCount = firstPlaneBytes / packedElementByteSize; // We can do the above because we do not currently support multiple planes if (KHR_DFDVAL(bdfd, BYTESPLANE1) != 0) { flags.valid = false; return; } // If packedElementCount is zero, then there's something wrong with bytesPlane0 being zero if (packedElementCount == 0) { flags.valid = false; return; } // By default we do not have directly accessible channels // (e.g. for block compressed we can only access packed data) channels = 0; switch (model) { case KHR_DF_MODEL_RGBSDA: [[fallthrough]]; case KHR_DF_MODEL_YUVSDA: [[fallthrough]]; case KHR_DF_MODEL_YIQSDA: [[fallthrough]]; case KHR_DF_MODEL_LABSDA: [[fallthrough]]; case KHR_DF_MODEL_CMYKA: [[fallthrough]]; case KHR_DF_MODEL_XYZW: [[fallthrough]]; case KHR_DF_MODEL_HSVA_ANG: [[fallthrough]]; case KHR_DF_MODEL_HSLA_ANG: [[fallthrough]]; case KHR_DF_MODEL_HSVA_HEX: [[fallthrough]]; case KHR_DF_MODEL_HSLA_HEX: [[fallthrough]]; case KHR_DF_MODEL_YCGCOA: [[fallthrough]]; case KHR_DF_MODEL_YCCBCCRC: [[fallthrough]]; case KHR_DF_MODEL_ICTCP: [[fallthrough]]; case KHR_DF_MODEL_CIEXYZ: [[fallthrough]]; case KHR_DF_MODEL_CIEXYY: // These color models are handled as simple per-channel texel blocks switch (vkFormat) { case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: flags.isFloat = true; channels = 3; codec.decodeFLOAT = decodeFLOAT_E9B5G5R5; break; case VK_FORMAT_B10G11R11_UFLOAT_PACK32: channels = 3; codec.decodeFLOAT = decodeFLOAT_B10G11R11; break; case VK_FORMAT_R16G16_SFIXED5_NV: channels = 2; codec.decodeFLOAT = decodeFLOAT_SFIXED5_NV<2>; break; case VK_FORMAT_D16_UNORM_S8_UINT: flags.isNormalized = true; channels = 2; codec.decodeUINT = decodeUINT_D16_S8; codec.decodeFLOAT = decodeFLOAT_D16_S8; break; case VK_FORMAT_X8_D24_UNORM_PACK32: flags.isNormalized = true; channels = 1; codec.decodeUINT = decodeUINT_D24; codec.decodeFLOAT = decodeFLOAT_D24; break; case VK_FORMAT_D24_UNORM_S8_UINT: flags.isNormalized = true; channels = 2; codec.decodeUINT = decodeUINT_D24_S8; codec.decodeFLOAT = decodeFLOAT_D24_S8; break; case VK_FORMAT_D32_SFLOAT_S8_UINT: channels = 2; codec.decodeFLOAT = decodeFLOAT_D32_S8; break; default: { // For other formats we only support cases where the number formats match across the samples const auto sampleCount = KHR_DFDSAMPLECOUNT(bdfd); const auto firstDataType = KHR_DFDSVAL(bdfd, 0, QUALIFIERS) & ~KHR_DF_SAMPLE_DATATYPE_LINEAR; const auto firstBitLength = KHR_DFDSVAL(bdfd, 0, BITLENGTH) + 1; const auto sampleUpper = KHR_DFDSVAL(bdfd, 0, SAMPLEUPPER); flags.isFloat = (firstDataType & KHR_DF_SAMPLE_DATATYPE_FLOAT) != 0; flags.isFloatHalf = flags.isFloat && (firstBitLength == 16); flags.isSigned = (firstDataType & KHR_DF_SAMPLE_DATATYPE_SIGNED) != 0; flags.isNormalized = sampleUpper != (flags.isFloat ? imageio::bit_cast(1.f) : 1u); // Channel count matches sample count for these cases channels = sampleCount; if (firstDataType & KHR_DF_SAMPLE_DATATYPE_EXPONENT) { // No other shared exponent formats are supported other than the ones handled explicitly flags.valid = false; return; } for (uint32_t i = 0; i < sampleCount; ++i) { const auto dataType = KHR_DFDSVAL(bdfd, i, QUALIFIERS) & ~KHR_DF_SAMPLE_DATATYPE_LINEAR; const auto bitLength = KHR_DFDSVAL(bdfd, i, BITLENGTH) + 1; // If not all elements match the packed element byte size then this is a packed format if (bitLength != firstBitLength || bitLength != packedElementByteSize * 8) { flags.isPacked = true; } // We do not support mixed component types here as all special cases are handled outside if (dataType != firstDataType) { flags.valid = false; return; } // For floats we also require either 32-bit or 16-bit float as all other // special cases are currently handled outside if (flags.isFloat && (bitLength != 16) && (bitLength != 32)) { flags.valid = false; return; } } if (flags.isFloatHalf) { // Data is a vector of FP16 values switch (sampleCount) { case 1: codec.decodeFLOAT = decodeFLOAT_FP16Vec<1>; break; case 2: codec.decodeFLOAT = decodeFLOAT_FP16Vec<2>; break; case 3: codec.decodeFLOAT = decodeFLOAT_FP16Vec<3>; break; case 4: codec.decodeFLOAT = decodeFLOAT_FP16Vec<4>; break; default: flags.valid = false; return; } } else if (flags.isFloat) { // Data is a vector of FP32 values switch (sampleCount) { case 1: codec.decodeFLOAT = decodeFLOAT_FP32Vec<1>; break; case 2: codec.decodeFLOAT = decodeFLOAT_FP32Vec<2>; break; case 3: codec.decodeFLOAT = decodeFLOAT_FP32Vec<3>; break; case 4: codec.decodeFLOAT = decodeFLOAT_FP32Vec<4>; break; default: flags.valid = false; return; } } else if (flags.isPacked) { // Data is packed so use the more general decoders for (uint32_t i = 0; i < sampleCount; ++i) { const auto bitOffset = KHR_DFDSVAL(bdfd, i, BITOFFSET); const auto bitLength = KHR_DFDSVAL(bdfd, i, BITLENGTH) + 1; SampleInfo sampleInfo{}; sampleInfo.elementIndex = (bitOffset >> 3) / packedElementByteSize; sampleInfo.bitOffset = bitOffset % (packedElementByteSize << 3); sampleInfo.bitLength = bitLength; // If any of the samples straddle the packed elements then there's something wrong if (sampleInfo.bitOffset + sampleInfo.bitLength > (packedElementByteSize << 3)) { flags.valid = false; return; } packedSampleInfo.emplace_back(sampleInfo); } switch (packedElementByteSize) { case 1: // 8-bit packed elements if (flags.isSigned) { codec.decodeSINT = decodeSINT_SINTPacked; if (flags.isNormalized) codec.decodeFLOAT = decodeFLOAT_SINTPacked; } else { codec.decodeUINT = decodeUINT_UINTPacked; if (flags.isNormalized) codec.decodeFLOAT = decodeFLOAT_UINTPacked; } break; case 2: // 16-bit packed elements if (flags.isSigned) { codec.decodeSINT = decodeSINT_SINTPacked; if (flags.isNormalized) codec.decodeFLOAT = decodeFLOAT_SINTPacked; } else { codec.decodeUINT = decodeUINT_UINTPacked; if (flags.isNormalized) codec.decodeFLOAT = decodeFLOAT_UINTPacked; } break; case 4: // 32-bit packed elements if (flags.isSigned) { codec.decodeSINT = decodeSINT_SINTPacked; if (flags.isNormalized) codec.decodeFLOAT = decodeFLOAT_SINTPacked; } else { codec.decodeUINT = decodeUINT_UINTPacked; if (flags.isNormalized) codec.decodeFLOAT = decodeFLOAT_UINTPacked; } break; default: flags.valid = false; return; } } else { // Data is not packed so we can use the optimized decoders if (flags.isSigned) { switch (packedElementByteSize) { case 1: // 8-bit signed integer switch (sampleCount) { case 1: codec.decodeSINT = decodeSINT_SINTVec; break; case 2: codec.decodeSINT = decodeSINT_SINTVec; break; case 3: codec.decodeSINT = decodeSINT_SINTVec; break; case 4: codec.decodeSINT = decodeSINT_SINTVec; break; default: flags.valid = false; return; } if (flags.isNormalized) { switch (sampleCount) { case 1: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; case 2: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; case 3: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; case 4: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; default: break; } } break; case 2: // 16-bit signed integer switch (sampleCount) { case 1: codec.decodeSINT = decodeSINT_SINTVec; break; case 2: codec.decodeSINT = decodeSINT_SINTVec; break; case 3: codec.decodeSINT = decodeSINT_SINTVec; break; case 4: codec.decodeSINT = decodeSINT_SINTVec; break; default: flags.valid = false; return; } if (flags.isNormalized) { switch (sampleCount) { case 1: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; case 2: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; case 3: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; case 4: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; default: break; } } break; case 4: // 32-bit signed integer switch (sampleCount) { case 1: codec.decodeSINT = decodeSINT_SINTVec; break; case 2: codec.decodeSINT = decodeSINT_SINTVec; break; case 3: codec.decodeSINT = decodeSINT_SINTVec; break; case 4: codec.decodeSINT = decodeSINT_SINTVec; break; default: flags.valid = false; return; } if (flags.isNormalized) { switch (sampleCount) { case 1: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; case 2: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; case 3: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; case 4: codec.decodeFLOAT = decodeFLOAT_SINTVec; break; default: break; } } break; default: flags.valid = false; return; } } else { switch (packedElementByteSize) { case 1: // 8-bit unsigned integer switch (sampleCount) { case 1: codec.decodeUINT = decodeUINT_UINTVec; break; case 2: codec.decodeUINT = decodeUINT_UINTVec; break; case 3: codec.decodeUINT = decodeUINT_UINTVec; break; case 4: codec.decodeUINT = decodeUINT_UINTVec; break; default: flags.valid = false; return; } if (flags.isNormalized) { switch (sampleCount) { case 1: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; case 2: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; case 3: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; case 4: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; default: break; } } break; case 2: // 16-bit unsigned integer switch (sampleCount) { case 1: codec.decodeUINT = decodeUINT_UINTVec; break; case 2: codec.decodeUINT = decodeUINT_UINTVec; break; case 3: codec.decodeUINT = decodeUINT_UINTVec; break; case 4: codec.decodeUINT = decodeUINT_UINTVec; break; default: flags.valid = false; return; } if (flags.isNormalized) { switch (sampleCount) { case 1: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; case 2: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; case 3: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; case 4: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; default: break; } } break; case 4: // 32-bit unsigned integer switch (sampleCount) { case 1: codec.decodeUINT = decodeUINT_UINTVec; break; case 2: codec.decodeUINT = decodeUINT_UINTVec; break; case 3: codec.decodeUINT = decodeUINT_UINTVec; break; case 4: codec.decodeUINT = decodeUINT_UINTVec; break; default: flags.valid = false; return; } if (flags.isNormalized) { switch (sampleCount) { case 1: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; case 2: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; case 3: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; case 4: codec.decodeFLOAT = decodeFLOAT_UINTVec; break; default: break; } } break; default: flags.valid = false; return; } } } break; } } break; case KHR_DF_MODEL_BC1A: [[fallthrough]]; case KHR_DF_MODEL_BC2: [[fallthrough]]; case KHR_DF_MODEL_BC3: [[fallthrough]]; case KHR_DF_MODEL_BC4: [[fallthrough]]; case KHR_DF_MODEL_BC5: [[fallthrough]]; case KHR_DF_MODEL_BC6H: [[fallthrough]]; case KHR_DF_MODEL_BC7: [[fallthrough]]; case KHR_DF_MODEL_ETC1: [[fallthrough]]; case KHR_DF_MODEL_ETC2: [[fallthrough]]; case KHR_DF_MODEL_ASTC: [[fallthrough]]; case KHR_DF_MODEL_PVRTC: [[fallthrough]]; case KHR_DF_MODEL_PVRTC2: // These color models are handled is raw compressed blocks flags.isBlockCompressed = true; break; case KHR_DF_MODEL_UASTC: // UASTC needs special handling packedElementByteSize = 1; packedElementCount = 16; flags.isBlockCompressed = true; break; case KHR_DF_MODEL_ETC1S: // ETC1S (as used by BasisLZ) is not supported directly flags.valid = false; return; default: flags.valid = false; return; } texelBlockByteSize = packedElementByteSize * packedElementCount; // If we couldn't determine the texel block size then something went wrong. if (texelBlockByteSize == 0) { flags.valid = false; return; } } operator bool() const { return flags.valid; } constexpr bool isBlockCompressed() const { return flags.isBlockCompressed; } constexpr bool isPacked() const { return flags.isPacked; } constexpr bool isFloat() const { return flags.isFloat; } constexpr bool isFloatHalf() const { return flags.isFloatHalf; } constexpr bool isSigned() const { return flags.isSigned; } constexpr bool isNormalized() const { return flags.isNormalized; } constexpr bool canDecodeUINT() const { return codec.decodeUINT != nullptr; } constexpr bool canDecodeSINT() const { return codec.decodeSINT != nullptr; } constexpr bool canDecodeFLOAT() const { return codec.decodeFLOAT != nullptr; } glm::uvec4 getTexelBlockDimensions() const { return texelBlockDimensions; } constexpr uint32_t getPackedElementByteSize() const { return packedElementByteSize; } constexpr uint32_t getPackedElementCount() const { return packedElementCount; } constexpr uint32_t getTexelBlockByteSize() const { return texelBlockByteSize; } constexpr uint32_t getChannelCount() const { return channels; } glm::uvec4 pixelToTexelBlockSize(glm::uvec4 pixelSize) const { return (pixelSize + texelBlockDimensions - glm::uvec4(1, 1, 1, 1)) / texelBlockDimensions; } uint32_t getPackedElement(const void* ptr, uint32_t index) const { return codec.getPackedElement(this, ptr, index); } glm::uvec4 decodeUINT(const void* ptr) const { return codec.decodeUINT(this, ptr); } glm::ivec4 decodeSINT(const void* ptr) const { return codec.decodeSINT(this, ptr); } glm::vec4 decodeFLOAT(const void* ptr) const { return codec.decodeFLOAT(this, ptr); } private: struct { uint32_t valid : 1; uint32_t isBlockCompressed : 1; uint32_t isPacked : 1; uint32_t isFloat : 1; uint32_t isFloatHalf : 1; uint32_t isSigned : 1; uint32_t isNormalized : 1; } flags; glm::uvec4 texelBlockDimensions = glm::uvec4(0, 0, 0, 0); uint32_t packedElementByteSize = 0; uint32_t packedElementCount = 0; uint32_t texelBlockByteSize = 0; uint32_t channels = 0; struct SampleInfo { uint32_t elementIndex; uint32_t bitOffset; uint32_t bitLength; }; std::vector packedSampleInfo = {}; typedef uint32_t (*GetPackedElement)(const ImageCodec*, const void*, uint32_t); typedef glm::uvec4 (*DecodeUINT)(const ImageCodec*, const void*); typedef glm::ivec4 (*DecodeSINT)(const ImageCodec*, const void*); typedef glm::vec4 (*DecodeFLOAT)(const ImageCodec*, const void*); struct { GetPackedElement getPackedElement = nullptr; DecodeUINT decodeUINT = nullptr; DecodeSINT decodeSINT = nullptr; DecodeFLOAT decodeFLOAT = nullptr; } codec = {}; template static uint32_t getPackedElement(const ImageCodec* codec, const void* ptr, uint32_t index) { static_assert(std::is_unsigned_v); (void)codec; // silences unused parameter warnings in release builds assert(sizeof(TYPE) == codec->getPackedElementByteSize()); auto data = reinterpret_cast(ptr); return data[index]; } static glm::vec4 decodeFLOAT_E9B5G5R5(const ImageCodec*, const void* ptr) { auto data = reinterpret_cast(ptr); return glm::vec4(glm::unpackF3x9_E1x5(data[0]), 1.f); } static glm::vec4 decodeFLOAT_B10G11R11(const ImageCodec*, const void* ptr) { auto data = reinterpret_cast(ptr); auto value = glm::unpackF2x11_1x10(data[0]); // Need to handle NaN and infinity as special cases, because GLM "swallows" them const uint32_t exponentShifts[] = { 6, 11 + 6, 22 + 5 }; const uint32_t exponentMask = 0x1F; const uint32_t mantissaShifts[] = { 0, 11, 22 }; const uint32_t mantissaMasks[] = { 0x3F, 0x3F, 0x1F }; for (uint32_t channel = 0; channel < 3; ++channel) { const uint32_t exponent = (data[0] >> exponentShifts[channel]) & exponentMask; const uint32_t mantissa = (data[0] >> mantissaShifts[channel]) & mantissaMasks[channel]; if (exponent == 31) { if (mantissa == 0) { value[channel] = std::numeric_limits::infinity(); } else { value[channel] = std::numeric_limits::quiet_NaN(); } } } return glm::vec4(value, 1.f); } static glm::uvec4 decodeUINT_D16_S8(const ImageCodec*, const void* ptr) { auto data = reinterpret_cast(ptr); return glm::uvec4(data[0], data[1] & 0xFF, 0, 0); } static glm::vec4 decodeFLOAT_D16_S8(const ImageCodec*, const void* ptr) { auto data = reinterpret_cast(ptr); return glm::vec4(imageio::convertUNORMToFloat(data[0], 16), static_cast(data[1] & 0xFF), 0.f, 1.f); } static glm::uvec4 decodeUINT_D24(const ImageCodec*, const void* ptr) { auto data = reinterpret_cast(ptr); return glm::uvec4(data[0] & 0xFFFFFF, 0, 0, 0); } static glm::vec4 decodeFLOAT_D24(const ImageCodec*, const void* ptr) { auto data = reinterpret_cast(ptr); return glm::vec4(imageio::convertUNORMToFloat(data[0] & 0xFFFFFF, 24), 0.f, 0.f, 1.f); } static glm::uvec4 decodeUINT_D24_S8(const ImageCodec*, const void* ptr) { auto data = reinterpret_cast(ptr); return glm::uvec4(data[0] >> 8, data[0] & 0xFF, 0, 0); } static glm::vec4 decodeFLOAT_D24_S8(const ImageCodec*, const void* ptr) { auto data = reinterpret_cast(ptr); return glm::vec4(imageio::convertUNORMToFloat(data[0] >> 8, 24), static_cast(data[0] & 0xFF), 0.f, 1.f); } static glm::vec4 decodeFLOAT_D32_S8(const ImageCodec*, const void* ptr) { auto data_float = reinterpret_cast(ptr); auto data_uint = reinterpret_cast(ptr); return glm::vec4(data_float[0], static_cast(data_uint[1] & 0xFF), 0.f, 1.f); } template static glm::vec4 decodeFLOAT_FP32Vec(const ImageCodec*, const void* ptr) { static_assert((COMPONENTS > 0) && (COMPONENTS <= 4)); auto data = reinterpret_cast(ptr); glm::vec4 result(0.f, 0.f, 0.f, 1.f); for (int i = 0; i < COMPONENTS; ++i) result[i] = data[i]; return result; } template static glm::vec4 decodeFLOAT_FP16Vec(const ImageCodec*, const void* ptr) { static_assert((COMPONENTS > 0) && (COMPONENTS <= 4)); auto data = reinterpret_cast(ptr); glm::vec4 result(0.f, 0.f, 0.f, 1.f); for (int i = 0; i < COMPONENTS; ++i) result[i] = imageio::half_to_float(data[i]); return result; } template static glm::vec4 decodeFLOAT_SFIXED5_NV(const ImageCodec*, const void* ptr) { static_assert((COMPONENTS > 0) && (COMPONENTS <= 4)); auto data = reinterpret_cast(ptr); glm::vec4 result(0.f, 0.f, 0.f, 1.f); for (int i = 0; i < COMPONENTS; ++i) result[i] = data[i] / 32.f; return result; } template static glm::uvec4 decodeUINT_UINTVec(const ImageCodec*, const void* ptr) { static_assert(std::is_unsigned_v); static_assert((COMPONENTS > 0) && (COMPONENTS <= 4)); auto data = reinterpret_cast(ptr); glm::uvec4 result(0, 0, 0, 0); for (int i = 0; i < COMPONENTS; ++i) result[i] = data[i]; return result; } template static glm::vec4 decodeFLOAT_UINTVec(const ImageCodec*, const void* ptr) { static_assert(std::is_unsigned_v); static_assert((COMPONENTS > 0) && (COMPONENTS <= 4)); auto data = reinterpret_cast(ptr); const auto upper = static_cast((256u << sizeof(TYPE)) - 1u); glm::vec4 result(0.f, 0.f, 0.f, 1.f); for (int i = 0; i < COMPONENTS; ++i) result[i] = static_cast(data[i]) / upper; return result; } template static glm::ivec4 decodeSINT_SINTVec(const ImageCodec*, const void* ptr) { static_assert(std::is_signed_v); static_assert((COMPONENTS > 0) && (COMPONENTS <= 4)); auto data = reinterpret_cast(ptr); glm::ivec4 result(0, 0, 0, 0); for (int i = 0; i < COMPONENTS; ++i) result[i] = data[i]; return result; } template static glm::vec4 decodeFLOAT_SINTVec(const ImageCodec*, const void* ptr) { static_assert(std::is_signed_v); static_assert((COMPONENTS > 0) && (COMPONENTS <= 4)); auto data = reinterpret_cast(ptr); const auto upper = static_cast((128u << sizeof(TYPE)) - 1u); glm::vec4 result(0.f, 0.f, 0.f, 1.f); for (int i = 0; i < COMPONENTS; ++i) result[i] = std::max(static_cast(data[i]) / upper, -1.f); return result; } template static glm::uvec4 decodeUINT_UINTPacked(const ImageCodec* codec, const void* ptr) { static_assert(std::is_unsigned_v); auto data = reinterpret_cast(ptr); glm::uvec4 result(0, 0, 0, 0); for (std::size_t i = 0; i < codec->packedSampleInfo.size(); ++i) { const auto& info = codec->packedSampleInfo[i]; result[static_cast(i)] = (data[info.elementIndex] >> info.bitOffset) & ((1u << info.bitLength) - 1u); } return result; } template static glm::vec4 decodeFLOAT_UINTPacked(const ImageCodec* codec, const void* ptr) { static_assert(std::is_unsigned_v); auto data = reinterpret_cast(ptr); glm::vec4 result(0.f, 0.f, 0.f, 1.f); for (std::size_t i = 0; i < codec->packedSampleInfo.size(); ++i) { const auto& info = codec->packedSampleInfo[i]; const auto upper = static_cast((1u << info.bitLength) - 1u); uint32_t rawValue = (data[info.elementIndex] >> info.bitOffset) & ((1u << info.bitLength) - 1u); result[static_cast(i)] = static_cast(rawValue) / upper; } return result; } template static glm::ivec4 decodeSINT_SINTPacked(const ImageCodec* codec, const void* ptr) { static_assert(std::is_signed_v); auto data = reinterpret_cast*>(ptr); glm::ivec4 result(0, 0, 0, 0); for (std::size_t i = 0; i < codec->packedSampleInfo.size(); ++i) { const auto& info = codec->packedSampleInfo[i]; uint32_t rawValue = (data[info.elementIndex] >> info.bitOffset) & ((1u << info.bitLength) - 1u); result[static_cast(i)] = imageio::sign_extend(rawValue, info.bitLength); } return result; } template static glm::vec4 decodeFLOAT_SINTPacked(const ImageCodec* codec, const void* ptr) { static_assert(std::is_signed_v); auto data = reinterpret_cast*>(ptr); glm::vec4 result(0.f, 0.f, 0.f, 1.f); for (std::size_t i = 0; i < codec->packedSampleInfo.size(); ++i) { const auto& info = codec->packedSampleInfo[i]; const auto upper = static_cast((1u << (info.bitLength - 1)) - 1u); uint32_t rawValue = (data[info.elementIndex] >> info.bitOffset) & ((1u << info.bitLength) - 1u); result[static_cast(i)] = static_cast(imageio::sign_extend(rawValue, info.bitLength)) / upper; } return result; } };