Files
2026-06-14 19:09:18 +01:00

716 lines
33 KiB
C++

// -*- 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 <algorithm>
#include <functional>
#include <limits>
#include <type_traits>
#include <map>
#include <vector>
#include <assert.h>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4201)
#endif
#include <glm/gtc/packing.hpp>
#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<uint8_t>; break;
case 2: codec.getPackedElement = getPackedElement<uint16_t>; break;
case 4: codec.getPackedElement = getPackedElement<uint32_t>; 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<uint32_t>(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<int8_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_SINTPacked<int8_t>;
} else {
codec.decodeUINT = decodeUINT_UINTPacked<uint8_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_UINTPacked<uint8_t>;
}
break;
case 2:
// 16-bit packed elements
if (flags.isSigned) {
codec.decodeSINT = decodeSINT_SINTPacked<int16_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_SINTPacked<int16_t>;
} else {
codec.decodeUINT = decodeUINT_UINTPacked<uint16_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_UINTPacked<uint16_t>;
}
break;
case 4:
// 32-bit packed elements
if (flags.isSigned) {
codec.decodeSINT = decodeSINT_SINTPacked<int32_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_SINTPacked<int32_t>;
} else {
codec.decodeUINT = decodeUINT_UINTPacked<uint32_t>;
if (flags.isNormalized)
codec.decodeFLOAT = decodeFLOAT_UINTPacked<uint32_t>;
}
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<int8_t, 1>; break;
case 2: codec.decodeSINT = decodeSINT_SINTVec<int8_t, 2>; break;
case 3: codec.decodeSINT = decodeSINT_SINTVec<int8_t, 3>; break;
case 4: codec.decodeSINT = decodeSINT_SINTVec<int8_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_SINTVec<int8_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_SINTVec<int8_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_SINTVec<int8_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_SINTVec<int8_t, 4>; break;
default: break;
}
}
break;
case 2:
// 16-bit signed integer
switch (sampleCount) {
case 1: codec.decodeSINT = decodeSINT_SINTVec<int16_t, 1>; break;
case 2: codec.decodeSINT = decodeSINT_SINTVec<int16_t, 2>; break;
case 3: codec.decodeSINT = decodeSINT_SINTVec<int16_t, 3>; break;
case 4: codec.decodeSINT = decodeSINT_SINTVec<int16_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_SINTVec<int16_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_SINTVec<int16_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_SINTVec<int16_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_SINTVec<int16_t, 4>; break;
default: break;
}
}
break;
case 4:
// 32-bit signed integer
switch (sampleCount) {
case 1: codec.decodeSINT = decodeSINT_SINTVec<int32_t, 1>; break;
case 2: codec.decodeSINT = decodeSINT_SINTVec<int32_t, 2>; break;
case 3: codec.decodeSINT = decodeSINT_SINTVec<int32_t, 3>; break;
case 4: codec.decodeSINT = decodeSINT_SINTVec<int32_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_SINTVec<int32_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_SINTVec<int32_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_SINTVec<int32_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_SINTVec<int32_t, 4>; 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<uint8_t, 1>; break;
case 2: codec.decodeUINT = decodeUINT_UINTVec<uint8_t, 2>; break;
case 3: codec.decodeUINT = decodeUINT_UINTVec<uint8_t, 3>; break;
case 4: codec.decodeUINT = decodeUINT_UINTVec<uint8_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint8_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint8_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint8_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint8_t, 4>; break;
default: break;
}
}
break;
case 2:
// 16-bit unsigned integer
switch (sampleCount) {
case 1: codec.decodeUINT = decodeUINT_UINTVec<uint16_t, 1>; break;
case 2: codec.decodeUINT = decodeUINT_UINTVec<uint16_t, 2>; break;
case 3: codec.decodeUINT = decodeUINT_UINTVec<uint16_t, 3>; break;
case 4: codec.decodeUINT = decodeUINT_UINTVec<uint16_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint16_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint16_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint16_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint16_t, 4>; break;
default: break;
}
}
break;
case 4:
// 32-bit unsigned integer
switch (sampleCount) {
case 1: codec.decodeUINT = decodeUINT_UINTVec<uint32_t, 1>; break;
case 2: codec.decodeUINT = decodeUINT_UINTVec<uint32_t, 2>; break;
case 3: codec.decodeUINT = decodeUINT_UINTVec<uint32_t, 3>; break;
case 4: codec.decodeUINT = decodeUINT_UINTVec<uint32_t, 4>; break;
default: flags.valid = false; return;
}
if (flags.isNormalized) {
switch (sampleCount) {
case 1: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint32_t, 1>; break;
case 2: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint32_t, 2>; break;
case 3: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint32_t, 3>; break;
case 4: codec.decodeFLOAT = decodeFLOAT_UINTVec<uint32_t, 4>; 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<SampleInfo> 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 <typename TYPE>
static uint32_t getPackedElement(const ImageCodec* codec, const void* ptr, uint32_t index) {
static_assert(std::is_unsigned_v<TYPE>);
(void)codec; // silences unused parameter warnings in release builds
assert(sizeof(TYPE) == codec->getPackedElementByteSize());
auto data = reinterpret_cast<const TYPE*>(ptr);
return data[index];
}
static glm::vec4 decodeFLOAT_E9B5G5R5(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint32_t*>(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<const uint32_t*>(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<float>::infinity();
} else {
value[channel] = std::numeric_limits<float>::quiet_NaN();
}
}
}
return glm::vec4(value, 1.f);
}
static glm::uvec4 decodeUINT_D16_S8(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint16_t*>(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<const uint16_t*>(ptr);
return glm::vec4(imageio::convertUNORMToFloat(data[0], 16), static_cast<float>(data[1] & 0xFF), 0.f, 1.f);
}
static glm::uvec4 decodeUINT_D24(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint32_t*>(ptr);
return glm::uvec4(data[0] & 0xFFFFFF, 0, 0, 0);
}
static glm::vec4 decodeFLOAT_D24(const ImageCodec*, const void* ptr) {
auto data = reinterpret_cast<const uint32_t*>(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<const uint32_t*>(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<const uint32_t*>(ptr);
return glm::vec4(imageio::convertUNORMToFloat(data[0] >> 8, 24), static_cast<float>(data[0] & 0xFF), 0.f, 1.f);
}
static glm::vec4 decodeFLOAT_D32_S8(const ImageCodec*, const void* ptr) {
auto data_float = reinterpret_cast<const float*>(ptr);
auto data_uint = reinterpret_cast<const uint32_t*>(ptr);
return glm::vec4(data_float[0], static_cast<float>(data_uint[1] & 0xFF), 0.f, 1.f);
}
template <int COMPONENTS>
static glm::vec4 decodeFLOAT_FP32Vec(const ImageCodec*, const void* ptr) {
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const float*>(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 <int COMPONENTS>
static glm::vec4 decodeFLOAT_FP16Vec(const ImageCodec*, const void* ptr) {
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const uint16_t*>(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 <int COMPONENTS>
static glm::vec4 decodeFLOAT_SFIXED5_NV(const ImageCodec*, const void* ptr) {
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const int16_t*>(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 <typename TYPE, int COMPONENTS>
static glm::uvec4 decodeUINT_UINTVec(const ImageCodec*, const void* ptr) {
static_assert(std::is_unsigned_v<TYPE>);
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const TYPE*>(ptr);
glm::uvec4 result(0, 0, 0, 0);
for (int i = 0; i < COMPONENTS; ++i)
result[i] = data[i];
return result;
}
template <typename TYPE, int COMPONENTS>
static glm::vec4 decodeFLOAT_UINTVec(const ImageCodec*, const void* ptr) {
static_assert(std::is_unsigned_v<TYPE>);
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const TYPE*>(ptr);
const auto upper = static_cast<float>((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<float>(data[i]) / upper;
return result;
}
template <typename TYPE, int COMPONENTS>
static glm::ivec4 decodeSINT_SINTVec(const ImageCodec*, const void* ptr) {
static_assert(std::is_signed_v<TYPE>);
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const TYPE*>(ptr);
glm::ivec4 result(0, 0, 0, 0);
for (int i = 0; i < COMPONENTS; ++i)
result[i] = data[i];
return result;
}
template <typename TYPE, int COMPONENTS>
static glm::vec4 decodeFLOAT_SINTVec(const ImageCodec*, const void* ptr) {
static_assert(std::is_signed_v<TYPE>);
static_assert((COMPONENTS > 0) && (COMPONENTS <= 4));
auto data = reinterpret_cast<const TYPE*>(ptr);
const auto upper = static_cast<float>((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<float>(data[i]) / upper, -1.f);
return result;
}
template <typename TYPE>
static glm::uvec4 decodeUINT_UINTPacked(const ImageCodec* codec, const void* ptr) {
static_assert(std::is_unsigned_v<TYPE>);
auto data = reinterpret_cast<const TYPE*>(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<glm::length_t>(i)] = (data[info.elementIndex] >> info.bitOffset) & ((1u << info.bitLength) - 1u);
}
return result;
}
template <typename TYPE>
static glm::vec4 decodeFLOAT_UINTPacked(const ImageCodec* codec, const void* ptr) {
static_assert(std::is_unsigned_v<TYPE>);
auto data = reinterpret_cast<const TYPE*>(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<float>((1u << info.bitLength) - 1u);
uint32_t rawValue = (data[info.elementIndex] >> info.bitOffset) & ((1u << info.bitLength) - 1u);
result[static_cast<glm::length_t>(i)] = static_cast<float>(rawValue) / upper;
}
return result;
}
template <typename TYPE>
static glm::ivec4 decodeSINT_SINTPacked(const ImageCodec* codec, const void* ptr) {
static_assert(std::is_signed_v<TYPE>);
auto data = reinterpret_cast<const std::make_unsigned_t<TYPE>*>(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<glm::length_t>(i)] = imageio::sign_extend(rawValue, info.bitLength);
}
return result;
}
template <typename TYPE>
static glm::vec4 decodeFLOAT_SINTPacked(const ImageCodec* codec, const void* ptr) {
static_assert(std::is_signed_v<TYPE>);
auto data = reinterpret_cast<const std::make_unsigned_t<TYPE>*>(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<float>((1u << (info.bitLength - 1)) - 1u);
uint32_t rawValue = (data[info.elementIndex] >> info.bitOffset) & ((1u << info.bitLength) - 1u);
result[static_cast<glm::length_t>(i)] = static_cast<float>(imageio::sign_extend(rawValue, info.bitLength)) / upper;
}
return result;
}
};