From d6e37ec4aab23618923a1fa100c9414ed34882c8 Mon Sep 17 00:00:00 2001 From: Alex Szpakowski Date: Tue, 17 Sep 2019 22:40:11 -0300 Subject: [PATCH] Add support for loading DDS files that contain raw/uncompressed pixel data. Resolves issue #1097. --- src/libraries/ddsparse/ddsinfo.h | 5 +- src/libraries/ddsparse/ddsparse.cpp | 502 ++++++++++++++++++------ src/libraries/ddsparse/ddsparse.h | 46 +-- src/modules/image/magpie/ddsHandler.cpp | 215 +++++++--- src/modules/image/magpie/ddsHandler.h | 10 +- 5 files changed, 578 insertions(+), 200 deletions(-) diff --git a/src/libraries/ddsparse/ddsinfo.h b/src/libraries/ddsparse/ddsinfo.h index f653b31e2..b2935f185 100644 --- a/src/libraries/ddsparse/ddsinfo.h +++ b/src/libraries/ddsparse/ddsinfo.h @@ -1,7 +1,7 @@ /** * Simple DDS data parser for compressed 2D textures. * - * Copyright (c) 2013-2017 Alex Szpakowski + * Copyright (c) 2013-2019 Alex Szpakowski * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages @@ -41,7 +41,8 @@ enum DDPF DDPF_FOURCC = 0x000004, DDPF_RGB = 0x000040, DDPF_YUV = 0x000200, - DDPF_LUMINANCE = 0x020000 + DDPF_LUMINANCE = 0x020000, + DDPF_BUMPDUDV = 0x080000, }; enum D3D10ResourceDimension diff --git a/src/libraries/ddsparse/ddsparse.cpp b/src/libraries/ddsparse/ddsparse.cpp index 6a598886c..a14557119 100644 --- a/src/libraries/ddsparse/ddsparse.cpp +++ b/src/libraries/ddsparse/ddsparse.cpp @@ -1,7 +1,7 @@ /** * Simple DDS data parser for compressed 2D textures. * - * Copyright (c) 2013-2017 Alex Szpakowski + * Copyright (c) 2013-2019 Alex Szpakowski * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages @@ -33,107 +33,347 @@ using namespace dds::dxinfo; // Creates a packed uint representation of a FourCC code. #define MakeFourCC(a, b, c, d) ((uint32_t) (((d)<<24) | ((c)<<16) | ((b)<<8) | (a))) -// Translate the old DDS format to our own. -static Format parseDDSFormat(const DDSPixelFormat &fmt) +#define ISBITMASK(r,g,b,a) (ddpf.rBitMask == r && ddpf.gBitMask == g && ddpf.bBitMask == b && ddpf.aBitMask == a) + +// Function adapted from DirectXTex: +// https://github.com/microsoft/DirectXTex/blob/master/DDSTextureLoader/DDSTextureLoader.cpp#L623 +static DXGIFormat getDXGIFormat(const DDSPixelFormat& ddpf) { - if ((fmt.flags & DDPF_FOURCC) == 0) - return FORMAT_UNKNOWN; - - Format f = FORMAT_UNKNOWN; - - switch (fmt.fourCC) + if (ddpf.flags & DDPF_RGB) { - case MakeFourCC('D','X','T','1'): - f = FORMAT_DXT1; - break; - case MakeFourCC('D','X','T','3'): - f = FORMAT_DXT3; - break; - case MakeFourCC('D','X','T','5'): - f = FORMAT_DXT5; - break; - case MakeFourCC('A','T','I','1'): - case MakeFourCC('B','C','4','U'): - f = FORMAT_BC4; - break; - case MakeFourCC('B','C','4','S'): - f = FORMAT_BC4s; - break; - case MakeFourCC('A','T','I','2'): - case MakeFourCC('B','C','5','U'): - f = FORMAT_BC5; - break; - case MakeFourCC('B','C','5','S'): - f = FORMAT_BC5s; - break; - default: - break; + // Note that sRGB formats are written using the "DX10" extended header + + switch (ddpf.rgbBitCount) + { + case 32: + if (ISBITMASK(0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)) + return DXGI_FORMAT_R8G8B8A8_UNORM; + + if (ISBITMASK(0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000)) + return DXGI_FORMAT_B8G8R8A8_UNORM; + + if (ISBITMASK(0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000)) + return DXGI_FORMAT_B8G8R8X8_UNORM; + + // No DXGI format maps to ISBITMASK(0x000000ff,0x0000ff00,0x00ff0000,0x00000000) aka D3DFMT_X8B8G8R8 + + // Note that many common DDS reader/writers (including D3DX) swap the + // the RED/BLUE masks for 10:10:10:2 formats. We assume + // below that the 'backwards' header mask is being used since it is most + // likely written by D3DX. The more robust solution is to use the 'DX10' + // header extension and specify the DXGI_FORMAT_R10G10B10A2_UNORM format directly + + // For 'correct' writers, this should be 0x000003ff,0x000ffc00,0x3ff00000 for RGB data + if (ISBITMASK(0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000)) + return DXGI_FORMAT_R10G10B10A2_UNORM; + + // No DXGI format maps to ISBITMASK(0x000003ff,0x000ffc00,0x3ff00000,0xc0000000) aka D3DFMT_A2R10G10B10 + + if (ISBITMASK(0x0000ffff, 0xffff0000, 0x00000000, 0x00000000)) + return DXGI_FORMAT_R16G16_UNORM; + + if (ISBITMASK(0xffffffff, 0x00000000, 0x00000000, 0x00000000)) + // Only 32-bit color channel format in D3D9 was R32F + return DXGI_FORMAT_R32_FLOAT; // D3DX writes this out as a FourCC of 114 + break; + + case 24: + // No 24bpp DXGI formats aka D3DFMT_R8G8B8 + break; + + case 16: + if (ISBITMASK(0x7c00, 0x03e0, 0x001f, 0x8000)) + return DXGI_FORMAT_B5G5R5A1_UNORM; + + if (ISBITMASK(0xf800, 0x07e0, 0x001f, 0x0000)) + return DXGI_FORMAT_B5G6R5_UNORM; + + // No DXGI format maps to ISBITMASK(0x7c00,0x03e0,0x001f,0x0000) aka D3DFMT_X1R5G5B5 + + // No DXGI format maps to ISBITMASK(0x0f00,0x00f0,0x000f,0x0000) aka D3DFMT_X4R4G4B4 + + // No 3:3:2, 3:3:2:8, or paletted DXGI formats aka D3DFMT_A8R3G3B2, D3DFMT_R3G3B2, D3DFMT_P8, D3DFMT_A8P8, etc. + break; + } + } + else if (ddpf.flags & DDPF_LUMINANCE) + { + if (ddpf.rgbBitCount == 8) + { + if (ISBITMASK(0x000000ff, 0x00000000, 0x00000000, 0x00000000)) + return DXGI_FORMAT_R8_UNORM; // D3DX10/11 writes this out as DX10 extension + + // No DXGI format maps to ISBITMASK(0x0f,0x00,0x00,0xf0) aka D3DFMT_A4L4 + + if (ISBITMASK(0x000000ff, 0x00000000, 0x00000000, 0x0000ff00)) + return DXGI_FORMAT_R8G8_UNORM; // Some DDS writers assume the bitcount should be 8 instead of 16 + } + + if (ddpf.rgbBitCount == 16) + { + if (ISBITMASK(0x0000ffff, 0x00000000, 0x00000000, 0x00000000)) + return DXGI_FORMAT_R16_UNORM; // D3DX10/11 writes this out as DX10 extension + + if (ISBITMASK(0x000000ff, 0x00000000, 0x00000000, 0x0000ff00)) + return DXGI_FORMAT_R8G8_UNORM; // D3DX10/11 writes this out as DX10 extension + } + } + else if (ddpf.flags & DDPF_ALPHA) + { + if (ddpf.rgbBitCount == 8) + return DXGI_FORMAT_A8_UNORM; + } + else if (ddpf.flags & DDPF_BUMPDUDV) + { + if (ddpf.rgbBitCount == 16) + { + if (ISBITMASK(0x00ff, 0xff00, 0x0000, 0x0000)) + return DXGI_FORMAT_R8G8_SNORM; // D3DX10/11 writes this out as DX10 extension + } + + if (ddpf.rgbBitCount == 32) + { + if (ISBITMASK(0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)) + return DXGI_FORMAT_R8G8B8A8_SNORM; // D3DX10/11 writes this out as DX10 extension + + if (ISBITMASK(0x0000ffff, 0xffff0000, 0x00000000, 0x00000000)) + return DXGI_FORMAT_R16G16_SNORM; // D3DX10/11 writes this out as DX10 extension + + // No DXGI format maps to ISBITMASK(0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000) aka D3DFMT_A2W10V10U10 + } + } + else if (ddpf.flags & DDPF_FOURCC) + { + switch (ddpf.fourCC) + { + case MakeFourCC('D','X','T','1'): + return DXGI_FORMAT_BC1_UNORM; + + case MakeFourCC('D','X','T','3'): + return DXGI_FORMAT_BC2_UNORM; + + case MakeFourCC('D','X','T','5'): + return DXGI_FORMAT_BC3_UNORM; + + // While pre-multiplied alpha isn't directly supported by the DXGI formats, + // they are basically the same as these BC formats so they can be mapped + case MakeFourCC('D','X','T','2'): + return DXGI_FORMAT_BC2_UNORM; + + case MakeFourCC('D','X','T','4'): + return DXGI_FORMAT_BC3_UNORM; + + case MakeFourCC('A','T','I','1'): + return DXGI_FORMAT_BC4_UNORM; + + case MakeFourCC('B','C','4','U'): + return DXGI_FORMAT_BC4_UNORM; + + case MakeFourCC('B','C','4','S'): + return DXGI_FORMAT_BC4_SNORM; + + case MakeFourCC('A','T','I','2'): + return DXGI_FORMAT_BC5_UNORM; + + case MakeFourCC('B','C','5','U'): + return DXGI_FORMAT_BC5_UNORM; + + case MakeFourCC('B','C','5','S'): + return DXGI_FORMAT_BC5_SNORM; + + // BC6H and BC7 are written using the "DX10" extended header + + case MakeFourCC('R','G','B','G'): + return DXGI_FORMAT_R8G8_B8G8_UNORM; + + case MakeFourCC('G','R','G','B'): + return DXGI_FORMAT_G8R8_G8B8_UNORM; + + // Check for D3DFORMAT enums being set here + case 36: // D3DFMT_A16B16G16R16 + return DXGI_FORMAT_R16G16B16A16_UNORM; + + case 110: // D3DFMT_Q16W16V16U16 + return DXGI_FORMAT_R16G16B16A16_SNORM; + + case 111: // D3DFMT_R16F + return DXGI_FORMAT_R16_FLOAT; + + case 112: // D3DFMT_G16R16F + return DXGI_FORMAT_R16G16_FLOAT; + + case 113: // D3DFMT_A16B16G16R16F + return DXGI_FORMAT_R16G16B16A16_FLOAT; + + case 114: // D3DFMT_R32F + return DXGI_FORMAT_R32_FLOAT; + + case 115: // D3DFMT_G32R32F + return DXGI_FORMAT_R32G32_FLOAT; + + case 116: // D3DFMT_A32B32G32R32F + return DXGI_FORMAT_R32G32B32A32_FLOAT; + } } - return f; + return DXGI_FORMAT_UNKNOWN; } -// Translate the new DX10 formats to our own. -static Format parseDX10Format(DXGIFormat fmt) +static size_t getBitsPerPixel(DXGIFormat fmt) { - Format f = FORMAT_UNKNOWN; + switch (fmt) + { + case DXGI_FORMAT_R32G32B32A32_TYPELESS: + case DXGI_FORMAT_R32G32B32A32_FLOAT: + case DXGI_FORMAT_R32G32B32A32_UINT: + case DXGI_FORMAT_R32G32B32A32_SINT: + return 128; + case DXGI_FORMAT_R32G32B32_TYPELESS: + case DXGI_FORMAT_R32G32B32_FLOAT: + case DXGI_FORMAT_R32G32B32_UINT: + case DXGI_FORMAT_R32G32B32_SINT: + return 96; + + case DXGI_FORMAT_R16G16B16A16_TYPELESS: + case DXGI_FORMAT_R16G16B16A16_FLOAT: + case DXGI_FORMAT_R16G16B16A16_UNORM: + case DXGI_FORMAT_R16G16B16A16_UINT: + case DXGI_FORMAT_R16G16B16A16_SNORM: + case DXGI_FORMAT_R16G16B16A16_SINT: + case DXGI_FORMAT_R32G32_TYPELESS: + case DXGI_FORMAT_R32G32_FLOAT: + case DXGI_FORMAT_R32G32_UINT: + case DXGI_FORMAT_R32G32_SINT: + case DXGI_FORMAT_R32G8X24_TYPELESS: + case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: + case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: + case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT: + return 64; + + case DXGI_FORMAT_R10G10B10A2_TYPELESS: + case DXGI_FORMAT_R10G10B10A2_UNORM: + case DXGI_FORMAT_R10G10B10A2_UINT: + case DXGI_FORMAT_R11G11B10_FLOAT: + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + case DXGI_FORMAT_R8G8B8A8_UINT: + case DXGI_FORMAT_R8G8B8A8_SNORM: + case DXGI_FORMAT_R8G8B8A8_SINT: + case DXGI_FORMAT_R16G16_TYPELESS: + case DXGI_FORMAT_R16G16_FLOAT: + case DXGI_FORMAT_R16G16_UNORM: + case DXGI_FORMAT_R16G16_UINT: + case DXGI_FORMAT_R16G16_SNORM: + case DXGI_FORMAT_R16G16_SINT: + case DXGI_FORMAT_R32_TYPELESS: + case DXGI_FORMAT_D32_FLOAT: + case DXGI_FORMAT_R32_FLOAT: + case DXGI_FORMAT_R32_UINT: + case DXGI_FORMAT_R32_SINT: + case DXGI_FORMAT_R24G8_TYPELESS: + case DXGI_FORMAT_D24_UNORM_S8_UINT: + case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: + case DXGI_FORMAT_X24_TYPELESS_G8_UINT: + case DXGI_FORMAT_R9G9B9E5_SHAREDEXP: + case DXGI_FORMAT_R8G8_B8G8_UNORM: + case DXGI_FORMAT_G8R8_G8B8_UNORM: + case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8X8_UNORM: + case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM: + case DXGI_FORMAT_B8G8R8A8_TYPELESS: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + case DXGI_FORMAT_B8G8R8X8_TYPELESS: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + return 32; + + case DXGI_FORMAT_R8G8_TYPELESS: + case DXGI_FORMAT_R8G8_UNORM: + case DXGI_FORMAT_R8G8_UINT: + case DXGI_FORMAT_R8G8_SNORM: + case DXGI_FORMAT_R8G8_SINT: + case DXGI_FORMAT_R16_TYPELESS: + case DXGI_FORMAT_R16_FLOAT: + case DXGI_FORMAT_D16_UNORM: + case DXGI_FORMAT_R16_UNORM: + case DXGI_FORMAT_R16_UINT: + case DXGI_FORMAT_R16_SNORM: + case DXGI_FORMAT_R16_SINT: + case DXGI_FORMAT_B5G6R5_UNORM: + case DXGI_FORMAT_B5G5R5A1_UNORM: + return 16; + + case DXGI_FORMAT_R8_TYPELESS: + case DXGI_FORMAT_R8_UNORM: + case DXGI_FORMAT_R8_UINT: + case DXGI_FORMAT_R8_SNORM: + case DXGI_FORMAT_R8_SINT: + case DXGI_FORMAT_A8_UNORM: + return 8; + + case DXGI_FORMAT_R1_UNORM: + return 1; + + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + return 4; + + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + return 8; + + default: + return 0; + } +} + +static bool isBlockCompressed(DXGIFormat fmt) +{ switch (fmt) { case DXGI_FORMAT_BC1_TYPELESS: case DXGI_FORMAT_BC1_UNORM: - f = FORMAT_DXT1; - break; case DXGI_FORMAT_BC1_UNORM_SRGB: - f = FORMAT_DXT1srgb; - break; - case DXGI_FORMAT_BC2_TYPELESS: - case DXGI_FORMAT_BC2_UNORM: - f = FORMAT_DXT3; - break; - case DXGI_FORMAT_BC2_UNORM_SRGB: - f = FORMAT_DXT3srgb; - break; - case DXGI_FORMAT_BC3_TYPELESS: - case DXGI_FORMAT_BC3_UNORM: - f = FORMAT_DXT5; - break; - case DXGI_FORMAT_BC3_UNORM_SRGB: - f = FORMAT_DXT5srgb; - break; case DXGI_FORMAT_BC4_TYPELESS: case DXGI_FORMAT_BC4_UNORM: - f = FORMAT_BC4; - break; case DXGI_FORMAT_BC4_SNORM: - f = FORMAT_BC4s; - break; + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: case DXGI_FORMAT_BC5_TYPELESS: case DXGI_FORMAT_BC5_UNORM: - f = FORMAT_BC5; - break; case DXGI_FORMAT_BC5_SNORM: - f = FORMAT_BC5s; - break; case DXGI_FORMAT_BC6H_TYPELESS: case DXGI_FORMAT_BC6H_UF16: - f = FORMAT_BC6H; - break; case DXGI_FORMAT_BC6H_SF16: - f = FORMAT_BC6Hs; - break; case DXGI_FORMAT_BC7_TYPELESS: case DXGI_FORMAT_BC7_UNORM: - f = FORMAT_BC7; - break; case DXGI_FORMAT_BC7_UNORM_SRGB: - f = FORMAT_BC7srgb; - break; + return true; default: - break; + return false; } - - return f; } bool isDDS(const void *data, size_t dataSize) @@ -170,10 +410,10 @@ bool isDDS(const void *data, size_t dataSize) return true; } -bool isCompressedDDS(const void *data, size_t dataSize) +DXGIFormat getDDSPixelFormat(const void *data, size_t dataSize) { if (!isDDS(data, dataSize)) - return false; + return DXGI_FORMAT_UNKNOWN; const uint8_t *readData = (const uint8_t *) data; ptrdiff_t offset = sizeof(uint32_t); @@ -185,14 +425,20 @@ bool isCompressedDDS(const void *data, size_t dataSize) if ((header->format.flags & DDPF_FOURCC) && (header->format.fourCC == MakeFourCC('D','X','1','0'))) { DDSHeader10 *header10 = (DDSHeader10 *) &readData[offset]; - return parseDX10Format(header10->dxgiFormat) != FORMAT_UNKNOWN; + return header10->dxgiFormat; } - return parseDDSFormat(header->format) != FORMAT_UNKNOWN; + return getDXGIFormat(header->format); +} + +bool isCompressedDDS(const void *data, size_t dataSize) +{ + DXGIFormat format = getDDSPixelFormat(data, dataSize); + return format != DXGI_FORMAT_UNKNOWN && isBlockCompressed(format); } Parser::Parser(const void *data, size_t dataSize) - : format(FORMAT_UNKNOWN) + : format(DXGI_FORMAT_UNKNOWN) { parseData(data, dataSize); } @@ -204,7 +450,7 @@ Parser::Parser(const Parser &other) } Parser::Parser() - : format(FORMAT_UNKNOWN) + : format(DXGI_FORMAT_UNKNOWN) { } @@ -220,7 +466,7 @@ Parser::~Parser() { } -Format Parser::getFormat() const +DXGIFormat Parser::getFormat() const { return format; } @@ -238,45 +484,78 @@ size_t Parser::getMipmapCount() const return texData.size(); } -size_t Parser::parseImageSize(Format fmt, int width, int height) const +size_t Parser::parseImageSize(DXGIFormat fmt, int width, int height) const { - size_t numBlocksWide = 0; - size_t numBlocksHigh = 0; - size_t numBytesPerBlock = 0; + size_t bytes = 0; + size_t bytesPerBlock = 0; + + bool packed = false; + bool blockCompressed = false; switch (fmt) { - case FORMAT_DXT1: - case FORMAT_DXT1srgb: - case FORMAT_BC4: - case FORMAT_BC4s: - numBytesPerBlock = 8; + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + blockCompressed = true; + bytesPerBlock = 8; break; - case FORMAT_DXT3: - case FORMAT_DXT3srgb: - case FORMAT_DXT5: - case FORMAT_DXT5srgb: - case FORMAT_BC5s: - case FORMAT_BC5: - case FORMAT_BC6H: - case FORMAT_BC7: - case FORMAT_BC7srgb: - numBytesPerBlock = 16; + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + blockCompressed = true; + bytesPerBlock = 16; + break; + case DXGI_FORMAT_R8G8_B8G8_UNORM: + case DXGI_FORMAT_G8R8_G8B8_UNORM: + packed = true; + bytesPerBlock = 4; break; default: break; } - if (width > 0) - numBlocksWide = std::max(1, (width + 3) / 4); + if (packed) + { + size_t rowBytes = (((size_t) width + 1u) >> 1) * bytesPerBlock; + bytes = rowBytes * height; + } + else if (blockCompressed) + { + size_t numBlocksWide = width > 0 ? std::max(1, (width + 3) / 4) : 0; + size_t numBlocksHigh = height > 0 ? std::max(1, (height + 3) / 4) : 0; + bytes = numBlocksWide * bytesPerBlock * numBlocksHigh; + } + else + { + size_t bpp = getBitsPerPixel(fmt); + if (bpp == 0) + return 0; - if (height > 0) - numBlocksHigh = std::max(1, (height + 3) / 4); + // Round up to the nearest byte. + size_t rowBytes = ((size_t) width * bpp + 7u) / 8u; + bytes = rowBytes * height; + } - return numBlocksWide * numBytesPerBlock * numBlocksHigh; + return bytes; } -bool Parser::parseTexData(const uint8_t *data, size_t dataSize, Format fmt, int w, int h, int mips) +bool Parser::parseTexData(const uint8_t *data, size_t dataSize, DXGIFormat fmt, int w, int h, int mips) { size_t offset = 0; std::vector newTexData; @@ -321,7 +600,6 @@ bool Parser::parseData(const void *data, size_t dataSize) DDSHeader *header = (DDSHeader *) &readData[offset]; offset += sizeof(DDSHeader); - // Check for DX10 extension. if ((header->format.flags & DDPF_FOURCC) && (header->format.fourCC == MakeFourCC('D','X','1','0'))) { @@ -342,16 +620,14 @@ bool Parser::parseData(const void *data, size_t dataSize) if (header10->arraySize > 1) return false; - format = parseDX10Format(header10->dxgiFormat); + format = header10->dxgiFormat; } else - format = parseDDSFormat(header->format); + format = getDXGIFormat(header->format); - - if (format == FORMAT_UNKNOWN) + if (format == DXGI_FORMAT_UNKNOWN) return false; - int w = header->width; int h = header->height; diff --git a/src/libraries/ddsparse/ddsparse.h b/src/libraries/ddsparse/ddsparse.h index 0becef74a..fe62d291c 100644 --- a/src/libraries/ddsparse/ddsparse.h +++ b/src/libraries/ddsparse/ddsparse.h @@ -1,7 +1,7 @@ /** * Simple DDS data parser for compressed 2D textures. * - * Copyright (c) 2013-2017 Alex Szpakowski + * Copyright (c) 2013-2019 Alex Szpakowski * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages @@ -28,40 +28,18 @@ #include +#include "ddsinfo.h" + namespace dds { -// Supported DDS formats. -// Formats with an 's' suffix have signed data. -enum Format -{ - FORMAT_DXT1, - FORMAT_DXT1srgb, - FORMAT_DXT3, - FORMAT_DXT3srgb, - FORMAT_DXT5, - FORMAT_DXT5srgb, - FORMAT_BC4, - FORMAT_BC4s, - FORMAT_BC5, - FORMAT_BC5s, - FORMAT_BC6H, - FORMAT_BC6Hs, - FORMAT_BC7, - FORMAT_BC7srgb, // sRGB color space. - FORMAT_UNKNOWN -}; - // Represents a single mipmap level of a texture. struct Image { - int width; - int height; - size_t dataSize; - const uint8_t *data; - - Image() : width(0), height(0), dataSize(0), data(0) - {} + int width = 0; + int height = 0; + size_t dataSize = 0; + const uint8_t *data = nullptr; }; /** @@ -82,6 +60,8 @@ bool isDDS(const void *data, size_t dataSize); **/ bool isCompressedDDS(const void *data, size_t dataSize); +dxinfo::DXGIFormat getDDSPixelFormat(const void *data, size_t dataSize); + class Parser { public: @@ -104,7 +84,7 @@ public: /** * Gets the format of this texture. **/ - Format getFormat() const; + dxinfo::DXGIFormat getFormat() const; /** * Gets the data of this texture at a mipmap level. Mipmap level 0 @@ -124,12 +104,12 @@ public: private: - size_t parseImageSize(Format fmt, int width, int height) const; - bool parseTexData(const uint8_t *data, size_t dataSize, Format fmt, int w, int h, int mips); + size_t parseImageSize(dxinfo::DXGIFormat fmt, int width, int height) const; + bool parseTexData(const uint8_t *data, size_t dataSize, dxinfo::DXGIFormat fmt, int w, int h, int mips); bool parseData(const void *data, size_t dataSize); std::vector texData; - Format format; + dxinfo::DXGIFormat format; }; // Parser diff --git a/src/modules/image/magpie/ddsHandler.cpp b/src/modules/image/magpie/ddsHandler.cpp index ee7bfe6a1..777942596 100644 --- a/src/modules/image/magpie/ddsHandler.cpp +++ b/src/modules/image/magpie/ddsHandler.cpp @@ -20,6 +20,10 @@ #include "ddsHandler.h" #include "common/Exception.h" +#include "image/ImageData.h" + +// dds parser +#include "ddsparse/ddsparse.h" namespace love { @@ -28,6 +32,172 @@ namespace image namespace magpie { +static PixelFormat convertFormat(dds::dxinfo::DXGIFormat dxformat, bool &sRGB) +{ + using namespace dds::dxinfo; + + sRGB = false; + + switch (dxformat) + { + case DXGI_FORMAT_R32G32B32A32_TYPELESS: + case DXGI_FORMAT_R32G32B32A32_FLOAT: + return PIXELFORMAT_RGBA32F; + + case DXGI_FORMAT_R16G16B16A16_TYPELESS: + case DXGI_FORMAT_R16G16B16A16_FLOAT: + return PIXELFORMAT_RGBA16F; + + case DXGI_FORMAT_R16G16B16A16_UNORM: + return PIXELFORMAT_RGBA16; + + case DXGI_FORMAT_R32G32_TYPELESS: + case DXGI_FORMAT_R32G32_FLOAT: + return PIXELFORMAT_RG32F; + + case DXGI_FORMAT_R10G10B10A2_TYPELESS: + case DXGI_FORMAT_R10G10B10A2_UNORM: + return PIXELFORMAT_RGB10A2; + + case DXGI_FORMAT_R11G11B10_FLOAT: + return PIXELFORMAT_RG11B10F; + + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + sRGB = (dxformat == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB); + return PIXELFORMAT_RGBA8; + + case DXGI_FORMAT_R16G16_TYPELESS: + case DXGI_FORMAT_R16G16_FLOAT: + return PIXELFORMAT_RG16F; + + case DXGI_FORMAT_R16G16_UNORM: + return PIXELFORMAT_RG16; + + case DXGI_FORMAT_R32_TYPELESS: + case DXGI_FORMAT_R32_FLOAT: + return PIXELFORMAT_R32F; + + case DXGI_FORMAT_R8G8_TYPELESS: + case DXGI_FORMAT_R8G8_UNORM: + return PIXELFORMAT_RG8; + + case DXGI_FORMAT_R16_TYPELESS: + case DXGI_FORMAT_R16_FLOAT: + return PIXELFORMAT_R16F; + + case DXGI_FORMAT_R16_UNORM: + return PIXELFORMAT_R16; + + case DXGI_FORMAT_R8_TYPELESS: + case DXGI_FORMAT_R8_UNORM: + case DXGI_FORMAT_A8_UNORM: + return PIXELFORMAT_R8; + + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + sRGB = (dxformat == DXGI_FORMAT_BC1_UNORM_SRGB); + return PIXELFORMAT_DXT1; + + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + sRGB = (dxformat == DXGI_FORMAT_BC2_UNORM_SRGB); + return PIXELFORMAT_DXT3; + + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + sRGB = (dxformat == DXGI_FORMAT_BC3_UNORM_SRGB); + return PIXELFORMAT_DXT5; + + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + return PIXELFORMAT_BC4; + + case DXGI_FORMAT_BC4_SNORM: + return PIXELFORMAT_BC4s; + + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + return PIXELFORMAT_BC5; + + case DXGI_FORMAT_BC5_SNORM: + return PIXELFORMAT_BC5s; + + case DXGI_FORMAT_B5G6R5_UNORM: + return PIXELFORMAT_RGB565; + + case DXGI_FORMAT_B5G5R5A1_UNORM: + return PIXELFORMAT_RGB5A1; + + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + return PIXELFORMAT_BC6H; + + case DXGI_FORMAT_BC6H_SF16: + return PIXELFORMAT_BC6Hs; + + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + sRGB = (dxformat == DXGI_FORMAT_BC7_UNORM_SRGB); + return PIXELFORMAT_BC7; + + default: + return PIXELFORMAT_UNKNOWN; + } +} + +bool DDSHandler::canDecode(Data *data) +{ + using namespace dds::dxinfo; + + DXGIFormat dxformat = dds::getDDSPixelFormat(data->getData(), data->getSize()); + bool isSRGB = false; + PixelFormat format = convertFormat(dxformat, isSRGB); + + return ImageData::validPixelFormat(format); +} + +FormatHandler::DecodedImage DDSHandler::decode(Data *data) +{ + DecodedImage img; + + dds::Parser parser(data->getData(), data->getSize()); + + bool isSRGB = false; + img.format = convertFormat(parser.getFormat(), isSRGB); + + if (!ImageData::validPixelFormat(img.format)) + throw love::Exception("Could not parse DDS pixel data: Unsupported format."); + + if (parser.getMipmapCount() == 0) + throw love::Exception("Could not parse DDS pixel data: No readable texture data."); + + // We only support the top mip level through this codepath. + const dds::Image *ddsimg = parser.getImageData(0); + + try + { + img.data = new uint8[ddsimg->dataSize]; + } + catch (std::exception &) + { + throw love::Exception("Out of memory."); + } + + memcpy(img.data, ddsimg->data, ddsimg->dataSize); + + img.size = ddsimg->dataSize; + img.width = ddsimg->width; + img.height = ddsimg->height; + + return img; +} + bool DDSHandler::canParseCompressed(Data *data) { return dds::isCompressedDDS(data->getData(), data->getSize()); @@ -35,7 +205,7 @@ bool DDSHandler::canParseCompressed(Data *data) StrongRef DDSHandler::parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) { - if (!dds::isDDS(filedata->getData(), filedata->getSize())) + if (!dds::isCompressedDDS(filedata->getData(), filedata->getSize())) throw love::Exception("Could not decode compressed data (not a DDS file?)"); PixelFormat texformat = PIXELFORMAT_UNKNOWN; @@ -88,49 +258,6 @@ StrongRef DDSHandler::parseCompressed(Data *filedata, std::vec return memory; } -PixelFormat DDSHandler::convertFormat(dds::Format ddsformat, bool &sRGB) -{ - sRGB = false; - - switch (ddsformat) - { - case dds::FORMAT_DXT1: - return PIXELFORMAT_DXT1; - case dds::FORMAT_DXT1srgb: - sRGB = true; - return PIXELFORMAT_DXT1; - case dds::FORMAT_DXT3: - return PIXELFORMAT_DXT3; - case dds::FORMAT_DXT3srgb: - sRGB = true; - return PIXELFORMAT_DXT3; - case dds::FORMAT_DXT5: - return PIXELFORMAT_DXT5; - case dds::FORMAT_DXT5srgb: - sRGB = true; - return PIXELFORMAT_DXT5; - case dds::FORMAT_BC4: - return PIXELFORMAT_BC4; - case dds::FORMAT_BC4s: - return PIXELFORMAT_BC4s; - case dds::FORMAT_BC5: - return PIXELFORMAT_BC5; - case dds::FORMAT_BC5s: - return PIXELFORMAT_BC5s; - case dds::FORMAT_BC6H: - return PIXELFORMAT_BC6H; - case dds::FORMAT_BC6Hs: - return PIXELFORMAT_BC6Hs; - case dds::FORMAT_BC7: - return PIXELFORMAT_BC7; - case dds::FORMAT_BC7srgb: - sRGB = true; - return PIXELFORMAT_BC7; - default: - return PIXELFORMAT_UNKNOWN; - } -} - } // magpie } // image } // love diff --git a/src/modules/image/magpie/ddsHandler.h b/src/modules/image/magpie/ddsHandler.h index c795abaf5..846992017 100644 --- a/src/modules/image/magpie/ddsHandler.h +++ b/src/modules/image/magpie/ddsHandler.h @@ -23,9 +23,6 @@ // LOVE #include "image/FormatHandler.h" -// dds parser -#include "ddsparse/ddsparse.h" - // STL #include @@ -46,16 +43,13 @@ public: virtual ~DDSHandler() {} // Implements FormatHandler. + bool canDecode(Data *data) override; + DecodedImage decode(Data *data) override; bool canParseCompressed(Data *data) override; - StrongRef parseCompressed(Data *filedata, std::vector> &images, PixelFormat &format, bool &sRGB) override; -private: - - static PixelFormat convertFormat(dds::Format ddsformat, bool &sRGB); - }; // DDSHandler } // magpie