Add support for dds in linux

This commit is contained in:
alsed
2025-08-12 19:18:07 -04:00
parent f37b75df7b
commit 09a304c214
5 changed files with 1387 additions and 81 deletions

View File

@@ -48,6 +48,9 @@
#include <ThirdParty/stb/stb_dxt.h>
#if USE_EDITOR
// Import ddspp library
#include <ThirdParty/ddspp/ddspp.h>
// Compression libs for Editor
#include <ThirdParty/detex/detex.h>
#include <ThirdParty/bc7enc16/bc7enc16.h>
@@ -77,6 +80,7 @@ static TextureData const* stbDecompress(const TextureData& textureData, TextureD
{
if (!PixelFormatExtensions::IsCompressed(textureData.Format))
return &textureData;
const bool srgb = PixelFormatExtensions::IsSRGB(textureData.Format);
switch (textureData.Format)
{
@@ -92,90 +96,104 @@ static TextureData const* stbDecompress(const TextureData& textureData, TextureD
decompressed.Format = srgb ? PixelFormat::R8G8B8A8_UNorm_sRGB : PixelFormat::R8G8B8A8_UNorm;
break;
}
decompressed.Width = textureData.Width;
decompressed.Height = textureData.Height;
decompressed.Depth = textureData.Depth;
decompressed.Items.Resize(1);
decompressed.Items[0].Mips.Resize(1);
TextureMipData* decompressedData = decompressed.GetData(0, 0);
decompressedData->RowPitch = textureData.Width * PixelFormatExtensions::SizeInBytes(decompressed.Format);
decompressedData->Lines = textureData.Height;
decompressedData->DepthPitch = decompressedData->RowPitch * decompressedData->Lines;
decompressedData->Data.Allocate(decompressedData->DepthPitch);
byte* decompressedBytes = decompressedData->Data.Get();
Color32 colors[16];
int32 blocksWidth = textureData.Width / 4;
int32 blocksHeight = textureData.Height / 4;
const TextureMipData* blocksData = textureData.GetData(0, 0);
const byte* blocksBytes = blocksData->Data.Get();
decompressed.Items.Resize(textureData.Items.Count());
typedef bool (*detexDecompressBlockFuncType)(const uint8_t* bitstring, uint32_t mode_mask, uint32_t flags, uint8_t* pixel_buffer);
detexDecompressBlockFuncType detexDecompressBlockFunc;
int32 pixelSize, blockSize;
switch (textureData.Format)
{
case PixelFormat::BC1_UNorm:
case PixelFormat::BC1_UNorm_sRGB:
detexDecompressBlockFunc = detexDecompressBlockBC1;
pixelSize = 4;
blockSize = 8;
break;
case PixelFormat::BC2_UNorm:
case PixelFormat::BC2_UNorm_sRGB:
detexDecompressBlockFunc = detexDecompressBlockBC2;
pixelSize = 4;
blockSize = 16;
break;
case PixelFormat::BC3_UNorm:
case PixelFormat::BC3_UNorm_sRGB:
detexDecompressBlockFunc = detexDecompressBlockBC3;
pixelSize = 4;
blockSize = 16;
break;
case PixelFormat::BC4_UNorm:
detexDecompressBlockFunc = detexDecompressBlockRGTC1;
pixelSize = 1;
blockSize = 8;
break;
case PixelFormat::BC5_UNorm:
detexDecompressBlockFunc = detexDecompressBlockRGTC2;
pixelSize = 2;
blockSize = 16;
break;
case PixelFormat::BC7_UNorm:
case PixelFormat::BC7_UNorm_sRGB:
detexDecompressBlockFunc = detexDecompressBlockBPTC;
pixelSize = 4;
blockSize = 16;
break;
default:
LOG(Warning, "Texture data format {0} is not supported by detex library.", (int32)textureData.Format);
return nullptr;
case PixelFormat::BC1_UNorm:
case PixelFormat::BC1_UNorm_sRGB:
detexDecompressBlockFunc = detexDecompressBlockBC1;
pixelSize = 4;
blockSize = 8;
break;
case PixelFormat::BC2_UNorm:
case PixelFormat::BC2_UNorm_sRGB:
detexDecompressBlockFunc = detexDecompressBlockBC2;
pixelSize = 4;
blockSize = 16;
break;
case PixelFormat::BC3_UNorm:
case PixelFormat::BC3_UNorm_sRGB:
detexDecompressBlockFunc = detexDecompressBlockBC3;
pixelSize = 4;
blockSize = 16;
break;
case PixelFormat::BC4_UNorm:
detexDecompressBlockFunc = detexDecompressBlockRGTC1;
pixelSize = 1;
blockSize = 8;
break;
case PixelFormat::BC5_UNorm:
detexDecompressBlockFunc = detexDecompressBlockRGTC2;
pixelSize = 2;
blockSize = 16;
break;
case PixelFormat::BC7_UNorm:
case PixelFormat::BC7_UNorm_sRGB:
detexDecompressBlockFunc = detexDecompressBlockBPTC;
pixelSize = 4;
blockSize = 16;
break;
default:
LOG(Warning, "Texture data format {0} is not supported by detex library.", (int32)textureData.Format);
return nullptr;
}
uint8 blockBuffer[DETEX_MAX_BLOCK_SIZE];
for (int32 y = 0; y < blocksHeight; y++)
for (int32 arrayIndex = 0; arrayIndex < textureData.Items.Count(); arrayIndex++)
{
int32 rows;
if (y * 4 + 3 >= textureData.Height)
rows = textureData.Height - y * 4;
else
rows = 4;
for (int32 x = 0; x < blocksWidth; x++)
const auto& srcItem = textureData.Items[arrayIndex];
auto& dstItem = decompressed.Items[arrayIndex];
dstItem.Mips.Resize(srcItem.Mips.Count());
for (int32 mipIndex = 0; mipIndex < srcItem.Mips.Count(); mipIndex++)
{
const byte* block = blocksBytes + y * blocksData->RowPitch + x * blockSize;
if (!detexDecompressBlockFunc(block, DETEX_MODE_MASK_ALL, 0, blockBuffer))
memset(blockBuffer, 0, DETEX_MAX_BLOCK_SIZE);
uint8* pixels = decompressedBytes + y * 4 * textureData.Width * pixelSize + x * 4 * pixelSize;
int32 columns;
if (x * 4 + 3 >= textureData.Width)
columns = textureData.Width - x * 4;
else
columns = 4;
for (int32 row = 0; row < rows; row++)
memcpy(pixels + row * textureData.Width * pixelSize, blockBuffer + row * 4 * pixelSize, columns * pixelSize);
const auto& srcMip = srcItem.Mips[mipIndex];
auto& dstMip = dstItem.Mips[mipIndex];
int mipWidth = Math::Max(1, textureData.Width >> mipIndex);
int mipHeight = Math::Max(1, textureData.Height >> mipIndex);
int blocksWidth = (mipWidth + 3) / 4;
int blocksHeight = (mipHeight + 3) / 4;
dstMip.RowPitch = mipWidth * pixelSize;
dstMip.Lines = mipHeight;
dstMip.DepthPitch = dstMip.RowPitch * mipHeight;
dstMip.Data.Allocate(dstMip.DepthPitch);
byte* decompressedBytes = dstMip.Data.Get();
const byte* blocksBytes = srcMip.Data.Get();
// Descompress block by block
for (int32 by = 0; by < blocksHeight; by++)
{
int32 rows = (by * 4 + 3 >= mipHeight) ? (mipHeight - by * 4) : 4;
for (int32 bx = 0; bx < blocksWidth; bx++)
{
int32 columns = (bx * 4 + 3 >= mipWidth) ? (mipWidth - bx * 4) : 4;
const byte* block = blocksBytes + by * srcMip.RowPitch + bx * blockSize;
if (!detexDecompressBlockFunc(block, DETEX_MODE_MASK_ALL, 0, blockBuffer))
memset(blockBuffer, 0, DETEX_MAX_BLOCK_SIZE);
uint8* pixels = decompressedBytes + (by * 4) * mipWidth * pixelSize + (bx * 4) * pixelSize;
for (int32 row = 0; row < rows; row++)
memcpy(pixels + row * mipWidth * pixelSize,blockBuffer + row * 4 * pixelSize,
columns * pixelSize);
}
}
}
}
@@ -375,9 +393,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
}
}
#endif
stbi_image_free(stbData);
break;
}
case ImageType::RAW:
@@ -447,9 +463,96 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
break;
}
case ImageType::DDS:
{
#if USE_EDITOR
// Load image as stream of bytes
const unsigned char* ddsData = static_cast<const unsigned char*>(fileData.Get());
// Decode header and get pointer to initial data
ddspp::Descriptor desc;
if (ddspp::decode_header(ddsData, desc) != ddspp::Success)
{
MessageBox::Show(TEXT("Failed to decode DDS file."), TEXT("Import warning"),
MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "Failed to decode DDS file.");
return true;
}
const unsigned char* initialData = ddsData + desc.headerSize;
// Setup texture data
textureData.Width = desc.width;
textureData.Height = desc.height;
textureData.Depth = desc.depth;
switch(desc.format)
{
case ddspp::BC1_UNORM:
textureData.Format = PixelFormat::BC1_UNorm;
break;
case ddspp::BC1_UNORM_SRGB:
textureData.Format = PixelFormat::BC1_UNorm_sRGB;
break;
case ddspp::BC2_UNORM:
textureData.Format = PixelFormat::BC2_UNorm;
break;
case ddspp::BC2_UNORM_SRGB:
textureData.Format = PixelFormat::BC2_UNorm_sRGB;
break;
case ddspp::BC3_UNORM:
textureData.Format = PixelFormat::BC3_UNorm;
break;
case ddspp::BC3_UNORM_SRGB:
textureData.Format = PixelFormat::BC3_UNorm_sRGB;
break;
case ddspp::BC4_UNORM:
textureData.Format = PixelFormat::BC4_UNorm;
break;
case ddspp::BC5_UNORM:
textureData.Format = PixelFormat::BC5_UNorm;
break;
case ddspp::BC7_UNORM:
textureData.Format = PixelFormat::BC7_UNorm;
break;
case ddspp::BC7_UNORM_SRGB:
textureData.Format = PixelFormat::BC7_UNorm_sRGB;
break;
case ddspp::R8G8B8A8_UNORM:
textureData.Format = PixelFormat::R8G8B8A8_UNorm;
break;
case ddspp::R8G8B8A8_UNORM_SRGB:
textureData.Format = PixelFormat::R8G8B8A8_UNorm_sRGB;
break;
default:
LOG(Warning, "Unsupported DDS format.");
return true;
}
textureData.Items.Resize(desc.arraySize);
for (int32 arrayIndex = 0; arrayIndex < (int32)desc.arraySize; arrayIndex++)
{
auto& item = textureData.Items[arrayIndex];
item.Mips.Resize(desc.numMips);
for (int32 mipIndex = 0; mipIndex < (int32)desc.numMips; mipIndex++)
{
auto& mip = item.Mips[mipIndex];
mip.RowPitch = ddspp::get_row_pitch(desc, mipIndex);
mip.Lines = ddspp::get_height_pixels_blocks(desc, mipIndex);
mip.DepthPitch = mip.RowPitch * mip.Lines;
int32 offset = ddspp::get_offset(desc, mipIndex, arrayIndex);
mip.Data.Copy(initialData + offset, mip.DepthPitch);
}
}
#else
MessageBox::Show(TEXT("DDS format is not supported."), TEXT("Import warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "DDS format is not supported.");
break;
#endif
break;
}
case ImageType::TIFF:
MessageBox::Show(TEXT("TIFF format is not supported."), TEXT("Import warning"), MessageBoxButtons::OK, MessageBoxIcon::Warning);
LOG(Warning, "TIFF format is not supported.");
@@ -531,8 +634,9 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
// Check mip levels
int32 sourceMipLevels = textureDataSrc->GetMipLevels();
bool hasSourceMipLevels = isPowerOfTwo && sourceMipLevels > 1;
bool useMipLevels = isPowerOfTwo && (options.GenerateMipMaps || hasSourceMipLevels) && (width > 1 || height > 1);
int32 arraySize = (int32)textureDataSrc->GetArraySize();
bool useMipLevels = isPowerOfTwo && (options.GenerateMipMaps ||
hasSourceMipLevels) && (width > 1 || height > 1);
int32 mipLevels = MipLevelsCount(width, height, useMipLevels);
if (useMipLevels && !options.GenerateMipMaps && mipLevels != sourceMipLevels)
{
@@ -540,12 +644,28 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
return true;
}
// Decompress if texture is compressed (next steps need decompressed input data, for eg. mip maps generation or format changing)
if (PixelFormatExtensions::IsCompressed(textureDataSrc->Format))
bool keepAsIs = false;
if (//!options.FlipY && !options.FlipX &&
//!options.InvertGreenChannel &&
//!options.InvertRedChannel &&
//!options.InvertAlphaChannel &&
//!options.InvertBlueChannel &&
//!options.ReconstructZChannel &&
//options.Compress &&
type == ImageType::DDS &&
//mipLevels == sourceMipLevels &&
PixelFormatExtensions::IsCompressed(textureDataSrc->Format) &&
//!PixelFormatExtensions::IsSRGB(textureDataSrc->Format) &&
width >= 4 && height >= 4)
{
// TODO: implement texture decompression
errorMsg = String::Format(TEXT("Imported texture used compressed format {0}. Not supported for importing on this platform.."), (int32)textureDataSrc->Format);
return true;
// Keep image in the current compressed format (artist choice) so we don't have to run the slow mipmap generation
keepAsIs = true;
}
// Decompress if texture is compressed (next steps need decompressed input data, for eg. mip maps generation or format changing)
if (!keepAsIs && PixelFormatExtensions::IsCompressed(textureDataSrc->Format))
{
//stbDecompress(*textureDataSrc, *textureDataDst);
}
if (options.FlipX)
@@ -565,6 +685,7 @@ bool TextureTool::ImportTextureStb(ImageType type, const StringView& path, Textu
}
// Generate mip maps chain
int32 arraySize = (int32)textureDataSrc->GetArraySize();
if (useMipLevels && options.GenerateMipMaps)
{
for (int32 arrayIndex = 0; arrayIndex < arraySize; arrayIndex++)