From a4be1adba20b18385b8f365212b7bb1954c7b7d2 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 21 Jun 2026 14:16:43 +0200 Subject: [PATCH 1/6] feat: dynamically decompress bc5 textures for modman --- src/ModMan/Asset/Image/DynamicAssetsImage.cpp | 46 ++- .../Image/Compression/DecompressorBc5.cpp | 322 ++++++++++++++++++ .../Image/Compression/DecompressorBc5.h | 12 + .../Image/Compression/ImageDecompressor.cpp | 28 ++ .../Image/Compression/ImageDecompressor.h | 23 ++ src/ObjImage/Image/DdsWriter.cpp | 6 +- src/ObjImage/Image/ImageFormat.cpp | 3 + src/ObjImage/Image/ImageFormat.h | 2 + src/ObjImage/Image/Texture.cpp | 42 ++- src/ObjImage/Image/Texture.h | 44 +-- 10 files changed, 485 insertions(+), 43 deletions(-) create mode 100644 src/ObjImage/Image/Compression/DecompressorBc5.cpp create mode 100644 src/ObjImage/Image/Compression/DecompressorBc5.h create mode 100644 src/ObjImage/Image/Compression/ImageDecompressor.cpp create mode 100644 src/ObjImage/Image/Compression/ImageDecompressor.h diff --git a/src/ModMan/Asset/Image/DynamicAssetsImage.cpp b/src/ModMan/Asset/Image/DynamicAssetsImage.cpp index db34f2506..b44b104e9 100644 --- a/src/ModMan/Asset/Image/DynamicAssetsImage.cpp +++ b/src/ModMan/Asset/Image/DynamicAssetsImage.cpp @@ -2,10 +2,11 @@ #include "Context/ModManContext.h" #include "Game/CommonAsset.h" +#include "Image/Compression/ImageDecompressor.h" #include "Image/DdsWriter.h" #include "Image/ImageToCommonConverter.h" +#include "Image/TextureConverter.h" #include "Pool/XAssetInfo.h" -#include "SearchPath/SearchPaths.h" #include "Utils/Logging/Log.h" #include "Utils/StringUtils.h" @@ -44,6 +45,26 @@ namespace return false; } + bool IsWebGlUnsupportedCompression(const ImageFormatId imageFormatId) + { + // TODO: Add BC4 + return imageFormatId == ImageFormatId::BC5; + } + + std::optional UnsupportedUncompressedFormatConversionTarget(const ImageFormatId imageFormatId) + { + static std::unordered_map unsupportedFormatMap{ + {ImageFormatId::B8_G8_R8_X8, ImageFormatId::R8_G8_B8_A8}, + {ImageFormatId::R8_G8_B8, ImageFormatId::B8_G8_R8 }, + }; + + const auto entry = unsupportedFormatMap.find(imageFormatId); + if (entry != unsupportedFormatMap.end()) + return entry->second; + + return std::nullopt; + } + void ImageDds(const webwindowed::dynamic_asset_request& request, webwindowed::dynamic_asset_response& response) { const auto imageName = request.get_query("name"); @@ -68,8 +89,8 @@ namespace assert(zone); const auto gameName = GameId_Names[std::to_underlying(zone->m_game_id)]; - const auto converter = ToCommonConverter::GetForGame(zone->m_game_id); - if (!converter) + const auto toCommonConverter = ToCommonConverter::GetForGame(zone->m_game_id); + if (!toCommonConverter) { con::error("No image converter for game {}", gameName); response.send_response(500); @@ -79,7 +100,7 @@ namespace std::unique_ptr texture; { const auto searchPaths = ModManContext::Get().m_fast_file.GetSearchPaths(); - texture = converter->Convert(*image, searchPaths.Data()); + texture = toCommonConverter->Convert(*image, searchPaths.Data()); if (!texture) { con::warn("Failed to convert image {} of zone {}", *imageName, *zoneName); @@ -88,6 +109,23 @@ namespace } } + const auto originalTextureFormatId = texture->GetFormat()->GetId(); + if (IsWebGlUnsupportedCompression(originalTextureFormatId)) + { + auto* textureDecompressor = ImageDecompressor::GetDecompressorForFormat(originalTextureFormatId); + + if (textureDecompressor) + texture = textureDecompressor->Decompress(*texture); + } + + const auto uncompressedConversionTarget = UnsupportedUncompressedFormatConversionTarget(texture->GetFormat()->GetId()); + if (uncompressedConversionTarget) + { + TextureConverter converter(texture.get(), ImageFormat::ALL_FORMATS[std::to_underlying(*uncompressedConversionTarget)]); + auto newTexture = converter.Convert(); + texture = std::move(newTexture); + } + std::ostringstream ss; DdsWriter output; output.DumpImage(ss, texture.get()); diff --git a/src/ObjImage/Image/Compression/DecompressorBc5.cpp b/src/ObjImage/Image/Compression/DecompressorBc5.cpp new file mode 100644 index 000000000..af8a9b405 --- /dev/null +++ b/src/ObjImage/Image/Compression/DecompressorBc5.cpp @@ -0,0 +1,322 @@ +#include "DecompressorBc5.h" + +#include "Utils/Alignment.h" + +#include + +// uint8_t pixelsRed[16]{ +// static_cast(in[2] & 0x7), +// static_cast((in[2] & 0x38) >> 3), +// static_cast(((in[2] & 0xC0) >> 6) | ((in[3] & 0x1) << 2)), +// static_cast((in[3] & 0xE) >> 1), +// static_cast((in[3] & 0x70) >> 4), +// static_cast(((in[3] & 0x80) >> 7) | ((in[4] & 0x3) << 1)), +// static_cast((in[4] & 0x1C) >> 2), +// static_cast((in[4] & 0xE0) >> 5), +// static_cast(in[5] & 0x7), +// static_cast((in[5] & 0x38) >> 3), +// static_cast(((in[5] & 0xC0) >> 6) | ((in[6] & 0x1) << 2)), +// static_cast((in[6] & 0xE) >> 1), +// static_cast((in[6] & 0x70) >> 4), +// static_cast(((in[6] & 0x80) >> 7) | ((in[7] & 0x3) << 1)), +// static_cast((in[7] & 0x1C) >> 2), +// static_cast((in[7] & 0xE0) >> 5), +// }; +// uint8_t pixelsGreen[16]{ +// static_cast(in[BC5_CHANNEL_SIZE + 2] & 0x7), +// static_cast((in[BC5_CHANNEL_SIZE + 2] & 0x38) >> 3), +// static_cast(((in[BC5_CHANNEL_SIZE + 2] & 0xC0) >> 6) | ((in[BC5_CHANNEL_SIZE + 3] & 0x1) << 2)), +// static_cast((in[BC5_CHANNEL_SIZE + 3] & 0xE) >> 1), +// static_cast((in[BC5_CHANNEL_SIZE + 3] & 0x70) >> 4), +// static_cast(((in[BC5_CHANNEL_SIZE + 3] & 0x80) >> 7) | ((in[BC5_CHANNEL_SIZE + 4] & 0x3) << 1)), +// static_cast((in[BC5_CHANNEL_SIZE + 4] & 0x1C) >> 2), +// static_cast((in[BC5_CHANNEL_SIZE + 4] & 0xE0) >> 5), +// static_cast(in[BC5_CHANNEL_SIZE + 5] & 0x7), +// static_cast((in[BC5_CHANNEL_SIZE + 5] & 0x38) >> 3), +// static_cast(((in[BC5_CHANNEL_SIZE + 5] & 0xC0) >> 6) | ((in[BC5_CHANNEL_SIZE + 6] & 0x1) << 2)), +// static_cast((in[BC5_CHANNEL_SIZE + 6] & 0xE) >> 1), +// static_cast((in[BC5_CHANNEL_SIZE + 6] & 0x70) >> 4), +// static_cast(((in[BC5_CHANNEL_SIZE + 6] & 0x80) >> 7) | ((in[BC5_CHANNEL_SIZE + 7] & 0x3) << 1)), +// static_cast((in[BC5_CHANNEL_SIZE + 7] & 0x1C) >> 2), +// static_cast((in[BC5_CHANNEL_SIZE + 7] & 0xE0) >> 5), +// }; +// in[2] & 0x7 +// (in[2] & 0x38) >> 3 +// ((in[2] & 0xC0) >> 6) | ((in[3] & 0x1) << 2) +// (in[3] & 0xE) >> 1 +// (in[3] & 0x70) >> 4 +// ((in[3] & 0x80) >> 7) | ((in[4] & 0x3) << 1) +// (in[4] & 0x1C) >> 2 +// (in[4] & 0xE0) >> 5 + +namespace +{ + constexpr auto BC5_BLOCK_PIXELS = 4u; + constexpr auto BC5_CHANNEL_SIZE = 8u; + constexpr auto BC5_BLOCK_SIZE = BC5_CHANNEL_SIZE * 2u; + constexpr auto OUT_PIXEL_SIZE = 3u; + constexpr auto OUT_OFFSET_R = 0u; + constexpr auto OUT_OFFSET_G = 1u; + + void SetupColorTables(const uint8_t* in, uint8_t (&colorTableRed)[8], uint8_t (&colorTableGreen)[8]) + { +#define INTERPOLATE_BC5(colorTable, val0, val1, divide) \ + static_cast(static_cast(static_cast(val0) * static_cast((colorTable)[0]) \ + + static_cast(val1) * static_cast((colorTable)[1])) \ + / (divide)) + + colorTableRed[0] = in[0]; + colorTableRed[1] = in[1]; + + if (colorTableRed[0] > colorTableRed[1]) + { + // 6 interpolated color values + colorTableRed[2] = INTERPOLATE_BC5(colorTableRed, 6, 1, 7.0f); + colorTableRed[3] = INTERPOLATE_BC5(colorTableRed, 5, 2, 7.0f); + colorTableRed[4] = INTERPOLATE_BC5(colorTableRed, 4, 3, 7.0f); + colorTableRed[5] = INTERPOLATE_BC5(colorTableRed, 3, 4, 7.0f); + colorTableRed[6] = INTERPOLATE_BC5(colorTableRed, 2, 5, 7.0f); + colorTableRed[7] = INTERPOLATE_BC5(colorTableRed, 1, 6, 7.0f); + } + else + { + // 4 interpolated color values + colorTableRed[2] = INTERPOLATE_BC5(colorTableRed, 4, 1, 5.0f); + colorTableRed[3] = INTERPOLATE_BC5(colorTableRed, 3, 2, 5.0f); + colorTableRed[4] = INTERPOLATE_BC5(colorTableRed, 2, 3, 5.0f); + colorTableRed[5] = INTERPOLATE_BC5(colorTableRed, 1, 4, 5.0f); + colorTableRed[6] = std::numeric_limits::min(); + colorTableRed[7] = std::numeric_limits::max(); + } + + colorTableGreen[0] = in[BC5_CHANNEL_SIZE + 0]; + colorTableGreen[1] = in[BC5_CHANNEL_SIZE + 1]; + + if (colorTableGreen[0] > colorTableGreen[1]) + { + // 6 interpolated color values + colorTableGreen[2] = INTERPOLATE_BC5(colorTableGreen, 6, 1, 7.0f); + colorTableGreen[3] = INTERPOLATE_BC5(colorTableGreen, 5, 2, 7.0f); + colorTableGreen[4] = INTERPOLATE_BC5(colorTableGreen, 4, 3, 7.0f); + colorTableGreen[5] = INTERPOLATE_BC5(colorTableGreen, 3, 4, 7.0f); + colorTableGreen[6] = INTERPOLATE_BC5(colorTableGreen, 2, 5, 7.0f); + colorTableGreen[7] = INTERPOLATE_BC5(colorTableGreen, 1, 6, 7.0f); + } + else + { + // 4 interpolated color values + colorTableGreen[2] = INTERPOLATE_BC5(colorTableGreen, 4, 1, 5.0f); + colorTableGreen[3] = INTERPOLATE_BC5(colorTableGreen, 3, 2, 5.0f); + colorTableGreen[4] = INTERPOLATE_BC5(colorTableGreen, 2, 3, 5.0f); + colorTableGreen[5] = INTERPOLATE_BC5(colorTableGreen, 1, 4, 5.0f); + colorTableGreen[6] = std::numeric_limits::min(); + colorTableGreen[7] = std::numeric_limits::max(); + } + +#undef INTERPOLATE_BC5 + } + + void DecompressBlock(const uint8_t* in, uint8_t* out, const unsigned outPitch) + { + uint8_t colorTableRed[8]; + uint8_t colorTableGreen[8]; + SetupColorTables(in, colorTableRed, colorTableGreen); + const uint32_t dataRed0 = in[2] | static_cast(in[3] << 8u) | static_cast(in[4] << 16u); + const uint32_t dataRed1 = in[5] | static_cast(in[6] << 8u) | static_cast(in[7] << 16u); + const uint32_t dataGreen0 = + in[BC5_CHANNEL_SIZE + 2] | static_cast(in[BC5_CHANNEL_SIZE + 3] << 8) | static_cast(in[BC5_CHANNEL_SIZE + 4] << 16); + const uint32_t dataGreen1 = + in[BC5_CHANNEL_SIZE + 5] | static_cast(in[BC5_CHANNEL_SIZE + 6] << 8) | static_cast(in[BC5_CHANNEL_SIZE + 7] << 16); + + out[0 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 21) >> 21]; + out[0 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 21) >> 21]; + + out[0 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 18) >> 18]; + out[0 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 18) >> 18]; + + out[0 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 15) >> 15]; + out[0 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 15) >> 15]; + + out[0 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 12) >> 12]; + out[0 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 12) >> 12]; + + out[1 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 9) >> 9]; + out[1 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 9) >> 9]; + + out[1 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 6) >> 6]; + out[1 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 6) >> 6]; + + out[1 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 3) >> 3]; + out[1 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 3) >> 3]; + + out[1 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 0) >> 0]; + out[1 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 0) >> 0]; + + out[2 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 21) >> 21]; + out[2 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 21) >> 21]; + + out[2 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 18) >> 18]; + out[2 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 18) >> 18]; + + out[2 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 15) >> 15]; + out[2 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 15) >> 15]; + + out[2 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 12) >> 12]; + out[2 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 12) >> 12]; + + out[3 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 9) >> 9]; + out[3 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 9) >> 9]; + + out[3 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 6) >> 6]; + out[3 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 6) >> 6]; + + out[3 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 3) >> 3]; + out[3 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 3) >> 3]; + + out[3 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 0) >> 0]; + out[3 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 0) >> 0]; + } + + void DecompressBlockEdge(const uint8_t* in, uint8_t* out, const unsigned outPitch, const unsigned width, const unsigned height) + { + assert(width <= BC5_BLOCK_PIXELS); + assert(height <= BC5_BLOCK_PIXELS); + + uint8_t colorTableRed[8]; + uint8_t colorTableGreen[8]; + SetupColorTables(in, colorTableRed, colorTableGreen); + + const uint32_t dataRed0 = in[2] | static_cast(in[3] << 8u) | static_cast(in[4] << 16u); + const uint32_t dataRed1 = in[5] | static_cast(in[6] << 8u) | static_cast(in[7] << 16u); + const uint32_t dataGreen0 = + in[BC5_CHANNEL_SIZE + 2] | static_cast(in[BC5_CHANNEL_SIZE + 3] << 8) | static_cast(in[BC5_CHANNEL_SIZE + 4] << 16); + const uint32_t dataGreen1 = + in[BC5_CHANNEL_SIZE + 5] | static_cast(in[BC5_CHANNEL_SIZE + 6] << 8) | static_cast(in[BC5_CHANNEL_SIZE + 7] << 16); + + const uint8_t pixelsRed[]{ + colorTableRed[dataRed0 & (0x7 << 21) >> 21], + colorTableRed[dataRed0 & (0x7 << 18) >> 18], + colorTableRed[dataRed0 & (0x7 << 15) >> 15], + colorTableRed[dataRed0 & (0x7 << 12) >> 12], + colorTableRed[dataRed0 & (0x7 << 9) >> 9], + colorTableRed[dataRed0 & (0x7 << 6) >> 6], + colorTableRed[dataRed0 & (0x7 << 3) >> 3], + colorTableRed[dataRed0 & (0x7 << 0) >> 0], + colorTableRed[dataRed1 & (0x7 << 21) >> 21], + colorTableRed[dataRed1 & (0x7 << 18) >> 18], + colorTableRed[dataRed1 & (0x7 << 15) >> 15], + colorTableRed[dataRed1 & (0x7 << 12) >> 12], + colorTableRed[dataRed1 & (0x7 << 9) >> 9], + colorTableRed[dataRed1 & (0x7 << 6) >> 6], + colorTableRed[dataRed1 & (0x7 << 3) >> 3], + colorTableRed[dataRed1 & (0x7 << 0) >> 0], + }; + static_assert(std::extent_v == BC5_BLOCK_PIXELS * BC5_BLOCK_PIXELS); + + const uint8_t pixelsGreen[]{ + colorTableGreen[dataGreen0 & (0x7 << 21) >> 21], + colorTableGreen[dataGreen0 & (0x7 << 18) >> 18], + colorTableGreen[dataGreen0 & (0x7 << 15) >> 15], + colorTableGreen[dataGreen0 & (0x7 << 12) >> 12], + colorTableGreen[dataGreen0 & (0x7 << 9) >> 9], + colorTableGreen[dataGreen0 & (0x7 << 6) >> 6], + colorTableGreen[dataGreen0 & (0x7 << 3) >> 3], + colorTableGreen[dataGreen0 & (0x7 << 0) >> 0], + colorTableGreen[dataGreen1 & (0x7 << 21) >> 21], + colorTableGreen[dataGreen1 & (0x7 << 18) >> 18], + colorTableGreen[dataGreen1 & (0x7 << 15) >> 15], + colorTableGreen[dataGreen1 & (0x7 << 12) >> 12], + colorTableGreen[dataGreen1 & (0x7 << 9) >> 9], + colorTableGreen[dataGreen1 & (0x7 << 6) >> 6], + colorTableGreen[dataGreen1 & (0x7 << 3) >> 3], + colorTableGreen[dataGreen1 & (0x7 << 0) >> 0], + }; + static_assert(std::extent_v == BC5_BLOCK_PIXELS * BC5_BLOCK_PIXELS); + + for (auto curHeight = 0u; curHeight < height; curHeight++) + { + for (auto curWidth = 0u; curWidth < width; curWidth++) + { + out[curHeight * outPitch + curWidth * OUT_PIXEL_SIZE + OUT_OFFSET_R] = pixelsRed[curHeight * BC5_BLOCK_PIXELS + curWidth]; + out[curHeight * outPitch + curWidth * OUT_PIXEL_SIZE + OUT_OFFSET_G] = pixelsGreen[curHeight * BC5_BLOCK_PIXELS + curWidth]; + } + } + } +} // namespace + +namespace image +{ + std::unique_ptr DecompressorBc5::Decompress(const Texture& input) + { + const auto width = input.GetWidth(); + const auto height = input.GetHeight(); + const auto depth = input.GetDepth(); + + auto result = Texture::CreateForType(input.GetTextureType(), &ImageFormat::FORMAT_R8_G8_B8, width, height, depth, input.HasMipMaps()); + result->Allocate(); + + const auto faceCount = result->GetFaceCount(); + const auto mipCount = result->GetMipMapCount(); + assert(mipCount == input.GetMipMapCount()); + + for (auto mipLevel = 0; mipLevel < mipCount; mipLevel++) + { + const unsigned mipWidth = width >> mipLevel; + const unsigned mipHeight = height >> mipLevel; + const unsigned mipDepth = depth >> mipLevel; + const unsigned mipPitch = OUT_PIXEL_SIZE * mipWidth; + assert(mipPitch == result->GetFormat()->GetPitch(mipLevel, width)); + + for (auto face = 0; face < faceCount; face++) + { + const auto* bufferIn = input.GetBufferForMipLevel(mipLevel, face); + auto* bufferOut = result->GetBufferForMipLevel(mipLevel, face); + + for (auto curDepth = 0u; curDepth < mipDepth; curDepth++) + { + const auto fullBlocksWidth = utils::AlignToPrevious(mipWidth, BC5_BLOCK_PIXELS); + const auto fullBlocksHeight = utils::AlignToPrevious(mipHeight, BC5_BLOCK_PIXELS); + + for (auto curHeight = 0u; curHeight < fullBlocksHeight; curHeight += BC5_BLOCK_PIXELS) + { + for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC5_BLOCK_PIXELS) + { + DecompressBlock(bufferIn, bufferOut, mipPitch); + bufferIn += BC5_BLOCK_SIZE; + bufferOut += OUT_PIXEL_SIZE * BC5_BLOCK_PIXELS; + } + + if (fullBlocksWidth < mipWidth) + { + const auto edgeBlockWidth = mipWidth - fullBlocksWidth; + DecompressBlockEdge(bufferIn, bufferOut, mipPitch, edgeBlockWidth, BC5_BLOCK_PIXELS); + bufferIn += BC5_BLOCK_SIZE; + bufferOut += OUT_PIXEL_SIZE * edgeBlockWidth; + } + } + + if (fullBlocksHeight < mipHeight) + { + const auto edgeBlockHeight = mipHeight - fullBlocksHeight; + for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC5_BLOCK_PIXELS) + { + DecompressBlockEdge(bufferIn, bufferOut, mipPitch, BC5_BLOCK_PIXELS, edgeBlockHeight); + bufferIn += BC5_BLOCK_SIZE; + bufferOut += OUT_PIXEL_SIZE * BC5_BLOCK_PIXELS; + } + + if (fullBlocksWidth < mipWidth) + { + const auto edgeBlockWidth = mipWidth - fullBlocksWidth; + DecompressBlockEdge(bufferIn, bufferOut, mipPitch, edgeBlockWidth, edgeBlockHeight); + bufferIn += BC5_BLOCK_SIZE; + bufferOut += OUT_PIXEL_SIZE * edgeBlockWidth; + } + } + } + } + } + + return result; + } +} // namespace image diff --git a/src/ObjImage/Image/Compression/DecompressorBc5.h b/src/ObjImage/Image/Compression/DecompressorBc5.h new file mode 100644 index 000000000..71d9ec719 --- /dev/null +++ b/src/ObjImage/Image/Compression/DecompressorBc5.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ImageDecompressor.h" + +namespace image +{ + class DecompressorBc5 final : public ImageDecompressor + { + public: + std::unique_ptr Decompress(const Texture& input) override; + }; +} // namespace image diff --git a/src/ObjImage/Image/Compression/ImageDecompressor.cpp b/src/ObjImage/Image/Compression/ImageDecompressor.cpp new file mode 100644 index 000000000..7c450c709 --- /dev/null +++ b/src/ObjImage/Image/Compression/ImageDecompressor.cpp @@ -0,0 +1,28 @@ +#include "ImageDecompressor.h" + +#include "DecompressorBc5.h" + +namespace image +{ + ImageDecompressor* ImageDecompressor::GetDecompressorForFormat(const ImageFormatId formatId) + { + switch (formatId) + { + case ImageFormatId::BC1: + case ImageFormatId::BC2: + case ImageFormatId::BC3: + case ImageFormatId::BC4: + // TODO + return nullptr; + + case ImageFormatId::BC5: + { + static DecompressorBc5 bc5; + return &bc5; + } + + default: + return nullptr; + } + } +} // namespace image diff --git a/src/ObjImage/Image/Compression/ImageDecompressor.h b/src/ObjImage/Image/Compression/ImageDecompressor.h new file mode 100644 index 000000000..35c2cbc05 --- /dev/null +++ b/src/ObjImage/Image/Compression/ImageDecompressor.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Image/Texture.h" + +#include + +namespace image +{ + class ImageDecompressor + { + public: + ImageDecompressor() = default; + virtual ~ImageDecompressor() = default; + ImageDecompressor(const ImageDecompressor& other) = default; + ImageDecompressor(ImageDecompressor&& other) noexcept = default; + ImageDecompressor& operator=(const ImageDecompressor& other) = default; + ImageDecompressor& operator=(ImageDecompressor&& other) noexcept = default; + + virtual std::unique_ptr Decompress(const Texture& input) = 0; + + static ImageDecompressor* GetDecompressorForFormat(ImageFormatId formatId); + }; +} // namespace image diff --git a/src/ObjImage/Image/DdsWriter.cpp b/src/ObjImage/Image/DdsWriter.cpp index bc3e58002..26b4211ae 100644 --- a/src/ObjImage/Image/DdsWriter.cpp +++ b/src/ObjImage/Image/DdsWriter.cpp @@ -4,15 +4,15 @@ #include "Image/TextureConverter.h" #include -#include #include +#include using namespace image; namespace { - const std::map DDS_CONVERSION_TABLE{ - {ImageFormatId::R8_G8_B8, ImageFormatId::B8_G8_R8_X8}, + const std::unordered_map DDS_CONVERSION_TABLE{ + // {ImageFormatId::R8_G8_B8, ImageFormatId::B8_G8_R8_X8}, }; class DdsWriterInternal diff --git a/src/ObjImage/Image/ImageFormat.cpp b/src/ObjImage/Image/ImageFormat.cpp index 83bb93690..5f785477b 100644 --- a/src/ObjImage/Image/ImageFormat.cpp +++ b/src/ObjImage/Image/ImageFormat.cpp @@ -6,6 +6,7 @@ namespace { const char* IMAGE_FORMAT_NAMES[]{ "R8_G8_B8", + "B8_G8_R8", "B8_G8_R8_X8", "R8_G8_B8_A8", "B8_G8_R8_A8", @@ -173,6 +174,7 @@ namespace image } const ImageFormatUnsigned ImageFormat::FORMAT_R8_G8_B8(ImageFormatId::R8_G8_B8, oat::D3DFMT_R8G8B8, oat::DXGI_FORMAT_UNKNOWN, 24, 0, 8, 8, 8, 16, 8, 0, 0); + const ImageFormatUnsigned ImageFormat::FORMAT_B8_G8_R8(ImageFormatId::B8_G8_R8, oat::D3DFMT_UNKNOWN, oat::DXGI_FORMAT_UNKNOWN, 24, 16, 8, 8, 8, 0, 8, 0, 0); const ImageFormatUnsigned ImageFormat::FORMAT_B8_G8_R8_X8(ImageFormatId::B8_G8_R8_X8, oat::D3DFMT_X8R8G8B8, oat::DXGI_FORMAT_B8G8R8X8_UNORM, 32, 16, 8, 8, 8, 0, 8, 0, 0); const ImageFormatUnsigned @@ -192,6 +194,7 @@ namespace image const ImageFormat* const ImageFormat::ALL_FORMATS[static_cast(ImageFormatId::MAX)]{ &FORMAT_R8_G8_B8, + &FORMAT_B8_G8_R8, &FORMAT_B8_G8_R8_X8, &FORMAT_R8_G8_B8_A8, &FORMAT_B8_G8_R8_A8, diff --git a/src/ObjImage/Image/ImageFormat.h b/src/ObjImage/Image/ImageFormat.h index dceb84ee3..b4255cd7a 100644 --- a/src/ObjImage/Image/ImageFormat.h +++ b/src/ObjImage/Image/ImageFormat.h @@ -11,6 +11,7 @@ namespace image enum class ImageFormatId : std::uint8_t { R8_G8_B8, + B8_G8_R8, B8_G8_R8_X8, R8_G8_B8_A8, B8_G8_R8_A8, @@ -61,6 +62,7 @@ namespace image [[nodiscard]] virtual size_t GetSizeOfMipLevel(unsigned mipLevel, unsigned width, unsigned height, unsigned depth) const = 0; static const ImageFormatUnsigned FORMAT_R8_G8_B8; + static const ImageFormatUnsigned FORMAT_B8_G8_R8; static const ImageFormatUnsigned FORMAT_B8_G8_R8_X8; static const ImageFormatUnsigned FORMAT_R8_G8_B8_A8; static const ImageFormatUnsigned FORMAT_B8_G8_R8_A8; diff --git a/src/ObjImage/Image/Texture.cpp b/src/ObjImage/Image/Texture.cpp index 2b82fa9b4..23ec24d6e 100644 --- a/src/ObjImage/Image/Texture.cpp +++ b/src/ObjImage/Image/Texture.cpp @@ -10,36 +10,46 @@ namespace image // ================= Texture ==================== // ============================================== Texture::Texture(const ImageFormat* format, const bool mipMaps) + : m_format(format), + m_has_mip_maps(mipMaps) { - m_format = format; - m_has_mip_maps = mipMaps; - m_data = nullptr; } Texture::Texture(Texture&& other) noexcept + : m_format(other.m_format), + m_has_mip_maps(other.m_has_mip_maps), + m_data(std::move(other.m_data)) { - m_format = other.m_format; - m_has_mip_maps = other.m_has_mip_maps; - m_data = other.m_data; - - other.m_data = nullptr; } Texture& Texture::operator=(Texture&& other) noexcept { m_format = other.m_format; m_has_mip_maps = other.m_has_mip_maps; - m_data = other.m_data; + m_data = std::move(other.m_data); other.m_data = nullptr; return *this; } - Texture::~Texture() + Texture::~Texture() = default; + + std::unique_ptr Texture::CreateForType( + const TextureType type, const ImageFormat* format, const unsigned width, const unsigned height, const unsigned depth, const bool mipMaps) { - delete[] m_data; - m_data = nullptr; + switch (type) + { + case TextureType::T_2D: + return std::make_unique(format, width, height, mipMaps); + case TextureType::T_3D: + return std::make_unique(format, width, height, depth, mipMaps); + case TextureType::T_CUBE: + return std::make_unique(format, width, height, mipMaps); + } + + assert(false); + return nullptr; } const ImageFormat* Texture::GetFormat() const @@ -59,8 +69,8 @@ namespace image if (storageRequirement > 0) { - m_data = new uint8_t[storageRequirement]; - memset(m_data, 0, storageRequirement); + m_data = std::make_unique(storageRequirement); + memset(m_data.get(), 0, storageRequirement); } } @@ -218,7 +228,7 @@ namespace image // ============================================== // =============== TextureCube ================== // ============================================== - const int TextureCube::FACE_COUNT = 6; + static constexpr int FACE_COUNT = 6; TextureCube::TextureCube(const ImageFormat* format, const unsigned width, const unsigned height) : Texture2D(format, width, height) @@ -381,7 +391,7 @@ namespace image int Texture3D::GetMipMapCount() const { - unsigned maxDimension = std::max(std::max(m_width, m_height), m_depth); + unsigned maxDimension = std::max({m_width, m_height, m_depth}); int mipMapCount = 0; diff --git a/src/ObjImage/Image/Texture.h b/src/ObjImage/Image/Texture.h index 3ce0b9f60..169188459 100644 --- a/src/ObjImage/Image/Texture.h +++ b/src/ObjImage/Image/Texture.h @@ -1,7 +1,9 @@ #pragma once + #include "ImageFormat.h" #include +#include namespace image { @@ -14,20 +16,13 @@ namespace image class Texture { - protected: - const ImageFormat* m_format; - bool m_has_mip_maps; - uint8_t* m_data; - - Texture(const ImageFormat* format, bool mipMaps); - Texture(Texture&& other) noexcept; - - Texture& operator=(Texture&& other) noexcept; - public: Texture(const Texture& other) = delete; virtual ~Texture(); + static std::unique_ptr + CreateForType(TextureType type, const ImageFormat* format, unsigned width, unsigned height, unsigned depth, bool mipMaps); + Texture& operator=(const Texture& other) = delete; [[nodiscard]] virtual TextureType GetTextureType() const = 0; @@ -49,14 +44,20 @@ namespace image [[nodiscard]] bool HasMipMaps() const; [[nodiscard]] virtual int GetMipMapCount() const = 0; + + protected: + const ImageFormat* m_format; + bool m_has_mip_maps; + std::unique_ptr m_data; + + Texture(const ImageFormat* format, bool mipMaps); + Texture(Texture&& other) noexcept; + + Texture& operator=(Texture&& other) noexcept; }; class Texture2D : public Texture { - protected: - unsigned m_width; - unsigned m_height; - public: Texture2D(const ImageFormat* format, unsigned width, unsigned height); Texture2D(const ImageFormat* format, unsigned width, unsigned height, bool mipMaps); @@ -79,12 +80,14 @@ namespace image [[nodiscard]] const uint8_t* GetBufferForMipLevel(int mipLevel, int face) const override; [[nodiscard]] int GetMipMapCount() const override; + + protected: + unsigned m_width; + unsigned m_height; }; class TextureCube final : public Texture2D { - static const int FACE_COUNT; - public: TextureCube(const ImageFormat* format, unsigned width, unsigned height); TextureCube(const ImageFormat* format, unsigned width, unsigned height, bool mipMaps); @@ -105,10 +108,6 @@ namespace image class Texture3D final : public Texture { - unsigned m_width; - unsigned m_height; - unsigned m_depth; - public: Texture3D(const ImageFormat* format, unsigned width, unsigned height, unsigned depth); Texture3D(const ImageFormat* format, unsigned width, unsigned height, unsigned depth, bool mipMaps); @@ -131,5 +130,10 @@ namespace image [[nodiscard]] const uint8_t* GetBufferForMipLevel(int mipLevel, int face) const override; [[nodiscard]] int GetMipMapCount() const override; + + private: + unsigned m_width; + unsigned m_height; + unsigned m_depth; }; } // namespace image From d2e9401d863e027a6fc9da166074721b3740006d Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 21 Jun 2026 14:53:26 +0200 Subject: [PATCH 2/6] chore: restructure image format class --- src/ModMan/Asset/Image/DynamicAssetsImage.cpp | 2 +- .../Image/Compression/DecompressorBc5.cpp | 2 +- src/ObjImage/Image/DdsLoader.cpp | 14 +- src/ObjImage/Image/DdsWriter.cpp | 2 +- src/ObjImage/Image/Dx12TextureLoader.cpp | 2 +- src/ObjImage/Image/Dx9TextureLoader.cpp | 2 +- src/ObjImage/Image/ImageFormat.cpp | 164 ++++++++++++------ src/ObjImage/Image/ImageFormat.h | 118 ++++++++----- src/ObjImage/Image/IwiLoader.cpp | 77 ++++---- src/ObjWriting/Image/ImageDumper.cpp.template | 2 +- .../ObjCommonTests/Image/ImageFormatTests.cpp | 8 +- 11 files changed, 236 insertions(+), 157 deletions(-) diff --git a/src/ModMan/Asset/Image/DynamicAssetsImage.cpp b/src/ModMan/Asset/Image/DynamicAssetsImage.cpp index b44b104e9..812f94451 100644 --- a/src/ModMan/Asset/Image/DynamicAssetsImage.cpp +++ b/src/ModMan/Asset/Image/DynamicAssetsImage.cpp @@ -121,7 +121,7 @@ namespace const auto uncompressedConversionTarget = UnsupportedUncompressedFormatConversionTarget(texture->GetFormat()->GetId()); if (uncompressedConversionTarget) { - TextureConverter converter(texture.get(), ImageFormat::ALL_FORMATS[std::to_underlying(*uncompressedConversionTarget)]); + TextureConverter converter(texture.get(), ImageFormat::GetImageFormatById(*uncompressedConversionTarget)); auto newTexture = converter.Convert(); texture = std::move(newTexture); } diff --git a/src/ObjImage/Image/Compression/DecompressorBc5.cpp b/src/ObjImage/Image/Compression/DecompressorBc5.cpp index af8a9b405..dc40ba5de 100644 --- a/src/ObjImage/Image/Compression/DecompressorBc5.cpp +++ b/src/ObjImage/Image/Compression/DecompressorBc5.cpp @@ -252,7 +252,7 @@ namespace image const auto height = input.GetHeight(); const auto depth = input.GetDepth(); - auto result = Texture::CreateForType(input.GetTextureType(), &ImageFormat::FORMAT_R8_G8_B8, width, height, depth, input.HasMipMaps()); + auto result = Texture::CreateForType(input.GetTextureType(), &format::R8_G8_B8, width, height, depth, input.HasMipMaps()); result->Allocate(); const auto faceCount = result->GetFaceCount(); diff --git a/src/ObjImage/Image/DdsLoader.cpp b/src/ObjImage/Image/DdsLoader.cpp index 7a49cf2f3..8126ef92b 100644 --- a/src/ObjImage/Image/DdsLoader.cpp +++ b/src/ObjImage/Image/DdsLoader.cpp @@ -87,7 +87,7 @@ namespace return false; } - for (const auto* imageFormat : ImageFormat::ALL_FORMATS) + for (const auto* imageFormat : format::ALL) { if (imageFormat->GetDxgiFormat() == headerDx10.dxgiFormat) { @@ -105,25 +105,25 @@ namespace switch (pf.dwFourCC) { case utils::MakeMagic32('D', 'X', 'T', '1'): - m_format = &ImageFormat::FORMAT_BC1; + m_format = &format::BC1; return true; case utils::MakeMagic32('D', 'X', 'T', '3'): - m_format = &ImageFormat::FORMAT_BC2; + m_format = &format::BC2; return true; case utils::MakeMagic32('D', 'X', 'T', '5'): - m_format = &ImageFormat::FORMAT_BC3; + m_format = &format::BC3; return true; case utils::MakeMagic32('A', 'T', 'I', '1'): case utils::MakeMagic32('B', 'C', '4', 'U'): - m_format = &ImageFormat::FORMAT_BC4; + m_format = &format::BC4; return true; case utils::MakeMagic32('A', 'T', 'I', '2'): case utils::MakeMagic32('B', 'C', '5', 'U'): - m_format = &ImageFormat::FORMAT_BC5; + m_format = &format::BC5; return true; case utils::MakeMagic32('D', 'X', '1', '0'): @@ -165,7 +165,7 @@ namespace ExtractSizeAndOffsetFromMask(pf.dwBBitMask, bOffset, bSize); ExtractSizeAndOffsetFromMask(pf.dwABitMask, aOffset, aSize); - for (const auto* imageFormat : ImageFormat::ALL_FORMATS) + for (const auto* imageFormat : format::ALL) { if (imageFormat->GetType() != ImageFormatType::UNSIGNED) continue; diff --git a/src/ObjImage/Image/DdsWriter.cpp b/src/ObjImage/Image/DdsWriter.cpp index 26b4211ae..5df681ea1 100644 --- a/src/ObjImage/Image/DdsWriter.cpp +++ b/src/ObjImage/Image/DdsWriter.cpp @@ -213,7 +213,7 @@ namespace if (entry != DDS_CONVERSION_TABLE.end()) { - TextureConverter converter(m_texture, ImageFormat::ALL_FORMATS[static_cast(entry->second)]); + TextureConverter converter(m_texture, ImageFormat::GetImageFormatById(entry->second)); m_converted_texture = converter.Convert(); m_texture = m_converted_texture.get(); } diff --git a/src/ObjImage/Image/Dx12TextureLoader.cpp b/src/ObjImage/Image/Dx12TextureLoader.cpp index 85b5d6fc2..f132f0b78 100644 --- a/src/ObjImage/Image/Dx12TextureLoader.cpp +++ b/src/ObjImage/Image/Dx12TextureLoader.cpp @@ -16,7 +16,7 @@ namespace image const ImageFormat* Dx12TextureLoader::GetFormatForDx12Format() const { - for (const auto* i : ImageFormat::ALL_FORMATS) + for (const auto* i : format::ALL) { if (i->GetDxgiFormat() == m_format) return i; diff --git a/src/ObjImage/Image/Dx9TextureLoader.cpp b/src/ObjImage/Image/Dx9TextureLoader.cpp index a4f0645e8..7c57d0c5b 100644 --- a/src/ObjImage/Image/Dx9TextureLoader.cpp +++ b/src/ObjImage/Image/Dx9TextureLoader.cpp @@ -16,7 +16,7 @@ namespace image const ImageFormat* Dx9TextureLoader::GetFormatForDx9Format() const { - for (const auto* i : ImageFormat::ALL_FORMATS) + for (const auto* i : format::ALL) { if (i->GetD3DFormat() == m_format) return i; diff --git a/src/ObjImage/Image/ImageFormat.cpp b/src/ObjImage/Image/ImageFormat.cpp index 5f785477b..e029121e1 100644 --- a/src/ObjImage/Image/ImageFormat.cpp +++ b/src/ObjImage/Image/ImageFormat.cpp @@ -25,26 +25,29 @@ namespace namespace image { - const char* GetImageFormatName(ImageFormatId id) - { - if (id < ImageFormatId::MAX) - return IMAGE_FORMAT_NAMES[static_cast(id)]; - - return "unknown"; - } - - ImageFormat::ImageFormat(const ImageFormatId id, const oat::D3DFORMAT d3dFormat, const oat::DXGI_FORMAT dxgiFormat) + ImageFormat::ImageFormat(const ImageFormatId id, std::string name, const oat::D3DFORMAT d3dFormat, const oat::DXGI_FORMAT dxgiFormat) : m_id(id), + m_name(std::move(name)), m_d3d_format(d3dFormat), m_dxgi_format(dxgiFormat) { } + const ImageFormat* ImageFormat::GetImageFormatById(const ImageFormatId id) + { + return format::ALL[std::to_underlying(id)]; + } + ImageFormatId ImageFormat::GetId() const { return m_id; } + const std::string& ImageFormat::GetName() const + { + return m_name; + } + oat::D3DFORMAT ImageFormat::GetD3DFormat() const { return m_d3d_format; @@ -56,6 +59,7 @@ namespace image } ImageFormatUnsigned::ImageFormatUnsigned(const ImageFormatId id, + std::string name, const oat::D3DFORMAT d3dFormat, const oat::DXGI_FORMAT dxgiFormat, const unsigned bitsPerPixel, @@ -67,7 +71,7 @@ namespace image const unsigned bSize, const unsigned aOffset, const unsigned aSize) - : ImageFormat(id, d3dFormat, dxgiFormat), + : ImageFormat(id, std::move(name), d3dFormat, dxgiFormat), m_bits_per_pixel(bitsPerPixel), m_r_offset(rOffset), m_r_size(rSize), @@ -110,11 +114,43 @@ namespace image return mipWidth * mipHeight * mipDepth * m_bits_per_pixel / 8; } - ImageFormatBlockCompressed::ImageFormatBlockCompressed( - const ImageFormatId id, const oat::D3DFORMAT d3dFormat, const oat::DXGI_FORMAT dxgiFormat, const unsigned blockSize, const unsigned bitsPerBlock) - : ImageFormat(id, d3dFormat, dxgiFormat), + bool ImageFormatUnsigned::HasR() const + { + return m_r_size > 0; + } + + bool ImageFormatUnsigned::HasG() const + { + return m_g_size > 0; + } + + bool ImageFormatUnsigned::HasB() const + { + return m_b_size > 0; + } + + bool ImageFormatUnsigned::HasA() const + { + return m_a_size > 0; + } + + ImageFormatBlockCompressed::ImageFormatBlockCompressed(const ImageFormatId id, + std::string name, + const oat::D3DFORMAT d3dFormat, + const oat::DXGI_FORMAT dxgiFormat, + const unsigned blockSize, + const unsigned bitsPerBlock, + const bool hasR, + const bool hasG, + const bool hasB, + const bool hasA) + : ImageFormat(id, std::move(name), d3dFormat, dxgiFormat), m_block_size(blockSize), - m_bits_per_block(bitsPerBlock) + m_bits_per_block(bitsPerBlock), + m_has_r(hasR), + m_has_g(hasG), + m_has_b(hasB), + m_has_a(hasA) { } @@ -153,59 +189,73 @@ namespace image return blockCount * m_bits_per_block / 8; } - bool ImageFormatUnsigned::HasR() const + bool ImageFormatBlockCompressed::HasR() const { - return m_r_size > 0; + return m_has_r; } - bool ImageFormatUnsigned::HasG() const + bool ImageFormatBlockCompressed::HasG() const { - return m_g_size > 0; + return m_has_g; } - bool ImageFormatUnsigned::HasB() const + bool ImageFormatBlockCompressed::HasB() const { - return m_b_size > 0; + return m_has_b; } - bool ImageFormatUnsigned::HasA() const + bool ImageFormatBlockCompressed::HasA() const { - return m_a_size > 0; + return m_has_a; } - const ImageFormatUnsigned ImageFormat::FORMAT_R8_G8_B8(ImageFormatId::R8_G8_B8, oat::D3DFMT_R8G8B8, oat::DXGI_FORMAT_UNKNOWN, 24, 0, 8, 8, 8, 16, 8, 0, 0); - const ImageFormatUnsigned ImageFormat::FORMAT_B8_G8_R8(ImageFormatId::B8_G8_R8, oat::D3DFMT_UNKNOWN, oat::DXGI_FORMAT_UNKNOWN, 24, 16, 8, 8, 8, 0, 8, 0, 0); - const ImageFormatUnsigned - ImageFormat::FORMAT_B8_G8_R8_X8(ImageFormatId::B8_G8_R8_X8, oat::D3DFMT_X8R8G8B8, oat::DXGI_FORMAT_B8G8R8X8_UNORM, 32, 16, 8, 8, 8, 0, 8, 0, 0); - const ImageFormatUnsigned - ImageFormat::FORMAT_R8_G8_B8_A8(ImageFormatId::R8_G8_B8_A8, oat::D3DFMT_A8B8G8R8, oat::DXGI_FORMAT_R8G8B8A8_UNORM, 32, 0, 8, 8, 8, 16, 8, 24, 8); - const ImageFormatUnsigned - ImageFormat::FORMAT_B8_G8_R8_A8(ImageFormatId::B8_G8_R8_A8, oat::D3DFMT_A8R8G8B8, oat::DXGI_FORMAT_B8G8R8A8_UNORM, 32, 16, 8, 8, 8, 0, 8, 24, 8); - const ImageFormatUnsigned ImageFormat::FORMAT_A8(ImageFormatId::A8, oat::D3DFMT_A8, oat::DXGI_FORMAT_A8_UNORM, 8, 0, 0, 0, 0, 0, 0, 0, 8); - const ImageFormatUnsigned ImageFormat::FORMAT_R16_G16_B16_A16_FLOAT( - ImageFormatId::R16_G16_B16_A16_FLOAT, oat::D3DFMT_A16B16G16R16F, oat::DXGI_FORMAT_R16G16B16A16_FLOAT, 128, 0, 0, 0, 0, 0, 0, 0, 8); - const ImageFormatUnsigned ImageFormat::FORMAT_R8(ImageFormatId::R8, oat::D3DFMT_L8, oat::DXGI_FORMAT_R8_UNORM, 8, 0, 8, 0, 0, 0, 0, 0, 0); - const ImageFormatUnsigned ImageFormat::FORMAT_R8_A8(ImageFormatId::R8_A8, oat::D3DFMT_A8L8, oat::DXGI_FORMAT_UNKNOWN, 16, 0, 8, 0, 0, 0, 0, 8, 8); - const ImageFormatBlockCompressed ImageFormat::FORMAT_BC1(ImageFormatId::BC1, oat::D3DFMT_DXT1, oat::DXGI_FORMAT_BC1_UNORM, 4, 64); - const ImageFormatBlockCompressed ImageFormat::FORMAT_BC2(ImageFormatId::BC2, oat::D3DFMT_DXT3, oat::DXGI_FORMAT_BC2_UNORM, 4, 128); - const ImageFormatBlockCompressed ImageFormat::FORMAT_BC3(ImageFormatId::BC3, oat::D3DFMT_DXT5, oat::DXGI_FORMAT_BC3_UNORM, 4, 128); - const ImageFormatBlockCompressed ImageFormat::FORMAT_BC4(ImageFormatId::BC4, oat::D3DFMT_UNKNOWN, oat::DXGI_FORMAT_BC4_UNORM, 4, 64); - const ImageFormatBlockCompressed ImageFormat::FORMAT_BC5(ImageFormatId::BC5, oat::D3DFMT_UNKNOWN, oat::DXGI_FORMAT_BC5_UNORM, 4, 128); - - const ImageFormat* const ImageFormat::ALL_FORMATS[static_cast(ImageFormatId::MAX)]{ - &FORMAT_R8_G8_B8, - &FORMAT_B8_G8_R8, - &FORMAT_B8_G8_R8_X8, - &FORMAT_R8_G8_B8_A8, - &FORMAT_B8_G8_R8_A8, - &FORMAT_A8, - &FORMAT_R16_G16_B16_A16_FLOAT, - &FORMAT_R8, - &FORMAT_R8_A8, - &FORMAT_BC1, - &FORMAT_BC2, - &FORMAT_BC3, - &FORMAT_BC4, - &FORMAT_BC5, - }; + namespace format + { + const ImageFormatUnsigned R8_G8_B8(ImageFormatId::R8_G8_B8, "R8_G8_B8", oat::D3DFMT_R8G8B8, oat::DXGI_FORMAT_UNKNOWN, 24, 0, 8, 8, 8, 16, 8, 0, 0); + const ImageFormatUnsigned B8_G8_R8(ImageFormatId::B8_G8_R8, "B8_G8_R8", oat::D3DFMT_UNKNOWN, oat::DXGI_FORMAT_UNKNOWN, 24, 16, 8, 8, 8, 0, 8, 0, 0); + const ImageFormatUnsigned + B8_G8_R8_X8(ImageFormatId::B8_G8_R8_X8, "B8_G8_R8_X8", oat::D3DFMT_X8R8G8B8, oat::DXGI_FORMAT_B8G8R8X8_UNORM, 32, 16, 8, 8, 8, 0, 8, 0, 0); + const ImageFormatUnsigned + R8_G8_B8_A8(ImageFormatId::R8_G8_B8_A8, "R8_G8_B8_A8", oat::D3DFMT_A8B8G8R8, oat::DXGI_FORMAT_R8G8B8A8_UNORM, 32, 0, 8, 8, 8, 16, 8, 24, 8); + const ImageFormatUnsigned + B8_G8_R8_A8(ImageFormatId::B8_G8_R8_A8, "B8_G8_R8_A8", oat::D3DFMT_A8R8G8B8, oat::DXGI_FORMAT_B8G8R8A8_UNORM, 32, 16, 8, 8, 8, 0, 8, 24, 8); + const ImageFormatUnsigned A8(ImageFormatId::A8, "A8", oat::D3DFMT_A8, oat::DXGI_FORMAT_A8_UNORM, 8, 0, 0, 0, 0, 0, 0, 0, 8); + const ImageFormatUnsigned R16_G16_B16_A16_FLOAT(ImageFormatId::R16_G16_B16_A16_FLOAT, + "R16_G16_B16_A16_FLOAT", + oat::D3DFMT_A16B16G16R16F, + oat::DXGI_FORMAT_R16G16B16A16_FLOAT, + 128, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 8); + const ImageFormatUnsigned R8(ImageFormatId::R8, "R8", oat::D3DFMT_L8, oat::DXGI_FORMAT_R8_UNORM, 8, 0, 8, 0, 0, 0, 0, 0, 0); + const ImageFormatUnsigned R8_A8(ImageFormatId::R8_A8, "R8_A8", oat::D3DFMT_A8L8, oat::DXGI_FORMAT_UNKNOWN, 16, 0, 8, 0, 0, 0, 0, 8, 8); + const ImageFormatBlockCompressed BC1(ImageFormatId::BC1, "BC1", oat::D3DFMT_DXT1, oat::DXGI_FORMAT_BC1_UNORM, 4, 64, true, true, true, true); + const ImageFormatBlockCompressed BC2(ImageFormatId::BC2, "BC2", oat::D3DFMT_DXT3, oat::DXGI_FORMAT_BC2_UNORM, 4, 128, true, true, true, true); + const ImageFormatBlockCompressed BC3(ImageFormatId::BC3, "BC3", oat::D3DFMT_DXT5, oat::DXGI_FORMAT_BC3_UNORM, 4, 128, true, true, true, true); + const ImageFormatBlockCompressed BC4(ImageFormatId::BC4, "BC4", oat::D3DFMT_UNKNOWN, oat::DXGI_FORMAT_BC4_UNORM, 4, 64, true, false, false, false); + const ImageFormatBlockCompressed BC5(ImageFormatId::BC5, "BC5", oat::D3DFMT_UNKNOWN, oat::DXGI_FORMAT_BC5_UNORM, 4, 128, true, true, false, false); + + const ImageFormat* const ALL[static_cast(ImageFormatId::MAX)]{ + &R8_G8_B8, + &B8_G8_R8, + &B8_G8_R8_X8, + &R8_G8_B8_A8, + &B8_G8_R8_A8, + &A8, + &R16_G16_B16_A16_FLOAT, + &R8, + &R8_A8, + &BC1, + &BC2, + &BC3, + &BC4, + &BC5, + }; + } // namespace format } // namespace image diff --git a/src/ObjImage/Image/ImageFormat.h b/src/ObjImage/Image/ImageFormat.h index b4255cd7a..9cf9844bd 100644 --- a/src/ObjImage/Image/ImageFormat.h +++ b/src/ObjImage/Image/ImageFormat.h @@ -5,6 +5,7 @@ #include #include +#include namespace image { @@ -25,12 +26,9 @@ namespace image BC4, BC5, - MAX, - UNKNOWN + MAX }; - const char* GetImageFormatName(ImageFormatId id); - enum class ImageFormatType : std::uint8_t { UNKNOWN, @@ -43,17 +41,12 @@ namespace image class ImageFormat { - ImageFormatId m_id; - oat::D3DFORMAT m_d3d_format; - oat::DXGI_FORMAT m_dxgi_format; - - protected: - ImageFormat(ImageFormatId id, oat::D3DFORMAT d3dFormat, oat::DXGI_FORMAT dxgiFormat); - public: + static const ImageFormat* GetImageFormatById(ImageFormatId id); virtual ~ImageFormat() = default; [[nodiscard]] ImageFormatId GetId() const; + [[nodiscard]] const std::string& GetName() const; [[nodiscard]] oat::D3DFORMAT GetD3DFormat() const; [[nodiscard]] oat::DXGI_FORMAT GetDxgiFormat() const; @@ -61,37 +54,26 @@ namespace image [[nodiscard]] virtual size_t GetPitch(unsigned mipLevel, unsigned width) const = 0; [[nodiscard]] virtual size_t GetSizeOfMipLevel(unsigned mipLevel, unsigned width, unsigned height, unsigned depth) const = 0; - static const ImageFormatUnsigned FORMAT_R8_G8_B8; - static const ImageFormatUnsigned FORMAT_B8_G8_R8; - static const ImageFormatUnsigned FORMAT_B8_G8_R8_X8; - static const ImageFormatUnsigned FORMAT_R8_G8_B8_A8; - static const ImageFormatUnsigned FORMAT_B8_G8_R8_A8; - static const ImageFormatUnsigned FORMAT_A8; - static const ImageFormatUnsigned FORMAT_R16_G16_B16_A16_FLOAT; // TODO: Float not unsigned - static const ImageFormatUnsigned FORMAT_R8; - static const ImageFormatUnsigned FORMAT_R8_A8; - static const ImageFormatBlockCompressed FORMAT_BC1; - static const ImageFormatBlockCompressed FORMAT_BC2; - static const ImageFormatBlockCompressed FORMAT_BC3; - static const ImageFormatBlockCompressed FORMAT_BC4; - static const ImageFormatBlockCompressed FORMAT_BC5; - static const ImageFormat* const ALL_FORMATS[static_cast(ImageFormatId::MAX)]; + [[nodiscard]] virtual bool HasR() const = 0; + [[nodiscard]] virtual bool HasG() const = 0; + [[nodiscard]] virtual bool HasB() const = 0; + [[nodiscard]] virtual bool HasA() const = 0; + + protected: + ImageFormat(ImageFormatId id, std::string name, oat::D3DFORMAT d3dFormat, oat::DXGI_FORMAT dxgiFormat); + + private: + ImageFormatId m_id; + std::string m_name; + oat::D3DFORMAT m_d3d_format; + oat::DXGI_FORMAT m_dxgi_format; }; class ImageFormatUnsigned final : public ImageFormat { public: - unsigned m_bits_per_pixel; - unsigned m_r_offset; - unsigned m_r_size; - unsigned m_g_offset; - unsigned m_g_size; - unsigned m_b_offset; - unsigned m_b_size; - unsigned m_a_offset; - unsigned m_a_size; - ImageFormatUnsigned(ImageFormatId id, + std::string name, oat::D3DFORMAT d3dFormat, oat::DXGI_FORMAT dxgiFormat, unsigned bitsPerPixel, @@ -108,22 +90,70 @@ namespace image [[nodiscard]] size_t GetPitch(unsigned mipLevel, unsigned width) const override; [[nodiscard]] size_t GetSizeOfMipLevel(unsigned mipLevel, unsigned width, unsigned height, unsigned depth) const override; - [[nodiscard]] bool HasR() const; - [[nodiscard]] bool HasG() const; - [[nodiscard]] bool HasB() const; - [[nodiscard]] bool HasA() const; + [[nodiscard]] bool HasR() const override; + [[nodiscard]] bool HasG() const override; + [[nodiscard]] bool HasB() const override; + [[nodiscard]] bool HasA() const override; + + unsigned m_bits_per_pixel; + unsigned m_r_offset; + unsigned m_r_size; + unsigned m_g_offset; + unsigned m_g_size; + unsigned m_b_offset; + unsigned m_b_size; + unsigned m_a_offset; + unsigned m_a_size; }; class ImageFormatBlockCompressed final : public ImageFormat { public: - unsigned m_block_size; - unsigned m_bits_per_block; - - ImageFormatBlockCompressed(ImageFormatId id, oat::D3DFORMAT d3dFormat, oat::DXGI_FORMAT dxgiFormat, unsigned blockSize, unsigned bitsPerBlock); + ImageFormatBlockCompressed(ImageFormatId id, + std::string name, + oat::D3DFORMAT d3dFormat, + oat::DXGI_FORMAT dxgiFormat, + unsigned blockSize, + unsigned bitsPerBlock, + bool hasR, + bool hasG, + bool hasB, + bool hasA); [[nodiscard]] ImageFormatType GetType() const override; [[nodiscard]] size_t GetPitch(unsigned mipLevel, unsigned width) const override; [[nodiscard]] size_t GetSizeOfMipLevel(unsigned mipLevel, unsigned width, unsigned height, unsigned depth) const override; + + [[nodiscard]] bool HasR() const override; + [[nodiscard]] bool HasG() const override; + [[nodiscard]] bool HasB() const override; + [[nodiscard]] bool HasA() const override; + + unsigned m_block_size; + unsigned m_bits_per_block; + bool m_has_r; + bool m_has_g; + bool m_has_b; + bool m_has_a; }; + + namespace format + { + extern const ImageFormatUnsigned R8_G8_B8; + extern const ImageFormatUnsigned B8_G8_R8; + extern const ImageFormatUnsigned B8_G8_R8_X8; + extern const ImageFormatUnsigned R8_G8_B8_A8; + extern const ImageFormatUnsigned B8_G8_R8_A8; + extern const ImageFormatUnsigned A8; + extern const ImageFormatUnsigned R16_G16_B16_A16_FLOAT; // TODO: Float not unsigned + extern const ImageFormatUnsigned R8; + extern const ImageFormatUnsigned R8_A8; + extern const ImageFormatBlockCompressed BC1; + extern const ImageFormatBlockCompressed BC2; + extern const ImageFormatBlockCompressed BC3; + extern const ImageFormatBlockCompressed BC4; + extern const ImageFormatBlockCompressed BC5; + + extern const ImageFormat* const ALL[std::to_underlying(ImageFormatId::MAX)]; + } // namespace format } // namespace image diff --git a/src/ObjImage/Image/IwiLoader.cpp b/src/ObjImage/Image/IwiLoader.cpp index 503c804f0..ef4bc610d 100644 --- a/src/ObjImage/Image/IwiLoader.cpp +++ b/src/ObjImage/Image/IwiLoader.cpp @@ -5,7 +5,6 @@ #include #include -#include #include using namespace image; @@ -17,23 +16,23 @@ namespace switch (static_cast(format)) { case iwi6::IwiFormat::IMG_FORMAT_BITMAP_RGBA: - return &ImageFormat::FORMAT_R8_G8_B8_A8; + return &format::R8_G8_B8_A8; case iwi6::IwiFormat::IMG_FORMAT_BITMAP_RGB: - return &ImageFormat::FORMAT_R8_G8_B8; + return &format::R8_G8_B8; case iwi6::IwiFormat::IMG_FORMAT_BITMAP_ALPHA: - return &ImageFormat::FORMAT_A8; + return &format::A8; case iwi6::IwiFormat::IMG_FORMAT_DXT1: - return &ImageFormat::FORMAT_BC1; + return &format::BC1; case iwi6::IwiFormat::IMG_FORMAT_DXT3: - return &ImageFormat::FORMAT_BC2; + return &format::BC2; case iwi6::IwiFormat::IMG_FORMAT_DXT5: - return &ImageFormat::FORMAT_BC3; + return &format::BC3; case iwi6::IwiFormat::IMG_FORMAT_DXN: - return &ImageFormat::FORMAT_BC5; + return &format::BC5; case iwi6::IwiFormat::IMG_FORMAT_BITMAP_LUMINANCE_ALPHA: - return &ImageFormat::FORMAT_R8_A8; + return &format::R8_A8; case iwi6::IwiFormat::IMG_FORMAT_BITMAP_LUMINANCE: - return &ImageFormat::FORMAT_R8; + return &format::R8; case iwi6::IwiFormat::IMG_FORMAT_WAVELET_RGBA: // used case iwi6::IwiFormat::IMG_FORMAT_WAVELET_RGB: // used case iwi6::IwiFormat::IMG_FORMAT_WAVELET_LUMINANCE_ALPHA: @@ -122,23 +121,23 @@ namespace switch (static_cast(format)) { case iwi8::IwiFormat::IMG_FORMAT_BITMAP_RGBA: - return &ImageFormat::FORMAT_R8_G8_B8_A8; + return &format::R8_G8_B8_A8; case iwi8::IwiFormat::IMG_FORMAT_BITMAP_RGB: - return &ImageFormat::FORMAT_R8_G8_B8; + return &format::R8_G8_B8; case iwi8::IwiFormat::IMG_FORMAT_BITMAP_ALPHA: - return &ImageFormat::FORMAT_A8; + return &format::A8; case iwi8::IwiFormat::IMG_FORMAT_DXT1: - return &ImageFormat::FORMAT_BC1; + return &format::BC1; case iwi8::IwiFormat::IMG_FORMAT_DXT3: - return &ImageFormat::FORMAT_BC2; + return &format::BC2; case iwi8::IwiFormat::IMG_FORMAT_DXT5: - return &ImageFormat::FORMAT_BC3; + return &format::BC3; case iwi8::IwiFormat::IMG_FORMAT_DXN: - return &ImageFormat::FORMAT_BC5; + return &format::BC5; case iwi8::IwiFormat::IMG_FORMAT_BITMAP_LUMINANCE_ALPHA: - return &ImageFormat::FORMAT_R8_A8; + return &format::R8_A8; case iwi8::IwiFormat::IMG_FORMAT_BITMAP_LUMINANCE: - return &ImageFormat::FORMAT_R8; + return &format::R8; case iwi8::IwiFormat::IMG_FORMAT_WAVELET_RGBA: // used case iwi8::IwiFormat::IMG_FORMAT_WAVELET_RGB: // used case iwi8::IwiFormat::IMG_FORMAT_WAVELET_LUMINANCE_ALPHA: @@ -251,23 +250,23 @@ namespace switch (static_cast(format)) { case iwi13::IwiFormat::IMG_FORMAT_BITMAP_RGBA: - return &ImageFormat::FORMAT_R8_G8_B8_A8; + return &format::R8_G8_B8_A8; case iwi13::IwiFormat::IMG_FORMAT_BITMAP_RGB: - return &ImageFormat::FORMAT_R8_G8_B8; + return &format::R8_G8_B8; case iwi13::IwiFormat::IMG_FORMAT_BITMAP_ALPHA: - return &ImageFormat::FORMAT_A8; + return &format::A8; case iwi13::IwiFormat::IMG_FORMAT_DXT1: - return &ImageFormat::FORMAT_BC1; + return &format::BC1; case iwi13::IwiFormat::IMG_FORMAT_DXT3: - return &ImageFormat::FORMAT_BC2; + return &format::BC2; case iwi13::IwiFormat::IMG_FORMAT_DXT5: - return &ImageFormat::FORMAT_BC3; + return &format::BC3; case iwi13::IwiFormat::IMG_FORMAT_DXN: - return &ImageFormat::FORMAT_BC5; + return &format::BC5; case iwi13::IwiFormat::IMG_FORMAT_BITMAP_LUMINANCE_ALPHA: - return &ImageFormat::FORMAT_R8_A8; + return &format::R8_A8; case iwi13::IwiFormat::IMG_FORMAT_BITMAP_LUMINANCE: - return &ImageFormat::FORMAT_R8; + return &format::R8; case iwi13::IwiFormat::IMG_FORMAT_WAVELET_RGBA: // used case iwi13::IwiFormat::IMG_FORMAT_WAVELET_RGB: // used case iwi13::IwiFormat::IMG_FORMAT_WAVELET_LUMINANCE_ALPHA: @@ -299,7 +298,7 @@ namespace return std::nullopt; } - const auto* format = GetFormat6(header.format); + const auto* format = GetFormat13(header.format); if (format == nullptr) return std::nullopt; @@ -362,26 +361,26 @@ namespace switch (static_cast(format)) { case iwi27::IwiFormat::IMG_FORMAT_BITMAP_RGBA: - return &ImageFormat::FORMAT_R8_G8_B8_A8; + return &format::R8_G8_B8_A8; case iwi27::IwiFormat::IMG_FORMAT_BITMAP_ALPHA: - return &ImageFormat::FORMAT_A8; + return &format::A8; case iwi27::IwiFormat::IMG_FORMAT_DXT1: - return &ImageFormat::FORMAT_BC1; + return &format::BC1; case iwi27::IwiFormat::IMG_FORMAT_DXT3: - return &ImageFormat::FORMAT_BC2; + return &format::BC2; case iwi27::IwiFormat::IMG_FORMAT_DXT5: - return &ImageFormat::FORMAT_BC3; + return &format::BC3; case iwi27::IwiFormat::IMG_FORMAT_DXN: - return &ImageFormat::FORMAT_BC5; + return &format::BC5; case iwi27::IwiFormat::IMG_FORMAT_A16B16G16R16F: assert(false); // Unsupported yet - return &ImageFormat::FORMAT_R16_G16_B16_A16_FLOAT; + return &format::R16_G16_B16_A16_FLOAT; case iwi27::IwiFormat::IMG_FORMAT_BITMAP_RGB: - return &ImageFormat::FORMAT_R8_G8_B8; + return &format::R8_G8_B8; case iwi27::IwiFormat::IMG_FORMAT_BITMAP_LUMINANCE_ALPHA: - return &ImageFormat::FORMAT_R8_A8; + return &format::R8_A8; case iwi27::IwiFormat::IMG_FORMAT_BITMAP_LUMINANCE: - return &ImageFormat::FORMAT_R8; + return &format::R8; case iwi27::IwiFormat::IMG_FORMAT_WAVELET_RGBA: case iwi27::IwiFormat::IMG_FORMAT_WAVELET_RGB: case iwi27::IwiFormat::IMG_FORMAT_WAVELET_LUMINANCE_ALPHA: diff --git a/src/ObjWriting/Image/ImageDumper.cpp.template b/src/ObjWriting/Image/ImageDumper.cpp.template index d8e9b65b6..d5406266d 100644 --- a/src/ObjWriting/Image/ImageDumper.cpp.template +++ b/src/ObjWriting/Image/ImageDumper.cpp.template @@ -109,7 +109,7 @@ namespace image con::warn("Not dumping image {} as {} does not support the image format {}", image->name, GetImageOutputFormatName(ObjWriting::Configuration.ImageOutputFormat), - GetImageFormatName(texture->GetFormat()->GetId())); + texture->GetFormat()->GetName()); return; } diff --git a/test/ObjCommonTests/Image/ImageFormatTests.cpp b/test/ObjCommonTests/Image/ImageFormatTests.cpp index bf7b34a6c..033252473 100644 --- a/test/ObjCommonTests/Image/ImageFormatTests.cpp +++ b/test/ObjCommonTests/Image/ImageFormatTests.cpp @@ -8,12 +8,12 @@ namespace image::image_format { TEST_CASE("ImageFormat: EnsureAllFormatsArrayIndicesAreIds", "[image]") { - REQUIRE(static_cast(ImageFormatId::MAX) == std::extent_v); + REQUIRE(static_cast(ImageFormatId::MAX) == std::extent_v); - for (unsigned i = 0; i < std::extent_v; i++) + for (unsigned i = 0; i < std::extent_v; i++) { - REQUIRE(ImageFormat::ALL_FORMATS[i] != nullptr); - REQUIRE(i == static_cast(ImageFormat::ALL_FORMATS[i]->GetId())); + REQUIRE(format::ALL[i] != nullptr); + REQUIRE(i == static_cast(format::ALL[i]->GetId())); } } } // namespace image::image_format From d957c58369e056bf2d9edcfcb916b6a59bf7428b Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 21 Jun 2026 15:09:33 +0200 Subject: [PATCH 3/6] chore: keep dds file conversions --- src/ImageConverter/ImageConverter.cpp | 8 ++- src/ObjImage/Image/DdsWriter.cpp | 52 +++++++------------ src/ObjImage/Image/DdsWriter.h | 6 ++- src/ObjWriting/Image/ImageDumper.cpp.template | 6 ++- 4 files changed, 34 insertions(+), 38 deletions(-) diff --git a/src/ImageConverter/ImageConverter.cpp b/src/ImageConverter/ImageConverter.cpp index 48ff43fca..685682279 100644 --- a/src/ImageConverter/ImageConverter.cpp +++ b/src/ImageConverter/ImageConverter.cpp @@ -73,10 +73,16 @@ namespace return false; } - const auto loadResult = image::LoadIwi(file); + auto loadResult = image::LoadIwi(file); if (!loadResult) return false; + auto texture(std::move(loadResult->m_texture)); + + auto convertedTexture = ConvertTextureForDdsFileOutputIfNecessary(texture.get()); + if (convertedTexture) + texture = std::move(convertedTexture); + auto outPath = iwiPath; outPath.replace_extension(".dds"); diff --git a/src/ObjImage/Image/DdsWriter.cpp b/src/ObjImage/Image/DdsWriter.cpp index 5df681ea1..ee99855a4 100644 --- a/src/ObjImage/Image/DdsWriter.cpp +++ b/src/ObjImage/Image/DdsWriter.cpp @@ -4,30 +4,14 @@ #include "Image/TextureConverter.h" #include -#include -#include using namespace image; namespace { - const std::unordered_map DDS_CONVERSION_TABLE{ - // {ImageFormatId::R8_G8_B8, ImageFormatId::B8_G8_R8_X8}, - }; - class DdsWriterInternal { public: - static bool SupportsImageFormat(const ImageFormat* imageFormat) - { - return true; - } - - static std::string GetFileExtension() - { - return ".dds"; - } - DdsWriterInternal(std::ostream& stream, const Texture* texture) : m_stream(stream), m_texture(texture), @@ -37,8 +21,6 @@ namespace void DumpImage() { - ConvertTextureIfNecessary(); - DDS_HEADER header{}; PopulateDdsHeader(header); @@ -207,37 +189,39 @@ namespace } } - void ConvertTextureIfNecessary() - { - const auto entry = DDS_CONVERSION_TABLE.find(m_texture->GetFormat()->GetId()); - - if (entry != DDS_CONVERSION_TABLE.end()) - { - TextureConverter converter(m_texture, ImageFormat::GetImageFormatById(entry->second)); - m_converted_texture = converter.Convert(); - m_texture = m_converted_texture.get(); - } - } - std::ostream& m_stream; const Texture* m_texture; - std::unique_ptr m_converted_texture; bool m_use_dx10_extension; }; } // namespace namespace image { - DdsWriter::~DdsWriter() = default; + std::unique_ptr ConvertTextureForDdsFileOutputIfNecessary(const Texture* texture) + { + static const std::unordered_map ddsConversionTable{ + {ImageFormatId::R8_G8_B8, ImageFormatId::B8_G8_R8_X8}, + }; + + const auto entry = ddsConversionTable.find(texture->GetFormat()->GetId()); + + if (entry != ddsConversionTable.end()) + { + TextureConverter converter(texture, ImageFormat::GetImageFormatById(entry->second)); + return converter.Convert(); + } + + return nullptr; + } bool DdsWriter::SupportsImageFormat(const ImageFormat* imageFormat) { - return DdsWriterInternal::SupportsImageFormat(imageFormat); + return true; } std::string DdsWriter::GetFileExtension() { - return DdsWriterInternal::GetFileExtension(); + return ".dds"; } void DdsWriter::DumpImage(std::ostream& stream, const Texture* texture) diff --git a/src/ObjImage/Image/DdsWriter.h b/src/ObjImage/Image/DdsWriter.h index 771d48423..c2490e4bf 100644 --- a/src/ObjImage/Image/DdsWriter.h +++ b/src/ObjImage/Image/DdsWriter.h @@ -2,13 +2,15 @@ #include "ImageWriter.h" +#include + namespace image { + std::unique_ptr ConvertTextureForDdsFileOutputIfNecessary(const Texture* texture); + class DdsWriter final : public ImageWriter { public: - ~DdsWriter() override; - bool SupportsImageFormat(const ImageFormat* imageFormat) override; std::string GetFileExtension() override; void DumpImage(std::ostream& stream, const Texture* texture) override; diff --git a/src/ObjWriting/Image/ImageDumper.cpp.template b/src/ObjWriting/Image/ImageDumper.cpp.template index d5406266d..51ba97b50 100644 --- a/src/ObjWriting/Image/ImageDumper.cpp.template +++ b/src/ObjWriting/Image/ImageDumper.cpp.template @@ -100,9 +100,13 @@ namespace image void CLASS_NAME::DumpAsset(AssetDumpingContext& context, const XAssetInfo& asset) { const auto* image = asset.Asset(); - const auto texture = CONVERTER_NAME().Convert(asset, context.m_obj_search_path); + auto texture = CONVERTER_NAME().Convert(asset, context.m_obj_search_path); if (!texture) return; + + auto convertedTexture = ConvertTextureForDdsFileOutputIfNecessary(texture.get()); + if (convertedTexture) + texture = std::move(convertedTexture); if (!m_writer->SupportsImageFormat(texture->GetFormat())) { From 78c6c91ed2966c85a0b45f20824e3a804a5aebf6 Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 21 Jun 2026 16:20:50 +0200 Subject: [PATCH 4/6] chore: convert all kinds of webgl unsupported formats --- src/ModMan/Asset/Image/DynamicAssetsImage.cpp | 48 +++-- .../Image/Compression/DecompressorBc5.cpp | 175 ++++++++---------- .../Image/Compression/DecompressorBc5.h | 2 +- .../Image/Compression/ImageDecompressor.h | 12 +- src/ObjImage/Image/TextureConverter.cpp | 6 +- src/ObjImage/Image/TextureConverter.h | 1 - 6 files changed, 120 insertions(+), 124 deletions(-) diff --git a/src/ModMan/Asset/Image/DynamicAssetsImage.cpp b/src/ModMan/Asset/Image/DynamicAssetsImage.cpp index 812f94451..0c531ad16 100644 --- a/src/ModMan/Asset/Image/DynamicAssetsImage.cpp +++ b/src/ModMan/Asset/Image/DynamicAssetsImage.cpp @@ -45,17 +45,17 @@ namespace return false; } - bool IsWebGlUnsupportedCompression(const ImageFormatId imageFormatId) - { - // TODO: Add BC4 - return imageFormatId == ImageFormatId::BC5; - } - - std::optional UnsupportedUncompressedFormatConversionTarget(const ImageFormatId imageFormatId) + std::optional NeedsConversionToWebGlSupportedFormat(const ImageFormatId imageFormatId) { static std::unordered_map unsupportedFormatMap{ - {ImageFormatId::B8_G8_R8_X8, ImageFormatId::R8_G8_B8_A8}, + {ImageFormatId::BC4, ImageFormatId::B8_G8_R8 }, + {ImageFormatId::BC5, ImageFormatId::B8_G8_R8 }, + {ImageFormatId::B8_G8_R8_X8, ImageFormatId::B8_G8_R8 }, {ImageFormatId::R8_G8_B8, ImageFormatId::B8_G8_R8 }, + {ImageFormatId::R8_G8_B8_A8, ImageFormatId::B8_G8_R8_A8}, + {ImageFormatId::A8, ImageFormatId::B8_G8_R8 }, + {ImageFormatId::R8, ImageFormatId::B8_G8_R8 }, + {ImageFormatId::R8_A8, ImageFormatId::B8_G8_R8 }, }; const auto entry = unsupportedFormatMap.find(imageFormatId); @@ -109,21 +109,29 @@ namespace } } - const auto originalTextureFormatId = texture->GetFormat()->GetId(); - if (IsWebGlUnsupportedCompression(originalTextureFormatId)) + const auto* originalTextureFormat = texture->GetFormat(); + const auto originalTextureFormatId = originalTextureFormat->GetId(); + const auto targetFormatId = NeedsConversionToWebGlSupportedFormat(originalTextureFormatId); + if (targetFormatId) { - auto* textureDecompressor = ImageDecompressor::GetDecompressorForFormat(originalTextureFormatId); + const auto* targetFormat = ImageFormat::GetImageFormatById(*targetFormatId); + if (originalTextureFormat->GetType() == ImageFormatType::BLOCK_COMPRESSED) + { + auto* textureDecompressor = ImageDecompressor::GetDecompressorForFormat(originalTextureFormatId); + assert(textureDecompressor); - if (textureDecompressor) - texture = textureDecompressor->Decompress(*texture); - } + if (textureDecompressor) + texture = textureDecompressor->Decompress(*texture, targetFormat); + } + else + { + TextureConverter converter(texture.get(), targetFormat); + auto newTexture = converter.Convert(); + assert(newTexture); - const auto uncompressedConversionTarget = UnsupportedUncompressedFormatConversionTarget(texture->GetFormat()->GetId()); - if (uncompressedConversionTarget) - { - TextureConverter converter(texture.get(), ImageFormat::GetImageFormatById(*uncompressedConversionTarget)); - auto newTexture = converter.Convert(); - texture = std::move(newTexture); + if (newTexture) + texture = std::move(newTexture); + } } std::ostringstream ss; diff --git a/src/ObjImage/Image/Compression/DecompressorBc5.cpp b/src/ObjImage/Image/Compression/DecompressorBc5.cpp index dc40ba5de..6a7a64396 100644 --- a/src/ObjImage/Image/Compression/DecompressorBc5.cpp +++ b/src/ObjImage/Image/Compression/DecompressorBc5.cpp @@ -4,59 +4,11 @@ #include -// uint8_t pixelsRed[16]{ -// static_cast(in[2] & 0x7), -// static_cast((in[2] & 0x38) >> 3), -// static_cast(((in[2] & 0xC0) >> 6) | ((in[3] & 0x1) << 2)), -// static_cast((in[3] & 0xE) >> 1), -// static_cast((in[3] & 0x70) >> 4), -// static_cast(((in[3] & 0x80) >> 7) | ((in[4] & 0x3) << 1)), -// static_cast((in[4] & 0x1C) >> 2), -// static_cast((in[4] & 0xE0) >> 5), -// static_cast(in[5] & 0x7), -// static_cast((in[5] & 0x38) >> 3), -// static_cast(((in[5] & 0xC0) >> 6) | ((in[6] & 0x1) << 2)), -// static_cast((in[6] & 0xE) >> 1), -// static_cast((in[6] & 0x70) >> 4), -// static_cast(((in[6] & 0x80) >> 7) | ((in[7] & 0x3) << 1)), -// static_cast((in[7] & 0x1C) >> 2), -// static_cast((in[7] & 0xE0) >> 5), -// }; -// uint8_t pixelsGreen[16]{ -// static_cast(in[BC5_CHANNEL_SIZE + 2] & 0x7), -// static_cast((in[BC5_CHANNEL_SIZE + 2] & 0x38) >> 3), -// static_cast(((in[BC5_CHANNEL_SIZE + 2] & 0xC0) >> 6) | ((in[BC5_CHANNEL_SIZE + 3] & 0x1) << 2)), -// static_cast((in[BC5_CHANNEL_SIZE + 3] & 0xE) >> 1), -// static_cast((in[BC5_CHANNEL_SIZE + 3] & 0x70) >> 4), -// static_cast(((in[BC5_CHANNEL_SIZE + 3] & 0x80) >> 7) | ((in[BC5_CHANNEL_SIZE + 4] & 0x3) << 1)), -// static_cast((in[BC5_CHANNEL_SIZE + 4] & 0x1C) >> 2), -// static_cast((in[BC5_CHANNEL_SIZE + 4] & 0xE0) >> 5), -// static_cast(in[BC5_CHANNEL_SIZE + 5] & 0x7), -// static_cast((in[BC5_CHANNEL_SIZE + 5] & 0x38) >> 3), -// static_cast(((in[BC5_CHANNEL_SIZE + 5] & 0xC0) >> 6) | ((in[BC5_CHANNEL_SIZE + 6] & 0x1) << 2)), -// static_cast((in[BC5_CHANNEL_SIZE + 6] & 0xE) >> 1), -// static_cast((in[BC5_CHANNEL_SIZE + 6] & 0x70) >> 4), -// static_cast(((in[BC5_CHANNEL_SIZE + 6] & 0x80) >> 7) | ((in[BC5_CHANNEL_SIZE + 7] & 0x3) << 1)), -// static_cast((in[BC5_CHANNEL_SIZE + 7] & 0x1C) >> 2), -// static_cast((in[BC5_CHANNEL_SIZE + 7] & 0xE0) >> 5), -// }; -// in[2] & 0x7 -// (in[2] & 0x38) >> 3 -// ((in[2] & 0xC0) >> 6) | ((in[3] & 0x1) << 2) -// (in[3] & 0xE) >> 1 -// (in[3] & 0x70) >> 4 -// ((in[3] & 0x80) >> 7) | ((in[4] & 0x3) << 1) -// (in[4] & 0x1C) >> 2 -// (in[4] & 0xE0) >> 5 - namespace { constexpr auto BC5_BLOCK_PIXELS = 4u; constexpr auto BC5_CHANNEL_SIZE = 8u; constexpr auto BC5_BLOCK_SIZE = BC5_CHANNEL_SIZE * 2u; - constexpr auto OUT_PIXEL_SIZE = 3u; - constexpr auto OUT_OFFSET_R = 0u; - constexpr auto OUT_OFFSET_G = 1u; void SetupColorTables(const uint8_t* in, uint8_t (&colorTableRed)[8], uint8_t (&colorTableGreen)[8]) { @@ -116,7 +68,8 @@ namespace #undef INTERPOLATE_BC5 } - void DecompressBlock(const uint8_t* in, uint8_t* out, const unsigned outPitch) + void DecompressBlock( + const uint8_t* in, uint8_t* out, const unsigned outPitch, const unsigned outPixelSize, const unsigned outOffsetR, const unsigned outOffsetG) { uint8_t colorTableRed[8]; uint8_t colorTableGreen[8]; @@ -128,56 +81,63 @@ namespace const uint32_t dataGreen1 = in[BC5_CHANNEL_SIZE + 5] | static_cast(in[BC5_CHANNEL_SIZE + 6] << 8) | static_cast(in[BC5_CHANNEL_SIZE + 7] << 16); - out[0 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 21) >> 21]; - out[0 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 21) >> 21]; + out[0 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 21) >> 21]; + out[0 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 21) >> 21]; - out[0 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 18) >> 18]; - out[0 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 18) >> 18]; + out[0 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 18) >> 18]; + out[0 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 18) >> 18]; - out[0 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 15) >> 15]; - out[0 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 15) >> 15]; + out[0 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 15) >> 15]; + out[0 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 15) >> 15]; - out[0 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 12) >> 12]; - out[0 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 12) >> 12]; + out[0 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 12) >> 12]; + out[0 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 12) >> 12]; - out[1 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 9) >> 9]; - out[1 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 9) >> 9]; + out[1 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 9) >> 9]; + out[1 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 9) >> 9]; - out[1 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 6) >> 6]; - out[1 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 6) >> 6]; + out[1 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 6) >> 6]; + out[1 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 6) >> 6]; - out[1 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 3) >> 3]; - out[1 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 3) >> 3]; + out[1 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 3) >> 3]; + out[1 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 3) >> 3]; - out[1 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed0 & (0x7 << 0) >> 0]; - out[1 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen0 & (0x7 << 0) >> 0]; + out[1 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 0) >> 0]; + out[1 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 0) >> 0]; - out[2 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 21) >> 21]; - out[2 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 21) >> 21]; + out[2 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 21) >> 21]; + out[2 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 21) >> 21]; - out[2 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 18) >> 18]; - out[2 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 18) >> 18]; + out[2 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 18) >> 18]; + out[2 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 18) >> 18]; - out[2 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 15) >> 15]; - out[2 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 15) >> 15]; + out[2 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 15) >> 15]; + out[2 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 15) >> 15]; - out[2 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 12) >> 12]; - out[2 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 12) >> 12]; + out[2 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 12) >> 12]; + out[2 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 12) >> 12]; - out[3 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 9) >> 9]; - out[3 * outPitch + 0 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 9) >> 9]; + out[3 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 9) >> 9]; + out[3 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 9) >> 9]; - out[3 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 6) >> 6]; - out[3 * outPitch + 1 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 6) >> 6]; + out[3 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 6) >> 6]; + out[3 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 6) >> 6]; - out[3 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 3) >> 3]; - out[3 * outPitch + 2 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 3) >> 3]; + out[3 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 3) >> 3]; + out[3 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 3) >> 3]; - out[3 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_R] = colorTableRed[dataRed1 & (0x7 << 0) >> 0]; - out[3 * outPitch + 3 * OUT_PIXEL_SIZE + OUT_OFFSET_G] = colorTableGreen[dataGreen1 & (0x7 << 0) >> 0]; + out[3 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 0) >> 0]; + out[3 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 0) >> 0]; } - void DecompressBlockEdge(const uint8_t* in, uint8_t* out, const unsigned outPitch, const unsigned width, const unsigned height) + void DecompressBlockEdge(const uint8_t* in, + uint8_t* out, + const unsigned width, + const unsigned height, + const unsigned outPitch, + const unsigned outPixelSize, + const unsigned outOffsetR, + const unsigned outOffsetG) { assert(width <= BC5_BLOCK_PIXELS); assert(height <= BC5_BLOCK_PIXELS); @@ -237,8 +197,8 @@ namespace { for (auto curWidth = 0u; curWidth < width; curWidth++) { - out[curHeight * outPitch + curWidth * OUT_PIXEL_SIZE + OUT_OFFSET_R] = pixelsRed[curHeight * BC5_BLOCK_PIXELS + curWidth]; - out[curHeight * outPitch + curWidth * OUT_PIXEL_SIZE + OUT_OFFSET_G] = pixelsGreen[curHeight * BC5_BLOCK_PIXELS + curWidth]; + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetR] = pixelsRed[curHeight * BC5_BLOCK_PIXELS + curWidth]; + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetG] = pixelsGreen[curHeight * BC5_BLOCK_PIXELS + curWidth]; } } } @@ -246,13 +206,32 @@ namespace namespace image { - std::unique_ptr DecompressorBc5::Decompress(const Texture& input) + std::unique_ptr DecompressorBc5::Decompress(const Texture& input, const ImageFormat* targetFormat) { + assert(targetFormat->GetType() == ImageFormatType::UNSIGNED); + if (targetFormat->GetType() != ImageFormatType::UNSIGNED) + return nullptr; + + const auto* unsignedTargetFormat = dynamic_cast(targetFormat); + + const auto outPixelSize = unsignedTargetFormat->m_bits_per_pixel / 8u; + + // Only support formats with byte aligned channels + const auto redByteAligned = unsignedTargetFormat->m_r_size == 8 && (unsignedTargetFormat->m_r_offset % 8) == 0; + const auto greenByteAligned = unsignedTargetFormat->m_g_size == 8 && (unsignedTargetFormat->m_g_offset % 8) == 0; + assert(redByteAligned); + assert(greenByteAligned); + if (!redByteAligned || !greenByteAligned) + return nullptr; + + const auto outOffsetR = unsignedTargetFormat->m_r_offset / 8; + const auto outOffsetG = unsignedTargetFormat->m_g_offset / 8; + const auto width = input.GetWidth(); const auto height = input.GetHeight(); const auto depth = input.GetDepth(); - auto result = Texture::CreateForType(input.GetTextureType(), &format::R8_G8_B8, width, height, depth, input.HasMipMaps()); + auto result = Texture::CreateForType(input.GetTextureType(), targetFormat, width, height, depth, input.HasMipMaps()); result->Allocate(); const auto faceCount = result->GetFaceCount(); @@ -261,10 +240,10 @@ namespace image for (auto mipLevel = 0; mipLevel < mipCount; mipLevel++) { - const unsigned mipWidth = width >> mipLevel; - const unsigned mipHeight = height >> mipLevel; - const unsigned mipDepth = depth >> mipLevel; - const unsigned mipPitch = OUT_PIXEL_SIZE * mipWidth; + const auto mipWidth = std::max(width >> mipLevel, 1u); + const auto mipHeight = std::max(height >> mipLevel, 1u); + const auto mipDepth = std::max(depth >> mipLevel, 1u); + const unsigned mipPitch = outPixelSize * mipWidth; assert(mipPitch == result->GetFormat()->GetPitch(mipLevel, width)); for (auto face = 0; face < faceCount; face++) @@ -281,17 +260,17 @@ namespace image { for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC5_BLOCK_PIXELS) { - DecompressBlock(bufferIn, bufferOut, mipPitch); + DecompressBlock(bufferIn, bufferOut, mipPitch, outPixelSize, outOffsetR, outOffsetG); bufferIn += BC5_BLOCK_SIZE; - bufferOut += OUT_PIXEL_SIZE * BC5_BLOCK_PIXELS; + bufferOut += outPixelSize * BC5_BLOCK_PIXELS; } if (fullBlocksWidth < mipWidth) { const auto edgeBlockWidth = mipWidth - fullBlocksWidth; - DecompressBlockEdge(bufferIn, bufferOut, mipPitch, edgeBlockWidth, BC5_BLOCK_PIXELS); + DecompressBlockEdge(bufferIn, bufferOut, edgeBlockWidth, BC5_BLOCK_PIXELS, mipPitch, outPixelSize, outOffsetR, outOffsetG); bufferIn += BC5_BLOCK_SIZE; - bufferOut += OUT_PIXEL_SIZE * edgeBlockWidth; + bufferOut += outPixelSize * edgeBlockWidth; } } @@ -300,17 +279,17 @@ namespace image const auto edgeBlockHeight = mipHeight - fullBlocksHeight; for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC5_BLOCK_PIXELS) { - DecompressBlockEdge(bufferIn, bufferOut, mipPitch, BC5_BLOCK_PIXELS, edgeBlockHeight); + DecompressBlockEdge(bufferIn, bufferOut, BC5_BLOCK_PIXELS, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetG); bufferIn += BC5_BLOCK_SIZE; - bufferOut += OUT_PIXEL_SIZE * BC5_BLOCK_PIXELS; + bufferOut += outPixelSize * BC5_BLOCK_PIXELS; } if (fullBlocksWidth < mipWidth) { const auto edgeBlockWidth = mipWidth - fullBlocksWidth; - DecompressBlockEdge(bufferIn, bufferOut, mipPitch, edgeBlockWidth, edgeBlockHeight); + DecompressBlockEdge(bufferIn, bufferOut, edgeBlockWidth, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetG); bufferIn += BC5_BLOCK_SIZE; - bufferOut += OUT_PIXEL_SIZE * edgeBlockWidth; + bufferOut += outPixelSize * edgeBlockWidth; } } } diff --git a/src/ObjImage/Image/Compression/DecompressorBc5.h b/src/ObjImage/Image/Compression/DecompressorBc5.h index 71d9ec719..b9b8b5570 100644 --- a/src/ObjImage/Image/Compression/DecompressorBc5.h +++ b/src/ObjImage/Image/Compression/DecompressorBc5.h @@ -7,6 +7,6 @@ namespace image class DecompressorBc5 final : public ImageDecompressor { public: - std::unique_ptr Decompress(const Texture& input) override; + std::unique_ptr Decompress(const Texture& input, const ImageFormat* targetFormat) override; }; } // namespace image diff --git a/src/ObjImage/Image/Compression/ImageDecompressor.h b/src/ObjImage/Image/Compression/ImageDecompressor.h index 35c2cbc05..25c0dd7ea 100644 --- a/src/ObjImage/Image/Compression/ImageDecompressor.h +++ b/src/ObjImage/Image/Compression/ImageDecompressor.h @@ -2,6 +2,7 @@ #include "Image/Texture.h" +#include #include namespace image @@ -16,8 +17,17 @@ namespace image ImageDecompressor& operator=(const ImageDecompressor& other) = default; ImageDecompressor& operator=(ImageDecompressor&& other) noexcept = default; - virtual std::unique_ptr Decompress(const Texture& input) = 0; + virtual std::unique_ptr Decompress(const Texture& input, const ImageFormat* targetFormat) = 0; static ImageDecompressor* GetDecompressorForFormat(ImageFormatId formatId); + + protected: + static constexpr uint64_t Mask1(const unsigned length) + { + if (length >= sizeof(uint64_t) * 8) + return std::numeric_limits::max(); + + return std::numeric_limits::max() >> (sizeof(uint64_t) * 8 - length); + } }; } // namespace image diff --git a/src/ObjImage/Image/TextureConverter.cpp b/src/ObjImage/Image/TextureConverter.cpp index b848be0a7..619338435 100644 --- a/src/ObjImage/Image/TextureConverter.cpp +++ b/src/ObjImage/Image/TextureConverter.cpp @@ -4,12 +4,12 @@ namespace image { - constexpr uint64_t TextureConverter::Mask1(const unsigned length) + constexpr uint64_t Mask1(const unsigned length) { if (length >= sizeof(uint64_t) * 8) - return UINT64_MAX; + return std::numeric_limits::max(); - return UINT64_MAX >> (sizeof(uint64_t) * 8 - length); + return std::numeric_limits::max() >> (sizeof(uint64_t) * 8 - length); } void TextureConverter::SetPixelFunctions(const unsigned inBitCount, const unsigned outBitCount) diff --git a/src/ObjImage/Image/TextureConverter.h b/src/ObjImage/Image/TextureConverter.h index eefc9ae68..bdec57aa9 100644 --- a/src/ObjImage/Image/TextureConverter.h +++ b/src/ObjImage/Image/TextureConverter.h @@ -15,7 +15,6 @@ namespace image std::unique_ptr Convert(); private: - static constexpr uint64_t Mask1(unsigned length); void SetPixelFunctions(unsigned inBitCount, unsigned outBitCount); void CreateOutputTexture(); From 23727be2be30f449375fd2696105d7ed0aea2eab Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Sun, 21 Jun 2026 23:46:52 +0200 Subject: [PATCH 5/6] chore: add decompressors for remaining formats --- .../Image/Compression/DecompressorBc1.cpp | 448 ++++++++++++++++++ .../Image/Compression/DecompressorBc1.h | 12 + .../Image/Compression/DecompressorBc2.cpp | 387 +++++++++++++++ .../Image/Compression/DecompressorBc2.h | 12 + .../Image/Compression/DecompressorBc3.cpp | 410 ++++++++++++++++ .../Image/Compression/DecompressorBc3.h | 12 + .../Image/Compression/DecompressorBc4.cpp | 215 +++++++++ .../Image/Compression/DecompressorBc4.h | 12 + .../Image/Compression/DecompressorBc5.cpp | 185 ++++---- .../Image/Compression/ImageDecompressor.cpp | 25 +- src/ObjImage/Image/TextureConverter.cpp | 26 +- 11 files changed, 1647 insertions(+), 97 deletions(-) create mode 100644 src/ObjImage/Image/Compression/DecompressorBc1.cpp create mode 100644 src/ObjImage/Image/Compression/DecompressorBc1.h create mode 100644 src/ObjImage/Image/Compression/DecompressorBc2.cpp create mode 100644 src/ObjImage/Image/Compression/DecompressorBc2.h create mode 100644 src/ObjImage/Image/Compression/DecompressorBc3.cpp create mode 100644 src/ObjImage/Image/Compression/DecompressorBc3.h create mode 100644 src/ObjImage/Image/Compression/DecompressorBc4.cpp create mode 100644 src/ObjImage/Image/Compression/DecompressorBc4.h diff --git a/src/ObjImage/Image/Compression/DecompressorBc1.cpp b/src/ObjImage/Image/Compression/DecompressorBc1.cpp new file mode 100644 index 000000000..2ddbc6916 --- /dev/null +++ b/src/ObjImage/Image/Compression/DecompressorBc1.cpp @@ -0,0 +1,448 @@ +#include "DecompressorBc1.h" + +#include "Utils/Alignment.h" + +#include +#include + +namespace +{ + constexpr auto BC1_BLOCK_PIXELS = 4u; + constexpr unsigned BC1_BLUE_MASK = 0x1F; + constexpr unsigned BC1_BLUE_SHIFT = 0; + constexpr unsigned BC1_BLUE_MAX = 0x1F; + constexpr unsigned BC1_GREEN_MASK = 0x7E0; + constexpr unsigned BC1_GREEN_SHIFT = 5; + constexpr unsigned BC1_GREEN_MAX = 0x3F; + constexpr unsigned BC1_RED_MASK = 0xF800; + constexpr unsigned BC1_RED_SHIFT = 11; + constexpr unsigned BC1_RED_MAX = 0x1F; + constexpr auto BC1_COLOR_CHANNELS_SIZE = 8u; + constexpr auto BC1_BLOCK_SIZE = BC1_COLOR_CHANNELS_SIZE; + + void SetupColorTables( + const uint8_t* in, uint8_t (&colorTableRed)[4], uint8_t (&colorTableGreen)[4], uint8_t (&colorTableBlue)[4], uint8_t (&colorTableAlpha)[4]) + { +#define EXTRACT_COLOR_CHANNEL_BC1(color, mask, shift, colMax) \ + static_cast(static_cast(((color) & (mask)) >> (shift)) * std::numeric_limits::max() / (colMax)) +#define INTERPOLATE_BC1(colorTable, val0, val1) \ + static_cast( \ + (static_cast(val0) * static_cast((colorTable)[0]) + static_cast(val1) * static_cast((colorTable)[1])) \ + / ((val0) + (val1))) + + const auto color0 = static_cast(in[0] | (in[1] << 8)); + const auto color1 = static_cast(in[2] | (in[3] << 8)); + + colorTableRed[0] = EXTRACT_COLOR_CHANNEL_BC1(color0, BC1_RED_MASK, BC1_RED_SHIFT, BC1_RED_MAX); + colorTableRed[1] = EXTRACT_COLOR_CHANNEL_BC1(color1, BC1_RED_MASK, BC1_RED_SHIFT, BC1_RED_MAX); + + colorTableGreen[0] = EXTRACT_COLOR_CHANNEL_BC1(color0, BC1_GREEN_MASK, BC1_GREEN_SHIFT, BC1_GREEN_MAX); + colorTableGreen[1] = EXTRACT_COLOR_CHANNEL_BC1(color1, BC1_GREEN_MASK, BC1_GREEN_SHIFT, BC1_GREEN_MAX); + + colorTableBlue[0] = EXTRACT_COLOR_CHANNEL_BC1(color0, BC1_BLUE_MASK, BC1_BLUE_SHIFT, BC1_BLUE_MAX); + colorTableBlue[1] = EXTRACT_COLOR_CHANNEL_BC1(color1, BC1_BLUE_MASK, BC1_BLUE_SHIFT, BC1_BLUE_MAX); + + colorTableAlpha[0] = std::numeric_limits::max(); + colorTableAlpha[1] = std::numeric_limits::max(); + colorTableAlpha[2] = std::numeric_limits::max(); + + if (color0 > color1) + { + colorTableRed[2] = INTERPOLATE_BC1(colorTableRed, 2, 1); + colorTableRed[3] = INTERPOLATE_BC1(colorTableRed, 1, 2); + + colorTableGreen[2] = INTERPOLATE_BC1(colorTableGreen, 2, 1); + colorTableGreen[3] = INTERPOLATE_BC1(colorTableGreen, 1, 2); + + colorTableBlue[2] = INTERPOLATE_BC1(colorTableBlue, 2, 1); + colorTableBlue[3] = INTERPOLATE_BC1(colorTableBlue, 1, 2); + + colorTableAlpha[3] = std::numeric_limits::max(); + } + else + { + colorTableRed[2] = INTERPOLATE_BC1(colorTableRed, 1, 1); + colorTableGreen[2] = INTERPOLATE_BC1(colorTableGreen, 1, 1); + colorTableBlue[2] = INTERPOLATE_BC1(colorTableBlue, 1, 1); + + colorTableRed[3] = 0; + colorTableGreen[3] = 0; + colorTableBlue[3] = 0; + colorTableAlpha[3] = 0; + } + +#undef INTERPOLATE_BC1 +#undef EXTRACT_COLOR_CHANNEL_BC1 + } + + void DecompressBlock(const uint8_t* in, + uint8_t* out, + const unsigned outPitch, + const unsigned outPixelSize, + const unsigned outOffsetR, + const unsigned outOffsetG, + const unsigned outOffsetB, + const unsigned outOffsetA, + const bool hasAlpha) + { + uint8_t colorTableRed[4]; + uint8_t colorTableGreen[4]; + uint8_t colorTableBlue[4]; + uint8_t colorTableAlpha[4]; + SetupColorTables(in, colorTableRed, colorTableGreen, colorTableBlue, colorTableAlpha); + + const uint32_t dataColor0 = in[4]; + const uint32_t dataColor1 = in[5]; + const uint32_t dataColor2 = in[6]; + const uint32_t dataColor3 = in[7]; + + out[0 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 0)) >> 0]; + out[0 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 0)) >> 0]; + out[0 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 0)) >> 0]; + + out[0 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 2)) >> 2]; + out[0 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 2)) >> 2]; + out[0 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 2)) >> 2]; + + out[0 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 4)) >> 4]; + out[0 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 4)) >> 4]; + out[0 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 4)) >> 4]; + + out[0 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 6)) >> 6]; + out[0 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 6)) >> 6]; + out[0 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 6)) >> 6]; + + out[1 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 0)) >> 0]; + out[1 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 0)) >> 0]; + out[1 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 0)) >> 0]; + + out[1 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 2)) >> 2]; + out[1 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 2)) >> 2]; + out[1 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 2)) >> 2]; + + out[1 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 4)) >> 4]; + out[1 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 4)) >> 4]; + out[1 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 4)) >> 4]; + + out[1 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 6)) >> 6]; + out[1 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 6)) >> 6]; + out[1 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 6)) >> 6]; + + out[2 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 0)) >> 0]; + out[2 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 0)) >> 0]; + out[2 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 0)) >> 0]; + + out[2 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 2)) >> 2]; + out[2 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 2)) >> 2]; + out[2 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 2)) >> 2]; + + out[2 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 4)) >> 4]; + out[2 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 4)) >> 4]; + out[2 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 4)) >> 4]; + + out[2 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 6)) >> 6]; + out[2 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 6)) >> 6]; + out[2 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 6)) >> 6]; + + out[3 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 0)) >> 0]; + out[3 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 0)) >> 0]; + out[3 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 0)) >> 0]; + + out[3 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 2)) >> 2]; + out[3 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 2)) >> 2]; + out[3 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 2)) >> 2]; + + out[3 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 4)) >> 4]; + out[3 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 4)) >> 4]; + out[3 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 4)) >> 4]; + + out[3 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 6)) >> 6]; + out[3 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 6)) >> 6]; + out[3 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 6)) >> 6]; + + if (hasAlpha) + { + out[0 * outPitch + 0 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor0 & (0x3 << 0)) >> 0]; + out[0 * outPitch + 1 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor0 & (0x3 << 2)) >> 2]; + out[0 * outPitch + 2 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor0 & (0x3 << 4)) >> 4]; + out[0 * outPitch + 3 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor0 & (0x3 << 6)) >> 6]; + out[1 * outPitch + 0 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor1 & (0x3 << 0)) >> 0]; + out[1 * outPitch + 1 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor1 & (0x3 << 2)) >> 2]; + out[1 * outPitch + 2 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor1 & (0x3 << 4)) >> 4]; + out[1 * outPitch + 3 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor1 & (0x3 << 6)) >> 6]; + out[2 * outPitch + 0 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor2 & (0x3 << 0)) >> 0]; + out[2 * outPitch + 1 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor2 & (0x3 << 2)) >> 2]; + out[2 * outPitch + 2 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor2 & (0x3 << 4)) >> 4]; + out[2 * outPitch + 3 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor2 & (0x3 << 6)) >> 6]; + out[3 * outPitch + 0 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor3 & (0x3 << 0)) >> 0]; + out[3 * outPitch + 1 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor3 & (0x3 << 2)) >> 2]; + out[3 * outPitch + 2 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor3 & (0x3 << 4)) >> 4]; + out[3 * outPitch + 3 * outPixelSize + outOffsetA] = colorTableAlpha[(dataColor3 & (0x3 << 6)) >> 6]; + } + } + + void DecompressBlockEdge(const uint8_t* in, + uint8_t* out, + const unsigned width, + const unsigned height, + const unsigned outPitch, + const unsigned outPixelSize, + const unsigned outOffsetR, + const unsigned outOffsetG, + const unsigned outOffsetB, + const unsigned outOffsetA, + const bool hasAlpha) + { + assert(width <= BC1_BLOCK_PIXELS); + assert(height <= BC1_BLOCK_PIXELS); + + uint8_t colorTableRed[4]; + uint8_t colorTableGreen[4]; + uint8_t colorTableBlue[4]; + uint8_t colorTableAlpha[4]; + SetupColorTables(in, colorTableRed, colorTableGreen, colorTableBlue, colorTableAlpha); + + const uint8_t dataColor0 = in[4]; + const uint8_t dataColor1 = in[5]; + const uint8_t dataColor2 = in[6]; + const uint8_t dataColor3 = in[7]; + + const uint8_t pixelsRed[]{ + colorTableRed[(dataColor0 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor0 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor0 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor0 & (0x3 << 6)) >> 6], + colorTableRed[(dataColor1 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor1 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor1 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor1 & (0x3 << 6)) >> 6], + colorTableRed[(dataColor2 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor2 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor2 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor2 & (0x3 << 6)) >> 6], + colorTableRed[(dataColor3 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor3 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor3 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor3 & (0x3 << 6)) >> 6], + }; + static_assert(std::extent_v == BC1_BLOCK_PIXELS * BC1_BLOCK_PIXELS); + + const uint8_t pixelsGreen[]{ + colorTableGreen[(dataColor0 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor0 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor0 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor0 & (0x3 << 6)) >> 6], + colorTableGreen[(dataColor1 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor1 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor1 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor1 & (0x3 << 6)) >> 6], + colorTableGreen[(dataColor2 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor2 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor2 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor2 & (0x3 << 6)) >> 6], + colorTableGreen[(dataColor3 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor3 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor3 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor3 & (0x3 << 6)) >> 6], + }; + static_assert(std::extent_v == BC1_BLOCK_PIXELS * BC1_BLOCK_PIXELS); + + const uint8_t pixelsBlue[]{ + colorTableBlue[(dataColor0 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor0 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor0 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor0 & (0x3 << 6)) >> 6], + colorTableBlue[(dataColor1 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor1 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor1 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor1 & (0x3 << 6)) >> 6], + colorTableBlue[(dataColor2 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor2 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor2 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor2 & (0x3 << 6)) >> 6], + colorTableBlue[(dataColor3 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor3 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor3 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor3 & (0x3 << 6)) >> 6], + }; + static_assert(std::extent_v == BC1_BLOCK_PIXELS * BC1_BLOCK_PIXELS); + + for (auto curHeight = 0u; curHeight < height; curHeight++) + { + for (auto curWidth = 0u; curWidth < width; curWidth++) + { + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetR] = pixelsRed[curHeight * BC1_BLOCK_PIXELS + curWidth]; + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetG] = pixelsGreen[curHeight * BC1_BLOCK_PIXELS + curWidth]; + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetB] = pixelsBlue[curHeight * BC1_BLOCK_PIXELS + curWidth]; + } + } + + if (hasAlpha) + { + const uint8_t pixelsAlpha[]{ + colorTableAlpha[(dataColor0 & (0x3 << 0)) >> 0], + colorTableAlpha[(dataColor0 & (0x3 << 2)) >> 2], + colorTableAlpha[(dataColor0 & (0x3 << 4)) >> 4], + colorTableAlpha[(dataColor0 & (0x3 << 6)) >> 6], + colorTableAlpha[(dataColor1 & (0x3 << 0)) >> 0], + colorTableAlpha[(dataColor1 & (0x3 << 2)) >> 2], + colorTableAlpha[(dataColor1 & (0x3 << 4)) >> 4], + colorTableAlpha[(dataColor1 & (0x3 << 6)) >> 6], + colorTableAlpha[(dataColor2 & (0x3 << 0)) >> 0], + colorTableAlpha[(dataColor2 & (0x3 << 2)) >> 2], + colorTableAlpha[(dataColor2 & (0x3 << 4)) >> 4], + colorTableAlpha[(dataColor2 & (0x3 << 6)) >> 6], + colorTableAlpha[(dataColor3 & (0x3 << 0)) >> 0], + colorTableAlpha[(dataColor3 & (0x3 << 2)) >> 2], + colorTableAlpha[(dataColor3 & (0x3 << 4)) >> 4], + colorTableAlpha[(dataColor3 & (0x3 << 6)) >> 6], + }; + static_assert(std::extent_v == BC1_BLOCK_PIXELS * BC1_BLOCK_PIXELS); + + for (auto curHeight = 0u; curHeight < height; curHeight++) + { + for (auto curWidth = 0u; curWidth < width; curWidth++) + { + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetA] = pixelsAlpha[curHeight * BC1_BLOCK_PIXELS + curWidth]; + } + } + } + } +} // namespace + +namespace image +{ + std::unique_ptr DecompressorBc1::Decompress(const Texture& input, const ImageFormat* targetFormat) + { + assert(targetFormat->GetType() == ImageFormatType::UNSIGNED); + if (targetFormat->GetType() != ImageFormatType::UNSIGNED) + return nullptr; + + const auto* unsignedTargetFormat = dynamic_cast(targetFormat); + + const auto outPixelSize = unsignedTargetFormat->m_bits_per_pixel / 8u; + + // Only support formats with byte aligned channels + const auto redByteAligned = unsignedTargetFormat->m_r_size == 8 && (unsignedTargetFormat->m_r_offset % 8) == 0; + const auto greenByteAligned = unsignedTargetFormat->m_g_size == 8 && (unsignedTargetFormat->m_g_offset % 8) == 0; + const auto blueByteAligned = unsignedTargetFormat->m_b_size == 8 && (unsignedTargetFormat->m_b_offset % 8) == 0; + const auto hasAlpha = unsignedTargetFormat->m_a_size > 0; + const auto alphaByteAligned = unsignedTargetFormat->m_a_size == 8 && (unsignedTargetFormat->m_a_offset % 8) == 0; + assert(redByteAligned); + assert(greenByteAligned); + assert(blueByteAligned); + assert(!hasAlpha || alphaByteAligned); + if (!redByteAligned || !greenByteAligned || !blueByteAligned || (hasAlpha && !alphaByteAligned)) + return nullptr; + + const auto outOffsetR = unsignedTargetFormat->m_r_offset / 8; + const auto outOffsetG = unsignedTargetFormat->m_g_offset / 8; + const auto outOffsetB = unsignedTargetFormat->m_b_offset / 8; + const auto outOffsetA = unsignedTargetFormat->m_a_offset / 8; + + const auto width = input.GetWidth(); + const auto height = input.GetHeight(); + const auto depth = input.GetDepth(); + + auto result = Texture::CreateForType(input.GetTextureType(), targetFormat, width, height, depth, input.HasMipMaps()); + result->Allocate(); + + const auto faceCount = result->GetFaceCount(); + const auto mipCount = result->HasMipMaps() ? result->GetMipMapCount() : 1; + assert(mipCount == input.HasMipMaps() ? input.GetMipMapCount() : 1); + + for (auto mipLevel = 0; mipLevel < mipCount; mipLevel++) + { + const auto mipWidth = std::max(width >> mipLevel, 1u); + const auto mipHeight = std::max(height >> mipLevel, 1u); + const auto mipDepth = std::max(depth >> mipLevel, 1u); + const unsigned mipPitch = outPixelSize * mipWidth; + assert(mipPitch == result->GetFormat()->GetPitch(mipLevel, width)); + + for (auto face = 0; face < faceCount; face++) + { + const auto* bufferIn = input.GetBufferForMipLevel(mipLevel, face); + auto* bufferOut = result->GetBufferForMipLevel(mipLevel, face); + + for (auto curDepth = 0u; curDepth < mipDepth; curDepth++) + { + const auto fullBlocksWidth = utils::AlignToPrevious(mipWidth, BC1_BLOCK_PIXELS); + const auto fullBlocksHeight = utils::AlignToPrevious(mipHeight, BC1_BLOCK_PIXELS); + + for (auto curHeight = 0u; curHeight < fullBlocksHeight; curHeight += BC1_BLOCK_PIXELS) + { + for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC1_BLOCK_PIXELS) + { + DecompressBlock(bufferIn, bufferOut, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetB, outOffsetA, hasAlpha); + bufferIn += BC1_BLOCK_SIZE; + bufferOut += outPixelSize * BC1_BLOCK_PIXELS; + } + + if (fullBlocksWidth < mipWidth) + { + const auto edgeBlockWidth = mipWidth - fullBlocksWidth; + DecompressBlockEdge(bufferIn, + bufferOut, + edgeBlockWidth, + BC1_BLOCK_PIXELS, + mipPitch, + outPixelSize, + outOffsetR, + outOffsetG, + outOffsetB, + outOffsetA, + hasAlpha); + bufferIn += BC1_BLOCK_SIZE; + bufferOut += outPixelSize * edgeBlockWidth; + } + + bufferOut += mipPitch * (BC1_BLOCK_PIXELS - 1); + } + + if (fullBlocksHeight < mipHeight) + { + const auto edgeBlockHeight = mipHeight - fullBlocksHeight; + for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC1_BLOCK_PIXELS) + { + DecompressBlockEdge(bufferIn, + bufferOut, + BC1_BLOCK_PIXELS, + edgeBlockHeight, + mipPitch, + outPixelSize, + outOffsetR, + outOffsetG, + outOffsetB, + outOffsetA, + hasAlpha); + bufferIn += BC1_BLOCK_SIZE; + bufferOut += outPixelSize * BC1_BLOCK_PIXELS; + } + + if (fullBlocksWidth < mipWidth) + { + const auto edgeBlockWidth = mipWidth - fullBlocksWidth; + DecompressBlockEdge(bufferIn, + bufferOut, + edgeBlockWidth, + edgeBlockHeight, + mipPitch, + outPixelSize, + outOffsetR, + outOffsetG, + outOffsetB, + outOffsetA, + hasAlpha); + bufferIn += BC1_BLOCK_SIZE; + bufferOut += outPixelSize * edgeBlockWidth; + } + + bufferOut += mipPitch * (BC1_BLOCK_PIXELS - 1); + } + } + } + } + + return result; + } +} // namespace image diff --git a/src/ObjImage/Image/Compression/DecompressorBc1.h b/src/ObjImage/Image/Compression/DecompressorBc1.h new file mode 100644 index 000000000..c4c0831d9 --- /dev/null +++ b/src/ObjImage/Image/Compression/DecompressorBc1.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ImageDecompressor.h" + +namespace image +{ + class DecompressorBc1 final : public ImageDecompressor + { + public: + std::unique_ptr Decompress(const Texture& input, const ImageFormat* targetFormat) override; + }; +} // namespace image diff --git a/src/ObjImage/Image/Compression/DecompressorBc2.cpp b/src/ObjImage/Image/Compression/DecompressorBc2.cpp new file mode 100644 index 000000000..d9157cdfe --- /dev/null +++ b/src/ObjImage/Image/Compression/DecompressorBc2.cpp @@ -0,0 +1,387 @@ +#include "DecompressorBc2.h" + +#include "Utils/Alignment.h" + +#include +#include + +namespace +{ + constexpr auto BC2_BLOCK_PIXELS = 4u; + constexpr unsigned BC2_BLUE_MASK = 0x1F; + constexpr unsigned BC2_BLUE_SHIFT = 0; + constexpr unsigned BC2_BLUE_MAX = 0x1F; + constexpr unsigned BC2_GREEN_MASK = 0x7E0; + constexpr unsigned BC2_GREEN_SHIFT = 5; + constexpr unsigned BC2_GREEN_MAX = 0x3F; + constexpr unsigned BC2_RED_MASK = 0xF800; + constexpr unsigned BC2_RED_SHIFT = 11; + constexpr unsigned BC2_RED_MAX = 0x1F; + constexpr auto BC2_COLOR_CHANNELS_SIZE = 8u; + constexpr auto BC2_ALPHA_CHANNEL_SIZE = 8u; + constexpr auto BC2_BLOCK_SIZE = BC2_COLOR_CHANNELS_SIZE + BC2_ALPHA_CHANNEL_SIZE; + + void SetupColorTables(const uint8_t* in, uint8_t (&colorTableRed)[4], uint8_t (&colorTableGreen)[4], uint8_t (&colorTableBlue)[4]) + { +#define EXTRACT_COLOR_CHANNEL_BC2(color, mask, shift, colMax) \ + static_cast(static_cast(((color) & (mask)) >> (shift)) * std::numeric_limits::max() / (colMax)) +#define INTERPOLATE_BC2(colorTable, val0, val1) \ + static_cast( \ + (static_cast(val0) * static_cast((colorTable)[0]) + static_cast(val1) * static_cast((colorTable)[1])) \ + / ((val0) + (val1))) + + const auto color0 = static_cast(in[0 + BC2_ALPHA_CHANNEL_SIZE] | (in[1 + BC2_ALPHA_CHANNEL_SIZE] << 8)); + const auto color1 = static_cast(in[2 + BC2_ALPHA_CHANNEL_SIZE] | (in[3 + BC2_ALPHA_CHANNEL_SIZE] << 8)); + + colorTableRed[0] = EXTRACT_COLOR_CHANNEL_BC2(color0, BC2_RED_MASK, BC2_RED_SHIFT, BC2_RED_MAX); + colorTableRed[1] = EXTRACT_COLOR_CHANNEL_BC2(color1, BC2_RED_MASK, BC2_RED_SHIFT, BC2_RED_MAX); + colorTableRed[2] = INTERPOLATE_BC2(colorTableRed, 2, 1); + colorTableRed[3] = INTERPOLATE_BC2(colorTableRed, 1, 2); + + colorTableGreen[0] = EXTRACT_COLOR_CHANNEL_BC2(color0, BC2_GREEN_MASK, BC2_GREEN_SHIFT, BC2_GREEN_MAX); + colorTableGreen[1] = EXTRACT_COLOR_CHANNEL_BC2(color1, BC2_GREEN_MASK, BC2_GREEN_SHIFT, BC2_GREEN_MAX); + colorTableGreen[2] = INTERPOLATE_BC2(colorTableGreen, 2, 1); + colorTableGreen[3] = INTERPOLATE_BC2(colorTableGreen, 1, 2); + + colorTableBlue[0] = EXTRACT_COLOR_CHANNEL_BC2(color0, BC2_BLUE_MASK, BC2_BLUE_SHIFT, BC2_BLUE_MAX); + colorTableBlue[1] = EXTRACT_COLOR_CHANNEL_BC2(color1, BC2_BLUE_MASK, BC2_BLUE_SHIFT, BC2_BLUE_MAX); + colorTableBlue[2] = INTERPOLATE_BC2(colorTableBlue, 2, 1); + colorTableBlue[3] = INTERPOLATE_BC2(colorTableBlue, 1, 2); + +#undef INTERPOLATE_BC2 +#undef EXTRACT_COLOR_CHANNEL_BC2 + } + + void DecompressBlock(const uint8_t* in, + uint8_t* out, + const unsigned outPitch, + const unsigned outPixelSize, + const unsigned outOffsetR, + const unsigned outOffsetG, + const unsigned outOffsetB, + const unsigned outOffsetA) + { + uint8_t colorTableRed[4]; + uint8_t colorTableGreen[4]; + uint8_t colorTableBlue[4]; + SetupColorTables(in, colorTableRed, colorTableGreen, colorTableBlue); + + const uint16_t dataAlpha0 = static_cast(in[0] | static_cast(in[1] << 8u)); + const uint16_t dataAlpha1 = static_cast(in[2] | static_cast(in[3] << 8u)); + const uint16_t dataAlpha2 = static_cast(in[4] | static_cast(in[5] << 8u)); + const uint16_t dataAlpha3 = static_cast(in[6] | static_cast(in[7] << 8u)); + const uint32_t dataColor0 = in[BC2_ALPHA_CHANNEL_SIZE + 4]; + const uint32_t dataColor1 = in[BC2_ALPHA_CHANNEL_SIZE + 5]; + const uint32_t dataColor2 = in[BC2_ALPHA_CHANNEL_SIZE + 6]; + const uint32_t dataColor3 = in[BC2_ALPHA_CHANNEL_SIZE + 7]; + + out[0 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 0)) >> 0]; + out[0 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 0)) >> 0]; + out[0 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 0)) >> 0]; + out[0 * outPitch + 0 * outPixelSize + outOffsetA] = ((dataAlpha0 & 0xF) >> 0) * std::numeric_limits::max() / 0xF; + + out[0 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 2)) >> 2]; + out[0 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 2)) >> 2]; + out[0 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 2)) >> 2]; + out[0 * outPitch + 1 * outPixelSize + outOffsetA] = ((dataAlpha0 & 0xF0) >> 4) * std::numeric_limits::max() / 0xF; + + out[0 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 4)) >> 4]; + out[0 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 4)) >> 4]; + out[0 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 4)) >> 4]; + out[0 * outPitch + 2 * outPixelSize + outOffsetA] = ((dataAlpha0 & 0xF00) >> 8) * std::numeric_limits::max() / 0xF; + + out[0 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 6)) >> 6]; + out[0 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 6)) >> 6]; + out[0 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 6)) >> 6]; + out[0 * outPitch + 3 * outPixelSize + outOffsetA] = ((dataAlpha0 & 0xF000) >> 12) * std::numeric_limits::max() / 0xF; + + out[1 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 0)) >> 0]; + out[1 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 0)) >> 0]; + out[1 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 0)) >> 0]; + out[1 * outPitch + 0 * outPixelSize + outOffsetA] = ((dataAlpha1 & 0xF) >> 0) * std::numeric_limits::max() / 0xF; + + out[1 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 2)) >> 2]; + out[1 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 2)) >> 2]; + out[1 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 2)) >> 2]; + out[1 * outPitch + 1 * outPixelSize + outOffsetA] = ((dataAlpha1 & 0xF0) >> 4) * std::numeric_limits::max() / 0xF; + + out[1 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 4)) >> 4]; + out[1 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 4)) >> 4]; + out[1 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 4)) >> 4]; + out[1 * outPitch + 2 * outPixelSize + outOffsetA] = ((dataAlpha1 & 0xF00) >> 8) * std::numeric_limits::max() / 0xF; + + out[1 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 6)) >> 6]; + out[1 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 6)) >> 6]; + out[1 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 6)) >> 6]; + out[1 * outPitch + 3 * outPixelSize + outOffsetA] = ((dataAlpha1 & 0xF000) >> 12) * std::numeric_limits::max() / 0xF; + + out[2 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 0)) >> 0]; + out[2 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 0)) >> 0]; + out[2 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 0)) >> 0]; + out[2 * outPitch + 0 * outPixelSize + outOffsetA] = ((dataAlpha2 & 0xF) >> 0) * std::numeric_limits::max() / 0xF; + + out[2 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 2)) >> 2]; + out[2 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 2)) >> 2]; + out[2 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 2)) >> 2]; + out[2 * outPitch + 1 * outPixelSize + outOffsetA] = ((dataAlpha2 & 0xF0) >> 4) * std::numeric_limits::max() / 0xF; + + out[2 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 4)) >> 4]; + out[2 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 4)) >> 4]; + out[2 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 4)) >> 4]; + out[2 * outPitch + 2 * outPixelSize + outOffsetA] = ((dataAlpha2 & 0xF00) >> 8) * std::numeric_limits::max() / 0xF; + + out[2 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 6)) >> 6]; + out[2 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 6)) >> 6]; + out[2 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 6)) >> 6]; + out[2 * outPitch + 3 * outPixelSize + outOffsetA] = ((dataAlpha2 & 0xF000) >> 12) * std::numeric_limits::max() / 0xF; + + out[3 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 0)) >> 0]; + out[3 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 0)) >> 0]; + out[3 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 0)) >> 0]; + out[3 * outPitch + 0 * outPixelSize + outOffsetA] = ((dataAlpha3 & 0xF) >> 0) * std::numeric_limits::max() / 0xF; + + out[3 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 2)) >> 2]; + out[3 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 2)) >> 2]; + out[3 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 2)) >> 2]; + out[3 * outPitch + 1 * outPixelSize + outOffsetA] = ((dataAlpha3 & 0xF0) >> 4) * std::numeric_limits::max() / 0xF; + + out[3 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 4)) >> 4]; + out[3 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 4)) >> 4]; + out[3 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 4)) >> 4]; + out[3 * outPitch + 2 * outPixelSize + outOffsetA] = ((dataAlpha3 & 0xF00) >> 8) * std::numeric_limits::max() / 0xF; + + out[3 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 6)) >> 6]; + out[3 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 6)) >> 6]; + out[3 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 6)) >> 6]; + out[3 * outPitch + 3 * outPixelSize + outOffsetA] = ((dataAlpha3 & 0xF000) >> 12) * std::numeric_limits::max() / 0xF; + } + + void DecompressBlockEdge(const uint8_t* in, + uint8_t* out, + const unsigned width, + const unsigned height, + const unsigned outPitch, + const unsigned outPixelSize, + const unsigned outOffsetR, + const unsigned outOffsetG, + const unsigned outOffsetB, + const unsigned outOffsetA) + { + assert(width <= BC2_BLOCK_PIXELS); + assert(height <= BC2_BLOCK_PIXELS); + + uint8_t colorTableRed[4]; + uint8_t colorTableGreen[4]; + uint8_t colorTableBlue[4]; + SetupColorTables(in, colorTableRed, colorTableGreen, colorTableBlue); + + const uint16_t dataAlpha0 = static_cast(in[0] | static_cast(in[1] << 8u)); + const uint16_t dataAlpha1 = static_cast(in[2] | static_cast(in[3] << 8u)); + const uint16_t dataAlpha2 = static_cast(in[4] | static_cast(in[5] << 8u)); + const uint16_t dataAlpha3 = static_cast(in[6] | static_cast(in[7] << 8u)); + const uint8_t dataColor0 = in[BC2_ALPHA_CHANNEL_SIZE + 4]; + const uint8_t dataColor1 = in[BC2_ALPHA_CHANNEL_SIZE + 5]; + const uint8_t dataColor2 = in[BC2_ALPHA_CHANNEL_SIZE + 6]; + const uint8_t dataColor3 = in[BC2_ALPHA_CHANNEL_SIZE + 7]; + + const uint8_t pixelsRed[]{ + colorTableRed[(dataColor0 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor0 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor0 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor0 & (0x3 << 6)) >> 6], + colorTableRed[(dataColor1 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor1 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor1 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor1 & (0x3 << 6)) >> 6], + colorTableRed[(dataColor2 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor2 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor2 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor2 & (0x3 << 6)) >> 6], + colorTableRed[(dataColor3 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor3 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor3 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor3 & (0x3 << 6)) >> 6], + }; + static_assert(std::extent_v == BC2_BLOCK_PIXELS * BC2_BLOCK_PIXELS); + + const uint8_t pixelsGreen[]{ + colorTableGreen[(dataColor0 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor0 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor0 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor0 & (0x3 << 6)) >> 6], + colorTableGreen[(dataColor1 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor1 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor1 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor1 & (0x3 << 6)) >> 6], + colorTableGreen[(dataColor2 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor2 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor2 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor2 & (0x3 << 6)) >> 6], + colorTableGreen[(dataColor3 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor3 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor3 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor3 & (0x3 << 6)) >> 6], + }; + static_assert(std::extent_v == BC2_BLOCK_PIXELS * BC2_BLOCK_PIXELS); + + const uint8_t pixelsBlue[]{ + colorTableBlue[(dataColor0 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor0 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor0 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor0 & (0x3 << 6)) >> 6], + colorTableBlue[(dataColor1 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor1 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor1 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor1 & (0x3 << 6)) >> 6], + colorTableBlue[(dataColor2 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor2 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor2 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor2 & (0x3 << 6)) >> 6], + colorTableBlue[(dataColor3 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor3 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor3 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor3 & (0x3 << 6)) >> 6], + }; + static_assert(std::extent_v == BC2_BLOCK_PIXELS * BC2_BLOCK_PIXELS); + + const uint8_t pixelsAlpha[]{ + static_cast(((dataAlpha0 & 0xF) >> 0) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha0 & 0xF0) >> 4) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha0 & 0xF00) >> 8) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha0 & 0xF000) >> 12) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha1 & 0xF) >> 0) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha1 & 0xF0) >> 4) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha1 & 0xF00) >> 8) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha1 & 0xF000) >> 12) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha2 & 0xF) >> 0) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha2 & 0xF0) >> 4) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha2 & 0xF00) >> 8) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha2 & 0xF000) >> 12) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha3 & 0xF) >> 0) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha3 & 0xF0) >> 4) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha3 & 0xF00) >> 8) * std::numeric_limits::max() / 0xF), + static_cast(((dataAlpha3 & 0xF000) >> 12) * std::numeric_limits::max() / 0xF), + }; + static_assert(std::extent_v == BC2_BLOCK_PIXELS * BC2_BLOCK_PIXELS); + + for (auto curHeight = 0u; curHeight < height; curHeight++) + { + for (auto curWidth = 0u; curWidth < width; curWidth++) + { + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetR] = pixelsRed[curHeight * BC2_BLOCK_PIXELS + curWidth]; + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetG] = pixelsGreen[curHeight * BC2_BLOCK_PIXELS + curWidth]; + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetB] = pixelsBlue[curHeight * BC2_BLOCK_PIXELS + curWidth]; + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetA] = pixelsAlpha[curHeight * BC2_BLOCK_PIXELS + curWidth]; + } + } + } +} // namespace + +namespace image +{ + std::unique_ptr DecompressorBc2::Decompress(const Texture& input, const ImageFormat* targetFormat) + { + assert(targetFormat->GetType() == ImageFormatType::UNSIGNED); + if (targetFormat->GetType() != ImageFormatType::UNSIGNED) + return nullptr; + + const auto* unsignedTargetFormat = dynamic_cast(targetFormat); + + const auto outPixelSize = unsignedTargetFormat->m_bits_per_pixel / 8u; + + // Only support formats with byte aligned channels + const auto redByteAligned = unsignedTargetFormat->m_r_size == 8 && (unsignedTargetFormat->m_r_offset % 8) == 0; + const auto greenByteAligned = unsignedTargetFormat->m_g_size == 8 && (unsignedTargetFormat->m_g_offset % 8) == 0; + const auto blueByteAligned = unsignedTargetFormat->m_b_size == 8 && (unsignedTargetFormat->m_b_offset % 8) == 0; + const auto alphaByteAligned = unsignedTargetFormat->m_a_size == 8 && (unsignedTargetFormat->m_a_offset % 8) == 0; + assert(redByteAligned); + assert(greenByteAligned); + assert(blueByteAligned); + assert(alphaByteAligned); + if (!redByteAligned || !greenByteAligned || !blueByteAligned || !alphaByteAligned) + return nullptr; + + const auto outOffsetR = unsignedTargetFormat->m_r_offset / 8; + const auto outOffsetG = unsignedTargetFormat->m_g_offset / 8; + const auto outOffsetB = unsignedTargetFormat->m_b_offset / 8; + const auto outOffsetA = unsignedTargetFormat->m_a_offset / 8; + + const auto width = input.GetWidth(); + const auto height = input.GetHeight(); + const auto depth = input.GetDepth(); + + auto result = Texture::CreateForType(input.GetTextureType(), targetFormat, width, height, depth, input.HasMipMaps()); + result->Allocate(); + + const auto faceCount = result->GetFaceCount(); + const auto mipCount = result->HasMipMaps() ? result->GetMipMapCount() : 1; + assert(mipCount == input.HasMipMaps() ? input.GetMipMapCount() : 1); + + for (auto mipLevel = 0; mipLevel < mipCount; mipLevel++) + { + const auto mipWidth = std::max(width >> mipLevel, 1u); + const auto mipHeight = std::max(height >> mipLevel, 1u); + const auto mipDepth = std::max(depth >> mipLevel, 1u); + const unsigned mipPitch = outPixelSize * mipWidth; + assert(mipPitch == result->GetFormat()->GetPitch(mipLevel, width)); + + for (auto face = 0; face < faceCount; face++) + { + const auto* bufferIn = input.GetBufferForMipLevel(mipLevel, face); + auto* bufferOut = result->GetBufferForMipLevel(mipLevel, face); + + for (auto curDepth = 0u; curDepth < mipDepth; curDepth++) + { + const auto fullBlocksWidth = utils::AlignToPrevious(mipWidth, BC2_BLOCK_PIXELS); + const auto fullBlocksHeight = utils::AlignToPrevious(mipHeight, BC2_BLOCK_PIXELS); + + for (auto curHeight = 0u; curHeight < fullBlocksHeight; curHeight += BC2_BLOCK_PIXELS) + { + for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC2_BLOCK_PIXELS) + { + DecompressBlock(bufferIn, bufferOut, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetB, outOffsetA); + bufferIn += BC2_BLOCK_SIZE; + bufferOut += outPixelSize * BC2_BLOCK_PIXELS; + } + + if (fullBlocksWidth < mipWidth) + { + const auto edgeBlockWidth = mipWidth - fullBlocksWidth; + DecompressBlockEdge( + bufferIn, bufferOut, edgeBlockWidth, BC2_BLOCK_PIXELS, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetB, outOffsetA); + bufferIn += BC2_BLOCK_SIZE; + bufferOut += outPixelSize * edgeBlockWidth; + } + + bufferOut += mipPitch * (BC2_BLOCK_PIXELS - 1); + } + + if (fullBlocksHeight < mipHeight) + { + const auto edgeBlockHeight = mipHeight - fullBlocksHeight; + for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC2_BLOCK_PIXELS) + { + DecompressBlockEdge( + bufferIn, bufferOut, BC2_BLOCK_PIXELS, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetB, outOffsetA); + bufferIn += BC2_BLOCK_SIZE; + bufferOut += outPixelSize * BC2_BLOCK_PIXELS; + } + + if (fullBlocksWidth < mipWidth) + { + const auto edgeBlockWidth = mipWidth - fullBlocksWidth; + DecompressBlockEdge( + bufferIn, bufferOut, edgeBlockWidth, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetB, outOffsetA); + bufferIn += BC2_BLOCK_SIZE; + bufferOut += outPixelSize * edgeBlockWidth; + } + + bufferOut += mipPitch * (BC2_BLOCK_PIXELS - 1); + } + } + } + } + + return result; + } +} // namespace image diff --git a/src/ObjImage/Image/Compression/DecompressorBc2.h b/src/ObjImage/Image/Compression/DecompressorBc2.h new file mode 100644 index 000000000..e4b1c74ab --- /dev/null +++ b/src/ObjImage/Image/Compression/DecompressorBc2.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ImageDecompressor.h" + +namespace image +{ + class DecompressorBc2 final : public ImageDecompressor + { + public: + std::unique_ptr Decompress(const Texture& input, const ImageFormat* targetFormat) override; + }; +} // namespace image diff --git a/src/ObjImage/Image/Compression/DecompressorBc3.cpp b/src/ObjImage/Image/Compression/DecompressorBc3.cpp new file mode 100644 index 000000000..24b52bf43 --- /dev/null +++ b/src/ObjImage/Image/Compression/DecompressorBc3.cpp @@ -0,0 +1,410 @@ +#include "DecompressorBc3.h" + +#include "Utils/Alignment.h" + +#include +#include + +namespace +{ + constexpr auto BC3_BLOCK_PIXELS = 4u; + constexpr unsigned BC3_BLUE_MASK = 0x1F; + constexpr unsigned BC3_BLUE_SHIFT = 0; + constexpr unsigned BC3_BLUE_MAX = 0x1F; + constexpr unsigned BC3_GREEN_MASK = 0x7E0; + constexpr unsigned BC3_GREEN_SHIFT = 5; + constexpr unsigned BC3_GREEN_MAX = 0x3F; + constexpr unsigned BC3_RED_MASK = 0xF800; + constexpr unsigned BC3_RED_SHIFT = 11; + constexpr unsigned BC3_RED_MAX = 0x1F; + constexpr auto BC3_COLOR_CHANNELS_SIZE = 8u; + constexpr auto BC3_ALPHA_CHANNEL_SIZE = 8u; + constexpr auto BC3_BLOCK_SIZE = BC3_COLOR_CHANNELS_SIZE + BC3_ALPHA_CHANNEL_SIZE; + + void SetupColorTables( + const uint8_t* in, uint8_t (&colorTableRed)[4], uint8_t (&colorTableGreen)[4], uint8_t (&colorTableBlue)[4], uint8_t (&colorTableAlpha)[8]) + { +#define EXTRACT_COLOR_CHANNEL_BC3(color, mask, shift, colMax) \ + static_cast(static_cast(((color) & (mask)) >> (shift)) * std::numeric_limits::max() / (colMax)) +#define INTERPOLATE_BC3(colorTable, val0, val1) \ + static_cast( \ + (static_cast(val0) * static_cast((colorTable)[0]) + static_cast(val1) * static_cast((colorTable)[1])) \ + / ((val0) + (val1))) + + colorTableAlpha[0] = in[0]; + colorTableAlpha[1] = in[1]; + + if (colorTableAlpha[0] > colorTableAlpha[1]) + { + // 6 interpolated color values + colorTableAlpha[2] = INTERPOLATE_BC3(colorTableAlpha, 6, 1); + colorTableAlpha[3] = INTERPOLATE_BC3(colorTableAlpha, 5, 2); + colorTableAlpha[4] = INTERPOLATE_BC3(colorTableAlpha, 4, 3); + colorTableAlpha[5] = INTERPOLATE_BC3(colorTableAlpha, 3, 4); + colorTableAlpha[6] = INTERPOLATE_BC3(colorTableAlpha, 2, 5); + colorTableAlpha[7] = INTERPOLATE_BC3(colorTableAlpha, 1, 6); + } + else + { + // 4 interpolated color values + colorTableAlpha[2] = INTERPOLATE_BC3(colorTableAlpha, 4, 1); + colorTableAlpha[3] = INTERPOLATE_BC3(colorTableAlpha, 3, 2); + colorTableAlpha[4] = INTERPOLATE_BC3(colorTableAlpha, 2, 3); + colorTableAlpha[5] = INTERPOLATE_BC3(colorTableAlpha, 1, 4); + colorTableAlpha[6] = std::numeric_limits::min(); + colorTableAlpha[7] = std::numeric_limits::max(); + } + + const auto color0 = static_cast(in[0 + BC3_ALPHA_CHANNEL_SIZE] | (in[1 + BC3_ALPHA_CHANNEL_SIZE] << 8)); + const auto color1 = static_cast(in[2 + BC3_ALPHA_CHANNEL_SIZE] | (in[3 + BC3_ALPHA_CHANNEL_SIZE] << 8)); + + colorTableRed[0] = EXTRACT_COLOR_CHANNEL_BC3(color0, BC3_RED_MASK, BC3_RED_SHIFT, BC3_RED_MAX); + colorTableRed[1] = EXTRACT_COLOR_CHANNEL_BC3(color1, BC3_RED_MASK, BC3_RED_SHIFT, BC3_RED_MAX); + colorTableRed[2] = INTERPOLATE_BC3(colorTableRed, 2, 1); + colorTableRed[3] = INTERPOLATE_BC3(colorTableRed, 1, 2); + + colorTableGreen[0] = EXTRACT_COLOR_CHANNEL_BC3(color0, BC3_GREEN_MASK, BC3_GREEN_SHIFT, BC3_GREEN_MAX); + colorTableGreen[1] = EXTRACT_COLOR_CHANNEL_BC3(color1, BC3_GREEN_MASK, BC3_GREEN_SHIFT, BC3_GREEN_MAX); + colorTableGreen[2] = INTERPOLATE_BC3(colorTableGreen, 2, 1); + colorTableGreen[3] = INTERPOLATE_BC3(colorTableGreen, 1, 2); + + colorTableBlue[0] = EXTRACT_COLOR_CHANNEL_BC3(color0, BC3_BLUE_MASK, BC3_BLUE_SHIFT, BC3_BLUE_MAX); + colorTableBlue[1] = EXTRACT_COLOR_CHANNEL_BC3(color1, BC3_BLUE_MASK, BC3_BLUE_SHIFT, BC3_BLUE_MAX); + colorTableBlue[2] = INTERPOLATE_BC3(colorTableBlue, 2, 1); + colorTableBlue[3] = INTERPOLATE_BC3(colorTableBlue, 1, 2); + +#undef INTERPOLATE_BC3 +#undef EXTRACT_COLOR_CHANNEL_BC3 + } + + void DecompressBlock(const uint8_t* in, + uint8_t* out, + const unsigned outPitch, + const unsigned outPixelSize, + const unsigned outOffsetR, + const unsigned outOffsetG, + const unsigned outOffsetB, + const unsigned outOffsetA) + { + uint8_t colorTableRed[4]; + uint8_t colorTableGreen[4]; + uint8_t colorTableBlue[4]; + uint8_t colorTableAlpha[8]; + SetupColorTables(in, colorTableRed, colorTableGreen, colorTableBlue, colorTableAlpha); + + const uint32_t dataAlpha0 = in[2] | static_cast(in[3] << 8u) | static_cast(in[4] << 16u); + const uint32_t dataAlpha1 = in[5] | static_cast(in[6] << 8u) | static_cast(in[7] << 16u); + const uint8_t dataColor0 = in[BC3_ALPHA_CHANNEL_SIZE + 4]; + const uint8_t dataColor1 = in[BC3_ALPHA_CHANNEL_SIZE + 5]; + const uint8_t dataColor2 = in[BC3_ALPHA_CHANNEL_SIZE + 6]; + const uint8_t dataColor3 = in[BC3_ALPHA_CHANNEL_SIZE + 7]; + + out[0 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 0)) >> 0]; + out[0 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 0)) >> 0]; + out[0 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 0)) >> 0]; + out[0 * outPitch + 0 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha0 & (0x7 << 0)) >> 0]; + + out[0 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 2)) >> 2]; + out[0 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 2)) >> 2]; + out[0 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 2)) >> 2]; + out[0 * outPitch + 1 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha0 & (0x7 << 3)) >> 3]; + + out[0 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 4)) >> 4]; + out[0 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 4)) >> 4]; + out[0 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 4)) >> 4]; + out[0 * outPitch + 2 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha0 & (0x7 << 6)) >> 6]; + + out[0 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor0 & (0x3 << 6)) >> 6]; + out[0 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor0 & (0x3 << 6)) >> 6]; + out[0 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor0 & (0x3 << 6)) >> 6]; + out[0 * outPitch + 3 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha0 & (0x7 << 9)) >> 9]; + + out[1 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 0)) >> 0]; + out[1 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 0)) >> 0]; + out[1 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 0)) >> 0]; + out[1 * outPitch + 0 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha0 & (0x7 << 12)) >> 12]; + + out[1 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 2)) >> 2]; + out[1 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 2)) >> 2]; + out[1 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 2)) >> 2]; + out[1 * outPitch + 1 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha0 & (0x7 << 15)) >> 15]; + + out[1 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 4)) >> 4]; + out[1 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 4)) >> 4]; + out[1 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 4)) >> 4]; + out[1 * outPitch + 2 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha0 & (0x7 << 18)) >> 18]; + + out[1 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor1 & (0x3 << 6)) >> 6]; + out[1 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor1 & (0x3 << 6)) >> 6]; + out[1 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor1 & (0x3 << 6)) >> 6]; + out[1 * outPitch + 3 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha0 & (0x7 << 21)) >> 21]; + + out[2 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 0)) >> 0]; + out[2 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 0)) >> 0]; + out[2 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 0)) >> 0]; + out[2 * outPitch + 0 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha1 & (0x7 << 0)) >> 0]; + + out[2 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 2)) >> 2]; + out[2 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 2)) >> 2]; + out[2 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 2)) >> 2]; + out[2 * outPitch + 1 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha1 & (0x7 << 3)) >> 3]; + + out[2 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 4)) >> 4]; + out[2 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 4)) >> 4]; + out[2 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 4)) >> 4]; + out[2 * outPitch + 2 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha1 & (0x7 << 6)) >> 6]; + + out[2 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor2 & (0x3 << 6)) >> 6]; + out[2 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor2 & (0x3 << 6)) >> 6]; + out[2 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor2 & (0x3 << 6)) >> 6]; + out[2 * outPitch + 3 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha1 & (0x7 << 9)) >> 9]; + + out[3 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 0)) >> 0]; + out[3 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 0)) >> 0]; + out[3 * outPitch + 0 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 0)) >> 0]; + out[3 * outPitch + 0 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha1 & (0x7 << 12)) >> 12]; + + out[3 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 2)) >> 2]; + out[3 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 2)) >> 2]; + out[3 * outPitch + 1 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 2)) >> 2]; + out[3 * outPitch + 1 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha1 & (0x7 << 15)) >> 15]; + + out[3 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 4)) >> 4]; + out[3 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 4)) >> 4]; + out[3 * outPitch + 2 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 4)) >> 4]; + out[3 * outPitch + 2 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha1 & (0x7 << 18)) >> 18]; + + out[3 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataColor3 & (0x3 << 6)) >> 6]; + out[3 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataColor3 & (0x3 << 6)) >> 6]; + out[3 * outPitch + 3 * outPixelSize + outOffsetB] = colorTableBlue[(dataColor3 & (0x3 << 6)) >> 6]; + out[3 * outPitch + 3 * outPixelSize + outOffsetA] = colorTableAlpha[(dataAlpha1 & (0x7 << 21)) >> 21]; + } + + void DecompressBlockEdge(const uint8_t* in, + uint8_t* out, + const unsigned width, + const unsigned height, + const unsigned outPitch, + const unsigned outPixelSize, + const unsigned outOffsetR, + const unsigned outOffsetG, + const unsigned outOffsetB, + const unsigned outOffsetA) + { + assert(width <= BC3_BLOCK_PIXELS); + assert(height <= BC3_BLOCK_PIXELS); + + uint8_t colorTableRed[4]; + uint8_t colorTableGreen[4]; + uint8_t colorTableBlue[4]; + uint8_t colorTableAlpha[8]; + SetupColorTables(in, colorTableRed, colorTableGreen, colorTableBlue, colorTableAlpha); + + const uint32_t dataAlpha0 = in[2] | static_cast(in[3] << 8u) | static_cast(in[4] << 16u); + const uint32_t dataAlpha1 = in[5] | static_cast(in[6] << 8u) | static_cast(in[7] << 16u); + const uint8_t dataColor0 = in[BC3_ALPHA_CHANNEL_SIZE + 4]; + const uint8_t dataColor1 = in[BC3_ALPHA_CHANNEL_SIZE + 5]; + const uint8_t dataColor2 = in[BC3_ALPHA_CHANNEL_SIZE + 6]; + const uint8_t dataColor3 = in[BC3_ALPHA_CHANNEL_SIZE + 7]; + + const uint8_t pixelsRed[]{ + colorTableRed[(dataColor0 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor0 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor0 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor0 & (0x3 << 6)) >> 6], + colorTableRed[(dataColor1 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor1 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor1 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor1 & (0x3 << 6)) >> 6], + colorTableRed[(dataColor2 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor2 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor2 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor2 & (0x3 << 6)) >> 6], + colorTableRed[(dataColor3 & (0x3 << 0)) >> 0], + colorTableRed[(dataColor3 & (0x3 << 2)) >> 2], + colorTableRed[(dataColor3 & (0x3 << 4)) >> 4], + colorTableRed[(dataColor3 & (0x3 << 6)) >> 6], + }; + static_assert(std::extent_v == BC3_BLOCK_PIXELS * BC3_BLOCK_PIXELS); + + const uint8_t pixelsGreen[]{ + colorTableGreen[(dataColor0 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor0 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor0 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor0 & (0x3 << 6)) >> 6], + colorTableGreen[(dataColor1 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor1 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor1 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor1 & (0x3 << 6)) >> 6], + colorTableGreen[(dataColor2 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor2 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor2 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor2 & (0x3 << 6)) >> 6], + colorTableGreen[(dataColor3 & (0x3 << 0)) >> 0], + colorTableGreen[(dataColor3 & (0x3 << 2)) >> 2], + colorTableGreen[(dataColor3 & (0x3 << 4)) >> 4], + colorTableGreen[(dataColor3 & (0x3 << 6)) >> 6], + }; + static_assert(std::extent_v == BC3_BLOCK_PIXELS * BC3_BLOCK_PIXELS); + + const uint8_t pixelsBlue[]{ + colorTableBlue[(dataColor0 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor0 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor0 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor0 & (0x3 << 6)) >> 6], + colorTableBlue[(dataColor1 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor1 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor1 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor1 & (0x3 << 6)) >> 6], + colorTableBlue[(dataColor2 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor2 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor2 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor2 & (0x3 << 6)) >> 6], + colorTableBlue[(dataColor3 & (0x3 << 0)) >> 0], + colorTableBlue[(dataColor3 & (0x3 << 2)) >> 2], + colorTableBlue[(dataColor3 & (0x3 << 4)) >> 4], + colorTableBlue[(dataColor3 & (0x3 << 6)) >> 6], + }; + static_assert(std::extent_v == BC3_BLOCK_PIXELS * BC3_BLOCK_PIXELS); + + const uint8_t pixelsAlpha[]{ + colorTableAlpha[(dataAlpha0 & (0x7 << 0)) >> 0], + colorTableAlpha[(dataAlpha0 & (0x7 << 3)) >> 3], + colorTableAlpha[(dataAlpha0 & (0x7 << 6)) >> 6], + colorTableAlpha[(dataAlpha0 & (0x7 << 9)) >> 9], + colorTableAlpha[(dataAlpha0 & (0x7 << 12)) >> 12], + colorTableAlpha[(dataAlpha0 & (0x7 << 15)) >> 15], + colorTableAlpha[(dataAlpha0 & (0x7 << 18)) >> 18], + colorTableAlpha[(dataAlpha0 & (0x7 << 21)) >> 21], + colorTableAlpha[(dataAlpha1 & (0x7 << 0)) >> 0], + colorTableAlpha[(dataAlpha1 & (0x7 << 3)) >> 3], + colorTableAlpha[(dataAlpha1 & (0x7 << 6)) >> 6], + colorTableAlpha[(dataAlpha1 & (0x7 << 9)) >> 9], + colorTableAlpha[(dataAlpha1 & (0x7 << 12)) >> 12], + colorTableAlpha[(dataAlpha1 & (0x7 << 15)) >> 15], + colorTableAlpha[(dataAlpha1 & (0x7 << 18)) >> 18], + colorTableAlpha[(dataAlpha1 & (0x7 << 21)) >> 21], + }; + static_assert(std::extent_v == BC3_BLOCK_PIXELS * BC3_BLOCK_PIXELS); + + for (auto curHeight = 0u; curHeight < height; curHeight++) + { + for (auto curWidth = 0u; curWidth < width; curWidth++) + { + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetR] = pixelsRed[curHeight * BC3_BLOCK_PIXELS + curWidth]; + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetG] = pixelsGreen[curHeight * BC3_BLOCK_PIXELS + curWidth]; + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetB] = pixelsBlue[curHeight * BC3_BLOCK_PIXELS + curWidth]; + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetA] = pixelsAlpha[curHeight * BC3_BLOCK_PIXELS + curWidth]; + } + } + } +} // namespace + +namespace image +{ + std::unique_ptr DecompressorBc3::Decompress(const Texture& input, const ImageFormat* targetFormat) + { + assert(targetFormat->GetType() == ImageFormatType::UNSIGNED); + if (targetFormat->GetType() != ImageFormatType::UNSIGNED) + return nullptr; + + const auto* unsignedTargetFormat = dynamic_cast(targetFormat); + + const auto outPixelSize = unsignedTargetFormat->m_bits_per_pixel / 8u; + + // Only support formats with byte aligned channels + const auto redByteAligned = unsignedTargetFormat->m_r_size == 8 && (unsignedTargetFormat->m_r_offset % 8) == 0; + const auto greenByteAligned = unsignedTargetFormat->m_g_size == 8 && (unsignedTargetFormat->m_g_offset % 8) == 0; + const auto blueByteAligned = unsignedTargetFormat->m_b_size == 8 && (unsignedTargetFormat->m_b_offset % 8) == 0; + const auto alphaByteAligned = unsignedTargetFormat->m_a_size == 8 && (unsignedTargetFormat->m_a_offset % 8) == 0; + assert(redByteAligned); + assert(greenByteAligned); + assert(blueByteAligned); + assert(alphaByteAligned); + if (!redByteAligned || !greenByteAligned || !blueByteAligned || !alphaByteAligned) + return nullptr; + + const auto outOffsetR = unsignedTargetFormat->m_r_offset / 8; + const auto outOffsetG = unsignedTargetFormat->m_g_offset / 8; + const auto outOffsetB = unsignedTargetFormat->m_b_offset / 8; + const auto outOffsetA = unsignedTargetFormat->m_a_offset / 8; + + const auto width = input.GetWidth(); + const auto height = input.GetHeight(); + const auto depth = input.GetDepth(); + + auto result = Texture::CreateForType(input.GetTextureType(), targetFormat, width, height, depth, input.HasMipMaps()); + result->Allocate(); + + const auto faceCount = result->GetFaceCount(); + const auto mipCount = result->HasMipMaps() ? result->GetMipMapCount() : 1; + assert(mipCount == input.HasMipMaps() ? input.GetMipMapCount() : 1); + + for (auto mipLevel = 0; mipLevel < mipCount; mipLevel++) + { + const auto mipWidth = std::max(width >> mipLevel, 1u); + const auto mipHeight = std::max(height >> mipLevel, 1u); + const auto mipDepth = std::max(depth >> mipLevel, 1u); + const unsigned mipPitch = outPixelSize * mipWidth; + assert(mipPitch == result->GetFormat()->GetPitch(mipLevel, width)); + + for (auto face = 0; face < faceCount; face++) + { + const auto* bufferIn = input.GetBufferForMipLevel(mipLevel, face); + auto* bufferOut = result->GetBufferForMipLevel(mipLevel, face); + + for (auto curDepth = 0u; curDepth < mipDepth; curDepth++) + { + const auto fullBlocksWidth = utils::AlignToPrevious(mipWidth, BC3_BLOCK_PIXELS); + const auto fullBlocksHeight = utils::AlignToPrevious(mipHeight, BC3_BLOCK_PIXELS); + + for (auto curHeight = 0u; curHeight < fullBlocksHeight; curHeight += BC3_BLOCK_PIXELS) + { + for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC3_BLOCK_PIXELS) + { + DecompressBlock(bufferIn, bufferOut, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetB, outOffsetA); + bufferIn += BC3_BLOCK_SIZE; + bufferOut += outPixelSize * BC3_BLOCK_PIXELS; + } + + if (fullBlocksWidth < mipWidth) + { + const auto edgeBlockWidth = mipWidth - fullBlocksWidth; + DecompressBlockEdge( + bufferIn, bufferOut, edgeBlockWidth, BC3_BLOCK_PIXELS, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetB, outOffsetA); + bufferIn += BC3_BLOCK_SIZE; + bufferOut += outPixelSize * edgeBlockWidth; + } + + bufferOut += mipPitch * (BC3_BLOCK_PIXELS - 1); + } + + if (fullBlocksHeight < mipHeight) + { + const auto edgeBlockHeight = mipHeight - fullBlocksHeight; + for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC3_BLOCK_PIXELS) + { + DecompressBlockEdge( + bufferIn, bufferOut, BC3_BLOCK_PIXELS, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetB, outOffsetA); + bufferIn += BC3_BLOCK_SIZE; + bufferOut += outPixelSize * BC3_BLOCK_PIXELS; + } + + if (fullBlocksWidth < mipWidth) + { + const auto edgeBlockWidth = mipWidth - fullBlocksWidth; + DecompressBlockEdge( + bufferIn, bufferOut, edgeBlockWidth, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetB, outOffsetA); + bufferIn += BC3_BLOCK_SIZE; + bufferOut += outPixelSize * edgeBlockWidth; + } + + bufferOut += mipPitch * (BC3_BLOCK_PIXELS - 1); + } + } + } + } + + return result; + } +} // namespace image diff --git a/src/ObjImage/Image/Compression/DecompressorBc3.h b/src/ObjImage/Image/Compression/DecompressorBc3.h new file mode 100644 index 000000000..698940bec --- /dev/null +++ b/src/ObjImage/Image/Compression/DecompressorBc3.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ImageDecompressor.h" + +namespace image +{ + class DecompressorBc3 final : public ImageDecompressor + { + public: + std::unique_ptr Decompress(const Texture& input, const ImageFormat* targetFormat) override; + }; +} // namespace image diff --git a/src/ObjImage/Image/Compression/DecompressorBc4.cpp b/src/ObjImage/Image/Compression/DecompressorBc4.cpp new file mode 100644 index 000000000..30cda6534 --- /dev/null +++ b/src/ObjImage/Image/Compression/DecompressorBc4.cpp @@ -0,0 +1,215 @@ +#include "DecompressorBc4.h" + +#include "Utils/Alignment.h" + +#include +#include + +namespace +{ + constexpr auto BC4_BLOCK_PIXELS = 4u; + constexpr auto BC4_CHANNEL_SIZE = 8u; + constexpr auto BC4_BLOCK_SIZE = BC4_CHANNEL_SIZE; + + void SetupColorTable(const uint8_t* in, uint8_t (&colorTableRed)[8]) + { +#define INTERPOLATE_BC4(colorTable, val0, val1) \ + static_cast( \ + (static_cast(val0) * static_cast((colorTable)[0]) + static_cast(val1) * static_cast((colorTable)[1])) \ + / ((val0) + (val1))) + + colorTableRed[0] = in[0]; + colorTableRed[1] = in[1]; + + if (colorTableRed[0] > colorTableRed[1]) + { + // 6 interpolated color values + colorTableRed[2] = INTERPOLATE_BC4(colorTableRed, 6, 1); + colorTableRed[3] = INTERPOLATE_BC4(colorTableRed, 5, 2); + colorTableRed[4] = INTERPOLATE_BC4(colorTableRed, 4, 3); + colorTableRed[5] = INTERPOLATE_BC4(colorTableRed, 3, 4); + colorTableRed[6] = INTERPOLATE_BC4(colorTableRed, 2, 5); + colorTableRed[7] = INTERPOLATE_BC4(colorTableRed, 1, 6); + } + else + { + // 4 interpolated color values + colorTableRed[2] = INTERPOLATE_BC4(colorTableRed, 4, 1); + colorTableRed[3] = INTERPOLATE_BC4(colorTableRed, 3, 2); + colorTableRed[4] = INTERPOLATE_BC4(colorTableRed, 2, 3); + colorTableRed[5] = INTERPOLATE_BC4(colorTableRed, 1, 4); + colorTableRed[6] = std::numeric_limits::min(); + colorTableRed[7] = std::numeric_limits::max(); + } + +#undef INTERPOLATE_BC4 + } + + void DecompressBlock(const uint8_t* in, uint8_t* out, const unsigned outPitch, const unsigned outPixelSize, const unsigned outOffsetR) + { + uint8_t colorTableRed[8]; + SetupColorTable(in, colorTableRed); + const uint32_t dataRed0 = in[2] | static_cast(in[3] << 8u) | static_cast(in[4] << 16u); + const uint32_t dataRed1 = in[5] | static_cast(in[6] << 8u) | static_cast(in[7] << 16u); + + out[0 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 0)) >> 0]; + out[0 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 3)) >> 3]; + out[0 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 6)) >> 6]; + out[0 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 9)) >> 9]; + out[1 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 12)) >> 12]; + out[1 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 15)) >> 15]; + out[1 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 18)) >> 18]; + out[1 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 21)) >> 21]; + out[2 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 0)) >> 0]; + out[2 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 3)) >> 3]; + out[2 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 6)) >> 6]; + out[2 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 9)) >> 9]; + out[3 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 12)) >> 12]; + out[3 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 15)) >> 15]; + out[3 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 18)) >> 18]; + out[3 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 21)) >> 21]; + } + + void DecompressBlockEdge(const uint8_t* in, + uint8_t* out, + const unsigned width, + const unsigned height, + const unsigned outPitch, + const unsigned outPixelSize, + const unsigned outOffsetR) + { + assert(width <= BC4_BLOCK_PIXELS); + assert(height <= BC4_BLOCK_PIXELS); + + uint8_t colorTableRed[8]; + SetupColorTable(in, colorTableRed); + + const uint32_t dataRed0 = in[2] | static_cast(in[3] << 8u) | static_cast(in[4] << 16u); + const uint32_t dataRed1 = in[5] | static_cast(in[6] << 8u) | static_cast(in[7] << 16u); + + const uint8_t pixelsRed[]{ + colorTableRed[(dataRed0 & (0x7 << 0)) >> 21], + colorTableRed[(dataRed0 & (0x7 << 3)) >> 18], + colorTableRed[(dataRed0 & (0x7 << 6)) >> 15], + colorTableRed[(dataRed0 & (0x7 << 9)) >> 12], + colorTableRed[(dataRed0 & (0x7 << 12)) >> 9], + colorTableRed[(dataRed0 & (0x7 << 15)) >> 6], + colorTableRed[(dataRed0 & (0x7 << 18)) >> 3], + colorTableRed[(dataRed0 & (0x7 << 21)) >> 0], + colorTableRed[(dataRed1 & (0x7 << 0)) >> 21], + colorTableRed[(dataRed1 & (0x7 << 3)) >> 18], + colorTableRed[(dataRed1 & (0x7 << 6)) >> 15], + colorTableRed[(dataRed1 & (0x7 << 9)) >> 12], + colorTableRed[(dataRed1 & (0x7 << 12)) >> 9], + colorTableRed[(dataRed1 & (0x7 << 15)) >> 6], + colorTableRed[(dataRed1 & (0x7 << 18)) >> 3], + colorTableRed[(dataRed1 & (0x7 << 21)) >> 0], + }; + static_assert(std::extent_v == BC4_BLOCK_PIXELS * BC4_BLOCK_PIXELS); + + for (auto curHeight = 0u; curHeight < height; curHeight++) + { + for (auto curWidth = 0u; curWidth < width; curWidth++) + { + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetR] = pixelsRed[curHeight * BC4_BLOCK_PIXELS + curWidth]; + } + } + } +} // namespace + +namespace image +{ + std::unique_ptr DecompressorBc4::Decompress(const Texture& input, const ImageFormat* targetFormat) + { + assert(targetFormat->GetType() == ImageFormatType::UNSIGNED); + if (targetFormat->GetType() != ImageFormatType::UNSIGNED) + return nullptr; + + const auto* unsignedTargetFormat = dynamic_cast(targetFormat); + + const auto outPixelSize = unsignedTargetFormat->m_bits_per_pixel / 8u; + + // Only support formats with byte aligned channels + const auto redByteAligned = unsignedTargetFormat->m_r_size == 8 && (unsignedTargetFormat->m_r_offset % 8) == 0; + assert(redByteAligned); + if (!redByteAligned) + return nullptr; + + const auto outOffsetR = unsignedTargetFormat->m_r_offset / 8; + + const auto width = input.GetWidth(); + const auto height = input.GetHeight(); + const auto depth = input.GetDepth(); + + auto result = Texture::CreateForType(input.GetTextureType(), targetFormat, width, height, depth, input.HasMipMaps()); + result->Allocate(); + + const auto faceCount = result->GetFaceCount(); + const auto mipCount = result->HasMipMaps() ? result->GetMipMapCount() : 1; + assert(mipCount == input.HasMipMaps() ? input.GetMipMapCount() : 1); + + for (auto mipLevel = 0; mipLevel < mipCount; mipLevel++) + { + const auto mipWidth = std::max(width >> mipLevel, 1u); + const auto mipHeight = std::max(height >> mipLevel, 1u); + const auto mipDepth = std::max(depth >> mipLevel, 1u); + const unsigned mipPitch = outPixelSize * mipWidth; + assert(mipPitch == result->GetFormat()->GetPitch(mipLevel, width)); + + for (auto face = 0; face < faceCount; face++) + { + const auto* bufferIn = input.GetBufferForMipLevel(mipLevel, face); + auto* bufferOut = result->GetBufferForMipLevel(mipLevel, face); + + for (auto curDepth = 0u; curDepth < mipDepth; curDepth++) + { + const auto fullBlocksWidth = utils::AlignToPrevious(mipWidth, BC4_BLOCK_PIXELS); + const auto fullBlocksHeight = utils::AlignToPrevious(mipHeight, BC4_BLOCK_PIXELS); + + for (auto curHeight = 0u; curHeight < fullBlocksHeight; curHeight += BC4_BLOCK_PIXELS) + { + for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC4_BLOCK_PIXELS) + { + DecompressBlock(bufferIn, bufferOut, mipPitch, outPixelSize, outOffsetR); + bufferIn += BC4_BLOCK_SIZE; + bufferOut += outPixelSize * BC4_BLOCK_PIXELS; + } + + if (fullBlocksWidth < mipWidth) + { + const auto edgeBlockWidth = mipWidth - fullBlocksWidth; + DecompressBlockEdge(bufferIn, bufferOut, edgeBlockWidth, BC4_BLOCK_PIXELS, mipPitch, outPixelSize, outOffsetR); + bufferIn += BC4_BLOCK_SIZE; + bufferOut += outPixelSize * edgeBlockWidth; + } + + bufferOut += mipPitch * (BC4_BLOCK_PIXELS - 1); + } + + if (fullBlocksHeight < mipHeight) + { + const auto edgeBlockHeight = mipHeight - fullBlocksHeight; + for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC4_BLOCK_PIXELS) + { + DecompressBlockEdge(bufferIn, bufferOut, BC4_BLOCK_PIXELS, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR); + bufferIn += BC4_BLOCK_SIZE; + bufferOut += outPixelSize * BC4_BLOCK_PIXELS; + } + + if (fullBlocksWidth < mipWidth) + { + const auto edgeBlockWidth = mipWidth - fullBlocksWidth; + DecompressBlockEdge(bufferIn, bufferOut, edgeBlockWidth, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR); + bufferIn += BC4_BLOCK_SIZE; + bufferOut += outPixelSize * edgeBlockWidth; + } + + bufferOut += mipPitch * (BC4_BLOCK_PIXELS - 1); + } + } + } + } + + return result; + } +} // namespace image diff --git a/src/ObjImage/Image/Compression/DecompressorBc4.h b/src/ObjImage/Image/Compression/DecompressorBc4.h new file mode 100644 index 000000000..8e899f8b0 --- /dev/null +++ b/src/ObjImage/Image/Compression/DecompressorBc4.h @@ -0,0 +1,12 @@ +#pragma once + +#include "ImageDecompressor.h" + +namespace image +{ + class DecompressorBc4 final : public ImageDecompressor + { + public: + std::unique_ptr Decompress(const Texture& input, const ImageFormat* targetFormat) override; + }; +} // namespace image diff --git a/src/ObjImage/Image/Compression/DecompressorBc5.cpp b/src/ObjImage/Image/Compression/DecompressorBc5.cpp index 6a7a64396..33f5e8e1e 100644 --- a/src/ObjImage/Image/Compression/DecompressorBc5.cpp +++ b/src/ObjImage/Image/Compression/DecompressorBc5.cpp @@ -3,6 +3,7 @@ #include "Utils/Alignment.h" #include +#include namespace { @@ -12,10 +13,10 @@ namespace void SetupColorTables(const uint8_t* in, uint8_t (&colorTableRed)[8], uint8_t (&colorTableGreen)[8]) { -#define INTERPOLATE_BC5(colorTable, val0, val1, divide) \ - static_cast(static_cast(static_cast(val0) * static_cast((colorTable)[0]) \ - + static_cast(val1) * static_cast((colorTable)[1])) \ - / (divide)) +#define INTERPOLATE_BC5(colorTable, val0, val1) \ + static_cast( \ + (static_cast(val0) * static_cast((colorTable)[0]) + static_cast(val1) * static_cast((colorTable)[1])) \ + / ((val0) + (val1))) colorTableRed[0] = in[0]; colorTableRed[1] = in[1]; @@ -23,20 +24,20 @@ namespace if (colorTableRed[0] > colorTableRed[1]) { // 6 interpolated color values - colorTableRed[2] = INTERPOLATE_BC5(colorTableRed, 6, 1, 7.0f); - colorTableRed[3] = INTERPOLATE_BC5(colorTableRed, 5, 2, 7.0f); - colorTableRed[4] = INTERPOLATE_BC5(colorTableRed, 4, 3, 7.0f); - colorTableRed[5] = INTERPOLATE_BC5(colorTableRed, 3, 4, 7.0f); - colorTableRed[6] = INTERPOLATE_BC5(colorTableRed, 2, 5, 7.0f); - colorTableRed[7] = INTERPOLATE_BC5(colorTableRed, 1, 6, 7.0f); + colorTableRed[2] = INTERPOLATE_BC5(colorTableRed, 6, 1); + colorTableRed[3] = INTERPOLATE_BC5(colorTableRed, 5, 2); + colorTableRed[4] = INTERPOLATE_BC5(colorTableRed, 4, 3); + colorTableRed[5] = INTERPOLATE_BC5(colorTableRed, 3, 4); + colorTableRed[6] = INTERPOLATE_BC5(colorTableRed, 2, 5); + colorTableRed[7] = INTERPOLATE_BC5(colorTableRed, 1, 6); } else { // 4 interpolated color values - colorTableRed[2] = INTERPOLATE_BC5(colorTableRed, 4, 1, 5.0f); - colorTableRed[3] = INTERPOLATE_BC5(colorTableRed, 3, 2, 5.0f); - colorTableRed[4] = INTERPOLATE_BC5(colorTableRed, 2, 3, 5.0f); - colorTableRed[5] = INTERPOLATE_BC5(colorTableRed, 1, 4, 5.0f); + colorTableRed[2] = INTERPOLATE_BC5(colorTableRed, 4, 1); + colorTableRed[3] = INTERPOLATE_BC5(colorTableRed, 3, 2); + colorTableRed[4] = INTERPOLATE_BC5(colorTableRed, 2, 3); + colorTableRed[5] = INTERPOLATE_BC5(colorTableRed, 1, 4); colorTableRed[6] = std::numeric_limits::min(); colorTableRed[7] = std::numeric_limits::max(); } @@ -47,20 +48,20 @@ namespace if (colorTableGreen[0] > colorTableGreen[1]) { // 6 interpolated color values - colorTableGreen[2] = INTERPOLATE_BC5(colorTableGreen, 6, 1, 7.0f); - colorTableGreen[3] = INTERPOLATE_BC5(colorTableGreen, 5, 2, 7.0f); - colorTableGreen[4] = INTERPOLATE_BC5(colorTableGreen, 4, 3, 7.0f); - colorTableGreen[5] = INTERPOLATE_BC5(colorTableGreen, 3, 4, 7.0f); - colorTableGreen[6] = INTERPOLATE_BC5(colorTableGreen, 2, 5, 7.0f); - colorTableGreen[7] = INTERPOLATE_BC5(colorTableGreen, 1, 6, 7.0f); + colorTableGreen[2] = INTERPOLATE_BC5(colorTableGreen, 6, 1); + colorTableGreen[3] = INTERPOLATE_BC5(colorTableGreen, 5, 2); + colorTableGreen[4] = INTERPOLATE_BC5(colorTableGreen, 4, 3); + colorTableGreen[5] = INTERPOLATE_BC5(colorTableGreen, 3, 4); + colorTableGreen[6] = INTERPOLATE_BC5(colorTableGreen, 2, 5); + colorTableGreen[7] = INTERPOLATE_BC5(colorTableGreen, 1, 6); } else { // 4 interpolated color values - colorTableGreen[2] = INTERPOLATE_BC5(colorTableGreen, 4, 1, 5.0f); - colorTableGreen[3] = INTERPOLATE_BC5(colorTableGreen, 3, 2, 5.0f); - colorTableGreen[4] = INTERPOLATE_BC5(colorTableGreen, 2, 3, 5.0f); - colorTableGreen[5] = INTERPOLATE_BC5(colorTableGreen, 1, 4, 5.0f); + colorTableGreen[2] = INTERPOLATE_BC5(colorTableGreen, 4, 1); + colorTableGreen[3] = INTERPOLATE_BC5(colorTableGreen, 3, 2); + colorTableGreen[4] = INTERPOLATE_BC5(colorTableGreen, 2, 3); + colorTableGreen[5] = INTERPOLATE_BC5(colorTableGreen, 1, 4); colorTableGreen[6] = std::numeric_limits::min(); colorTableGreen[7] = std::numeric_limits::max(); } @@ -81,53 +82,53 @@ namespace const uint32_t dataGreen1 = in[BC5_CHANNEL_SIZE + 5] | static_cast(in[BC5_CHANNEL_SIZE + 6] << 8) | static_cast(in[BC5_CHANNEL_SIZE + 7] << 16); - out[0 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 21) >> 21]; - out[0 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 21) >> 21]; + out[0 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 0)) >> 0]; + out[0 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen0 & (0x7 << 0)) >> 0]; - out[0 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 18) >> 18]; - out[0 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 18) >> 18]; + out[0 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 3)) >> 3]; + out[0 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen0 & (0x7 << 3)) >> 3]; - out[0 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 15) >> 15]; - out[0 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 15) >> 15]; + out[0 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 6)) >> 6]; + out[0 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen0 & (0x7 << 6)) >> 6]; - out[0 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 12) >> 12]; - out[0 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 12) >> 12]; + out[0 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 9)) >> 9]; + out[0 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen0 & (0x7 << 9)) >> 9]; - out[1 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 9) >> 9]; - out[1 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 9) >> 9]; + out[1 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 12)) >> 12]; + out[1 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen0 & (0x7 << 12)) >> 12]; - out[1 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 6) >> 6]; - out[1 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 6) >> 6]; + out[1 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 15)) >> 15]; + out[1 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen0 & (0x7 << 15)) >> 15]; - out[1 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 3) >> 3]; - out[1 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 3) >> 3]; + out[1 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 18)) >> 18]; + out[1 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen0 & (0x7 << 18)) >> 18]; - out[1 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[dataRed0 & (0x7 << 0) >> 0]; - out[1 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen0 & (0x7 << 0) >> 0]; + out[1 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataRed0 & (0x7 << 21)) >> 21]; + out[1 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen0 & (0x7 << 21)) >> 21]; - out[2 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 21) >> 21]; - out[2 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 21) >> 21]; + out[2 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 0)) >> 0]; + out[2 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen1 & (0x7 << 0)) >> 0]; - out[2 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 18) >> 18]; - out[2 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 18) >> 18]; + out[2 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 3)) >> 3]; + out[2 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen1 & (0x7 << 3)) >> 3]; - out[2 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 15) >> 15]; - out[2 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 15) >> 15]; + out[2 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 6)) >> 6]; + out[2 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen1 & (0x7 << 6)) >> 6]; - out[2 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 12) >> 12]; - out[2 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 12) >> 12]; + out[2 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 9)) >> 9]; + out[2 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen1 & (0x7 << 9)) >> 9]; - out[3 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 9) >> 9]; - out[3 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 9) >> 9]; + out[3 * outPitch + 0 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 12)) >> 12]; + out[3 * outPitch + 0 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen1 & (0x7 << 12)) >> 12]; - out[3 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 6) >> 6]; - out[3 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 6) >> 6]; + out[3 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 15)) >> 15]; + out[3 * outPitch + 1 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen1 & (0x7 << 15)) >> 15]; - out[3 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 3) >> 3]; - out[3 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 3) >> 3]; + out[3 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 18)) >> 18]; + out[3 * outPitch + 2 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen1 & (0x7 << 18)) >> 18]; - out[3 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[dataRed1 & (0x7 << 0) >> 0]; - out[3 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[dataGreen1 & (0x7 << 0) >> 0]; + out[3 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 21)) >> 21]; + out[3 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen1 & (0x7 << 21)) >> 21]; } void DecompressBlockEdge(const uint8_t* in, @@ -154,42 +155,42 @@ namespace in[BC5_CHANNEL_SIZE + 5] | static_cast(in[BC5_CHANNEL_SIZE + 6] << 8) | static_cast(in[BC5_CHANNEL_SIZE + 7] << 16); const uint8_t pixelsRed[]{ - colorTableRed[dataRed0 & (0x7 << 21) >> 21], - colorTableRed[dataRed0 & (0x7 << 18) >> 18], - colorTableRed[dataRed0 & (0x7 << 15) >> 15], - colorTableRed[dataRed0 & (0x7 << 12) >> 12], - colorTableRed[dataRed0 & (0x7 << 9) >> 9], - colorTableRed[dataRed0 & (0x7 << 6) >> 6], - colorTableRed[dataRed0 & (0x7 << 3) >> 3], - colorTableRed[dataRed0 & (0x7 << 0) >> 0], - colorTableRed[dataRed1 & (0x7 << 21) >> 21], - colorTableRed[dataRed1 & (0x7 << 18) >> 18], - colorTableRed[dataRed1 & (0x7 << 15) >> 15], - colorTableRed[dataRed1 & (0x7 << 12) >> 12], - colorTableRed[dataRed1 & (0x7 << 9) >> 9], - colorTableRed[dataRed1 & (0x7 << 6) >> 6], - colorTableRed[dataRed1 & (0x7 << 3) >> 3], - colorTableRed[dataRed1 & (0x7 << 0) >> 0], + colorTableRed[(dataRed0 & (0x7 << 0)) >> 0], + colorTableRed[(dataRed0 & (0x7 << 3)) >> 3], + colorTableRed[(dataRed0 & (0x7 << 6)) >> 6], + colorTableRed[(dataRed0 & (0x7 << 9)) >> 9], + colorTableRed[(dataRed0 & (0x7 << 12)) >> 12], + colorTableRed[(dataRed0 & (0x7 << 15)) >> 15], + colorTableRed[(dataRed0 & (0x7 << 18)) >> 18], + colorTableRed[(dataRed0 & (0x7 << 21)) >> 21], + colorTableRed[(dataRed1 & (0x7 << 0)) >> 0], + colorTableRed[(dataRed1 & (0x7 << 3)) >> 3], + colorTableRed[(dataRed1 & (0x7 << 6)) >> 6], + colorTableRed[(dataRed1 & (0x7 << 9)) >> 9], + colorTableRed[(dataRed1 & (0x7 << 12)) >> 12], + colorTableRed[(dataRed1 & (0x7 << 15)) >> 15], + colorTableRed[(dataRed1 & (0x7 << 18)) >> 18], + colorTableRed[(dataRed1 & (0x7 << 21)) >> 21], }; static_assert(std::extent_v == BC5_BLOCK_PIXELS * BC5_BLOCK_PIXELS); const uint8_t pixelsGreen[]{ - colorTableGreen[dataGreen0 & (0x7 << 21) >> 21], - colorTableGreen[dataGreen0 & (0x7 << 18) >> 18], - colorTableGreen[dataGreen0 & (0x7 << 15) >> 15], - colorTableGreen[dataGreen0 & (0x7 << 12) >> 12], - colorTableGreen[dataGreen0 & (0x7 << 9) >> 9], - colorTableGreen[dataGreen0 & (0x7 << 6) >> 6], - colorTableGreen[dataGreen0 & (0x7 << 3) >> 3], - colorTableGreen[dataGreen0 & (0x7 << 0) >> 0], - colorTableGreen[dataGreen1 & (0x7 << 21) >> 21], - colorTableGreen[dataGreen1 & (0x7 << 18) >> 18], - colorTableGreen[dataGreen1 & (0x7 << 15) >> 15], - colorTableGreen[dataGreen1 & (0x7 << 12) >> 12], - colorTableGreen[dataGreen1 & (0x7 << 9) >> 9], - colorTableGreen[dataGreen1 & (0x7 << 6) >> 6], - colorTableGreen[dataGreen1 & (0x7 << 3) >> 3], - colorTableGreen[dataGreen1 & (0x7 << 0) >> 0], + colorTableGreen[(dataGreen0 & (0x7 << 0)) >> 0], + colorTableGreen[(dataGreen0 & (0x7 << 3)) >> 3], + colorTableGreen[(dataGreen0 & (0x7 << 6)) >> 6], + colorTableGreen[(dataGreen0 & (0x7 << 9)) >> 9], + colorTableGreen[(dataGreen0 & (0x7 << 12)) >> 12], + colorTableGreen[(dataGreen0 & (0x7 << 15)) >> 15], + colorTableGreen[(dataGreen0 & (0x7 << 18)) >> 18], + colorTableGreen[(dataGreen0 & (0x7 << 21)) >> 21], + colorTableGreen[(dataGreen1 & (0x7 << 0)) >> 0], + colorTableGreen[(dataGreen1 & (0x7 << 3)) >> 3], + colorTableGreen[(dataGreen1 & (0x7 << 6)) >> 6], + colorTableGreen[(dataGreen1 & (0x7 << 9)) >> 9], + colorTableGreen[(dataGreen1 & (0x7 << 12)) >> 12], + colorTableGreen[(dataGreen1 & (0x7 << 15)) >> 15], + colorTableGreen[(dataGreen1 & (0x7 << 18)) >> 18], + colorTableGreen[(dataGreen1 & (0x7 << 21)) >> 21], }; static_assert(std::extent_v == BC5_BLOCK_PIXELS * BC5_BLOCK_PIXELS); @@ -235,8 +236,8 @@ namespace image result->Allocate(); const auto faceCount = result->GetFaceCount(); - const auto mipCount = result->GetMipMapCount(); - assert(mipCount == input.GetMipMapCount()); + const auto mipCount = result->HasMipMaps() ? result->GetMipMapCount() : 1; + assert(mipCount == input.HasMipMaps() ? input.GetMipMapCount() : 1); for (auto mipLevel = 0; mipLevel < mipCount; mipLevel++) { @@ -272,6 +273,8 @@ namespace image bufferIn += BC5_BLOCK_SIZE; bufferOut += outPixelSize * edgeBlockWidth; } + + bufferOut += mipPitch * (BC5_BLOCK_PIXELS - 1); } if (fullBlocksHeight < mipHeight) @@ -291,6 +294,8 @@ namespace image bufferIn += BC5_BLOCK_SIZE; bufferOut += outPixelSize * edgeBlockWidth; } + + bufferOut += mipPitch * (BC5_BLOCK_PIXELS - 1); } } } diff --git a/src/ObjImage/Image/Compression/ImageDecompressor.cpp b/src/ObjImage/Image/Compression/ImageDecompressor.cpp index 7c450c709..7c4626726 100644 --- a/src/ObjImage/Image/Compression/ImageDecompressor.cpp +++ b/src/ObjImage/Image/Compression/ImageDecompressor.cpp @@ -1,5 +1,9 @@ #include "ImageDecompressor.h" +#include "DecompressorBc1.h" +#include "DecompressorBc2.h" +#include "DecompressorBc3.h" +#include "DecompressorBc4.h" #include "DecompressorBc5.h" namespace image @@ -9,11 +13,28 @@ namespace image switch (formatId) { case ImageFormatId::BC1: + { + static DecompressorBc1 bc1; + return &bc1; + } + case ImageFormatId::BC2: + { + static DecompressorBc2 bc2; + return &bc2; + } + case ImageFormatId::BC3: + { + static DecompressorBc3 bc3; + return &bc3; + } + case ImageFormatId::BC4: - // TODO - return nullptr; + { + static DecompressorBc4 bc4; + return &bc4; + } case ImageFormatId::BC5: { diff --git a/src/ObjImage/Image/TextureConverter.cpp b/src/ObjImage/Image/TextureConverter.cpp index 619338435..a3ccf56ce 100644 --- a/src/ObjImage/Image/TextureConverter.cpp +++ b/src/ObjImage/Image/TextureConverter.cpp @@ -2,7 +2,7 @@ #include -namespace image +namespace { constexpr uint64_t Mask1(const unsigned length) { @@ -12,6 +12,14 @@ namespace image return std::numeric_limits::max() >> (sizeof(uint64_t) * 8 - length); } + bool CanReorder(const unsigned inputSize, const unsigned outputSize) + { + return inputSize == 0 || outputSize == 0 || inputSize == outputSize; + } +} // namespace + +namespace image +{ void TextureConverter::SetPixelFunctions(const unsigned inBitCount, const unsigned outBitCount) { switch (inBitCount) @@ -150,10 +158,13 @@ namespace image const auto gInputMask = inputFormat->HasG() ? Mask1(inputFormat->m_g_size) << inputFormat->m_g_offset : 0; const auto bInputMask = inputFormat->HasB() ? Mask1(inputFormat->m_b_size) << inputFormat->m_b_offset : 0; const auto aInputMask = inputFormat->HasA() ? Mask1(inputFormat->m_a_size) << inputFormat->m_a_offset : 0; + const auto aOutputMask = outputFormat->HasA() ? Mask1(outputFormat->m_a_size) << outputFormat->m_a_offset : 0; const bool rConvert = rInputMask != 0 && outputFormat->m_r_size > 0; const bool gConvert = gInputMask != 0 && outputFormat->m_g_size > 0; const bool bConvert = bInputMask != 0 && outputFormat->m_b_size > 0; - const bool aConvert = aInputMask != 0 && outputFormat->m_a_size > 0; + + // alpha has a default of 1 so we need to convert even input has no alpha + const bool aConvert = outputFormat->m_a_size > 0; for (auto mipLevel = 0; mipLevel < mipCount; mipLevel++) { @@ -176,8 +187,13 @@ namespace image outPixel |= (inPixel & gInputMask) >> inputFormat->m_g_offset << outputFormat->m_g_offset; if (bConvert) outPixel |= (inPixel & bInputMask) >> inputFormat->m_b_offset << outputFormat->m_b_offset; + if (aConvert) - outPixel |= (inPixel & aInputMask) >> inputFormat->m_a_offset << outputFormat->m_a_offset; + { + const auto value = aInputMask != 0 ? (inPixel & aInputMask) >> inputFormat->m_a_offset << outputFormat->m_a_offset + : std::numeric_limits::max() & aOutputMask; + outPixel |= value; + } m_write_pixel_func(&outputBuffer[outputOffset], outPixel, outputFormat->m_bits_per_pixel); } @@ -194,8 +210,8 @@ namespace image SetPixelFunctions(inputFormat->m_bits_per_pixel, outputFormat->m_bits_per_pixel); - if (inputFormat->m_r_size == outputFormat->m_r_size && inputFormat->m_g_size == outputFormat->m_g_size - && inputFormat->m_b_size == outputFormat->m_b_size && inputFormat->m_a_size == outputFormat->m_a_size) + if (CanReorder(inputFormat->m_r_size, outputFormat->m_r_size) && CanReorder(inputFormat->m_g_size, outputFormat->m_g_size) + && CanReorder(inputFormat->m_b_size, outputFormat->m_b_size) && CanReorder(inputFormat->m_a_size, outputFormat->m_a_size)) { ReorderUnsignedToUnsigned(); } From b32701f64fe7194f3fae174509b2dbfb5b66ce5a Mon Sep 17 00:00:00 2001 From: Jan Laupetin Date: Mon, 22 Jun 2026 00:51:15 +0200 Subject: [PATCH 6/6] chore: always set full alpha if available on bc4 and bc5 decompression --- .../Image/Compression/DecompressorBc4.cpp | 82 +++++++++++++------ .../Image/Compression/DecompressorBc5.cpp | 53 ++++++++++-- 2 files changed, 104 insertions(+), 31 deletions(-) diff --git a/src/ObjImage/Image/Compression/DecompressorBc4.cpp b/src/ObjImage/Image/Compression/DecompressorBc4.cpp index 30cda6534..37810865e 100644 --- a/src/ObjImage/Image/Compression/DecompressorBc4.cpp +++ b/src/ObjImage/Image/Compression/DecompressorBc4.cpp @@ -45,7 +45,13 @@ namespace #undef INTERPOLATE_BC4 } - void DecompressBlock(const uint8_t* in, uint8_t* out, const unsigned outPitch, const unsigned outPixelSize, const unsigned outOffsetR) + void DecompressBlock(const uint8_t* in, + uint8_t* out, + const unsigned outPitch, + const unsigned outPixelSize, + const unsigned outOffsetR, + const unsigned outOffsetA, + const bool hasAlpha) { uint8_t colorTableRed[8]; SetupColorTable(in, colorTableRed); @@ -68,6 +74,17 @@ namespace out[3 * outPitch + 1 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 15)) >> 15]; out[3 * outPitch + 2 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 18)) >> 18]; out[3 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 21)) >> 21]; + + if (hasAlpha) + { + for (auto curHeight = 0u; curHeight < BC4_BLOCK_PIXELS; curHeight++) + { + for (auto curWidth = 0u; curWidth < BC4_BLOCK_PIXELS; curWidth++) + { + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetA] = std::numeric_limits::max(); + } + } + } } void DecompressBlockEdge(const uint8_t* in, @@ -76,7 +93,9 @@ namespace const unsigned height, const unsigned outPitch, const unsigned outPixelSize, - const unsigned outOffsetR) + const unsigned outOffsetR, + const unsigned outOffsetA, + const bool hasAlpha) { assert(width <= BC4_BLOCK_PIXELS); assert(height <= BC4_BLOCK_PIXELS); @@ -88,22 +107,22 @@ namespace const uint32_t dataRed1 = in[5] | static_cast(in[6] << 8u) | static_cast(in[7] << 16u); const uint8_t pixelsRed[]{ - colorTableRed[(dataRed0 & (0x7 << 0)) >> 21], - colorTableRed[(dataRed0 & (0x7 << 3)) >> 18], - colorTableRed[(dataRed0 & (0x7 << 6)) >> 15], - colorTableRed[(dataRed0 & (0x7 << 9)) >> 12], - colorTableRed[(dataRed0 & (0x7 << 12)) >> 9], - colorTableRed[(dataRed0 & (0x7 << 15)) >> 6], - colorTableRed[(dataRed0 & (0x7 << 18)) >> 3], - colorTableRed[(dataRed0 & (0x7 << 21)) >> 0], - colorTableRed[(dataRed1 & (0x7 << 0)) >> 21], - colorTableRed[(dataRed1 & (0x7 << 3)) >> 18], - colorTableRed[(dataRed1 & (0x7 << 6)) >> 15], - colorTableRed[(dataRed1 & (0x7 << 9)) >> 12], - colorTableRed[(dataRed1 & (0x7 << 12)) >> 9], - colorTableRed[(dataRed1 & (0x7 << 15)) >> 6], - colorTableRed[(dataRed1 & (0x7 << 18)) >> 3], - colorTableRed[(dataRed1 & (0x7 << 21)) >> 0], + colorTableRed[(dataRed0 & (0x7 << 0)) >> 0], + colorTableRed[(dataRed0 & (0x7 << 3)) >> 3], + colorTableRed[(dataRed0 & (0x7 << 6)) >> 6], + colorTableRed[(dataRed0 & (0x7 << 9)) >> 9], + colorTableRed[(dataRed0 & (0x7 << 12)) >> 12], + colorTableRed[(dataRed0 & (0x7 << 15)) >> 15], + colorTableRed[(dataRed0 & (0x7 << 18)) >> 18], + colorTableRed[(dataRed0 & (0x7 << 21)) >> 21], + colorTableRed[(dataRed1 & (0x7 << 0)) >> 0], + colorTableRed[(dataRed1 & (0x7 << 3)) >> 3], + colorTableRed[(dataRed1 & (0x7 << 6)) >> 6], + colorTableRed[(dataRed1 & (0x7 << 9)) >> 9], + colorTableRed[(dataRed1 & (0x7 << 12)) >> 12], + colorTableRed[(dataRed1 & (0x7 << 15)) >> 15], + colorTableRed[(dataRed1 & (0x7 << 18)) >> 18], + colorTableRed[(dataRed1 & (0x7 << 21)) >> 21], }; static_assert(std::extent_v == BC4_BLOCK_PIXELS * BC4_BLOCK_PIXELS); @@ -114,6 +133,17 @@ namespace out[curHeight * outPitch + curWidth * outPixelSize + outOffsetR] = pixelsRed[curHeight * BC4_BLOCK_PIXELS + curWidth]; } } + + if (hasAlpha) + { + for (auto curHeight = 0u; curHeight < height; curHeight++) + { + for (auto curWidth = 0u; curWidth < width; curWidth++) + { + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetA] = std::numeric_limits::max(); + } + } + } } } // namespace @@ -131,11 +161,15 @@ namespace image // Only support formats with byte aligned channels const auto redByteAligned = unsignedTargetFormat->m_r_size == 8 && (unsignedTargetFormat->m_r_offset % 8) == 0; + const auto hasAlpha = unsignedTargetFormat->m_a_size > 0; + const auto alphaByteAligned = unsignedTargetFormat->m_a_size == 8 && (unsignedTargetFormat->m_a_offset % 8) == 0; assert(redByteAligned); - if (!redByteAligned) + assert(!hasAlpha || alphaByteAligned); + if (!redByteAligned || (hasAlpha && !alphaByteAligned)) return nullptr; const auto outOffsetR = unsignedTargetFormat->m_r_offset / 8; + const auto outOffsetA = unsignedTargetFormat->m_a_offset / 8; const auto width = input.GetWidth(); const auto height = input.GetHeight(); @@ -170,7 +204,7 @@ namespace image { for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC4_BLOCK_PIXELS) { - DecompressBlock(bufferIn, bufferOut, mipPitch, outPixelSize, outOffsetR); + DecompressBlock(bufferIn, bufferOut, mipPitch, outPixelSize, outOffsetR, outOffsetA, hasAlpha); bufferIn += BC4_BLOCK_SIZE; bufferOut += outPixelSize * BC4_BLOCK_PIXELS; } @@ -178,7 +212,8 @@ namespace image if (fullBlocksWidth < mipWidth) { const auto edgeBlockWidth = mipWidth - fullBlocksWidth; - DecompressBlockEdge(bufferIn, bufferOut, edgeBlockWidth, BC4_BLOCK_PIXELS, mipPitch, outPixelSize, outOffsetR); + DecompressBlockEdge( + bufferIn, bufferOut, edgeBlockWidth, BC4_BLOCK_PIXELS, mipPitch, outPixelSize, outOffsetR, outOffsetA, hasAlpha); bufferIn += BC4_BLOCK_SIZE; bufferOut += outPixelSize * edgeBlockWidth; } @@ -191,7 +226,8 @@ namespace image const auto edgeBlockHeight = mipHeight - fullBlocksHeight; for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC4_BLOCK_PIXELS) { - DecompressBlockEdge(bufferIn, bufferOut, BC4_BLOCK_PIXELS, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR); + DecompressBlockEdge( + bufferIn, bufferOut, BC4_BLOCK_PIXELS, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetA, hasAlpha); bufferIn += BC4_BLOCK_SIZE; bufferOut += outPixelSize * BC4_BLOCK_PIXELS; } @@ -199,7 +235,7 @@ namespace image if (fullBlocksWidth < mipWidth) { const auto edgeBlockWidth = mipWidth - fullBlocksWidth; - DecompressBlockEdge(bufferIn, bufferOut, edgeBlockWidth, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR); + DecompressBlockEdge(bufferIn, bufferOut, edgeBlockWidth, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetA, hasAlpha); bufferIn += BC4_BLOCK_SIZE; bufferOut += outPixelSize * edgeBlockWidth; } diff --git a/src/ObjImage/Image/Compression/DecompressorBc5.cpp b/src/ObjImage/Image/Compression/DecompressorBc5.cpp index 33f5e8e1e..ceb33acaa 100644 --- a/src/ObjImage/Image/Compression/DecompressorBc5.cpp +++ b/src/ObjImage/Image/Compression/DecompressorBc5.cpp @@ -69,8 +69,14 @@ namespace #undef INTERPOLATE_BC5 } - void DecompressBlock( - const uint8_t* in, uint8_t* out, const unsigned outPitch, const unsigned outPixelSize, const unsigned outOffsetR, const unsigned outOffsetG) + void DecompressBlock(const uint8_t* in, + uint8_t* out, + const unsigned outPitch, + const unsigned outPixelSize, + const unsigned outOffsetR, + const unsigned outOffsetG, + const unsigned outOffsetA, + const bool hasAlpha) { uint8_t colorTableRed[8]; uint8_t colorTableGreen[8]; @@ -129,6 +135,17 @@ namespace out[3 * outPitch + 3 * outPixelSize + outOffsetR] = colorTableRed[(dataRed1 & (0x7 << 21)) >> 21]; out[3 * outPitch + 3 * outPixelSize + outOffsetG] = colorTableGreen[(dataGreen1 & (0x7 << 21)) >> 21]; + + if (hasAlpha) + { + for (auto curHeight = 0u; curHeight < BC5_BLOCK_PIXELS; curHeight++) + { + for (auto curWidth = 0u; curWidth < BC5_BLOCK_PIXELS; curWidth++) + { + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetA] = std::numeric_limits::max(); + } + } + } } void DecompressBlockEdge(const uint8_t* in, @@ -138,7 +155,9 @@ namespace const unsigned outPitch, const unsigned outPixelSize, const unsigned outOffsetR, - const unsigned outOffsetG) + const unsigned outOffsetG, + const unsigned outOffsetA, + const bool hasAlpha) { assert(width <= BC5_BLOCK_PIXELS); assert(height <= BC5_BLOCK_PIXELS); @@ -202,6 +221,17 @@ namespace out[curHeight * outPitch + curWidth * outPixelSize + outOffsetG] = pixelsGreen[curHeight * BC5_BLOCK_PIXELS + curWidth]; } } + + if (hasAlpha) + { + for (auto curHeight = 0u; curHeight < height; curHeight++) + { + for (auto curWidth = 0u; curWidth < width; curWidth++) + { + out[curHeight * outPitch + curWidth * outPixelSize + outOffsetA] = std::numeric_limits::max(); + } + } + } } } // namespace @@ -220,13 +250,17 @@ namespace image // Only support formats with byte aligned channels const auto redByteAligned = unsignedTargetFormat->m_r_size == 8 && (unsignedTargetFormat->m_r_offset % 8) == 0; const auto greenByteAligned = unsignedTargetFormat->m_g_size == 8 && (unsignedTargetFormat->m_g_offset % 8) == 0; + const auto hasAlpha = unsignedTargetFormat->m_a_size > 0; + const auto alphaByteAligned = unsignedTargetFormat->m_a_size == 8 && (unsignedTargetFormat->m_a_offset % 8) == 0; assert(redByteAligned); assert(greenByteAligned); - if (!redByteAligned || !greenByteAligned) + assert(!hasAlpha || alphaByteAligned); + if (!redByteAligned || !greenByteAligned || (hasAlpha && !alphaByteAligned)) return nullptr; const auto outOffsetR = unsignedTargetFormat->m_r_offset / 8; const auto outOffsetG = unsignedTargetFormat->m_g_offset / 8; + const auto outOffsetA = unsignedTargetFormat->m_a_offset / 8; const auto width = input.GetWidth(); const auto height = input.GetHeight(); @@ -261,7 +295,7 @@ namespace image { for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC5_BLOCK_PIXELS) { - DecompressBlock(bufferIn, bufferOut, mipPitch, outPixelSize, outOffsetR, outOffsetG); + DecompressBlock(bufferIn, bufferOut, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetA, hasAlpha); bufferIn += BC5_BLOCK_SIZE; bufferOut += outPixelSize * BC5_BLOCK_PIXELS; } @@ -269,7 +303,8 @@ namespace image if (fullBlocksWidth < mipWidth) { const auto edgeBlockWidth = mipWidth - fullBlocksWidth; - DecompressBlockEdge(bufferIn, bufferOut, edgeBlockWidth, BC5_BLOCK_PIXELS, mipPitch, outPixelSize, outOffsetR, outOffsetG); + DecompressBlockEdge( + bufferIn, bufferOut, edgeBlockWidth, BC5_BLOCK_PIXELS, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetA, hasAlpha); bufferIn += BC5_BLOCK_SIZE; bufferOut += outPixelSize * edgeBlockWidth; } @@ -282,7 +317,8 @@ namespace image const auto edgeBlockHeight = mipHeight - fullBlocksHeight; for (auto curWidth = 0u; curWidth < fullBlocksWidth; curWidth += BC5_BLOCK_PIXELS) { - DecompressBlockEdge(bufferIn, bufferOut, BC5_BLOCK_PIXELS, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetG); + DecompressBlockEdge( + bufferIn, bufferOut, BC5_BLOCK_PIXELS, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetA, hasAlpha); bufferIn += BC5_BLOCK_SIZE; bufferOut += outPixelSize * BC5_BLOCK_PIXELS; } @@ -290,7 +326,8 @@ namespace image if (fullBlocksWidth < mipWidth) { const auto edgeBlockWidth = mipWidth - fullBlocksWidth; - DecompressBlockEdge(bufferIn, bufferOut, edgeBlockWidth, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetG); + DecompressBlockEdge( + bufferIn, bufferOut, edgeBlockWidth, edgeBlockHeight, mipPitch, outPixelSize, outOffsetR, outOffsetG, outOffsetA, hasAlpha); bufferIn += BC5_BLOCK_SIZE; bufferOut += outPixelSize * edgeBlockWidth; }