//------------------------------------------------------------------------------------- // DirectXTexDDS.cpp // // DirectX Texture Library - Microsoft DirectDraw Surface (DDS) file format reader/writer // // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. // // http://go.microsoft.com/fwlink/?LinkId=248926 //------------------------------------------------------------------------------------- #include "directxtexp.h" #include "dds.h" using namespace DirectX; static_assert(static_cast(TEX_DIMENSION_TEXTURE1D) == static_cast(DDS_DIMENSION_TEXTURE1D), "header enum mismatch"); static_assert(static_cast(TEX_DIMENSION_TEXTURE2D) == static_cast(DDS_DIMENSION_TEXTURE2D), "header enum mismatch"); static_assert(static_cast(TEX_DIMENSION_TEXTURE3D) == static_cast(DDS_DIMENSION_TEXTURE3D), "header enum mismatch"); namespace { //------------------------------------------------------------------------------------- // Legacy format mapping table (used for DDS files without 'DX10' extended header) //------------------------------------------------------------------------------------- enum CONVERSION_FLAGS { CONV_FLAGS_NONE = 0x0, CONV_FLAGS_EXPAND = 0x1, // Conversion requires expanded pixel size CONV_FLAGS_NOALPHA = 0x2, // Conversion requires setting alpha to known value CONV_FLAGS_SWIZZLE = 0x4, // BGR/RGB order swizzling required CONV_FLAGS_PAL8 = 0x8, // Has an 8-bit palette CONV_FLAGS_888 = 0x10, // Source is an 8:8:8 (24bpp) format CONV_FLAGS_565 = 0x20, // Source is a 5:6:5 (16bpp) format CONV_FLAGS_5551 = 0x40, // Source is a 5:5:5:1 (16bpp) format CONV_FLAGS_4444 = 0x80, // Source is a 4:4:4:4 (16bpp) format CONV_FLAGS_44 = 0x100, // Source is a 4:4 (8bpp) format CONV_FLAGS_332 = 0x200, // Source is a 3:3:2 (8bpp) format CONV_FLAGS_8332 = 0x400, // Source is a 8:3:3:2 (16bpp) format CONV_FLAGS_A8P8 = 0x800, // Has an 8-bit palette with an alpha channel CONV_FLAGS_DX10 = 0x10000, // Has the 'DX10' extension header CONV_FLAGS_PMALPHA = 0x20000, // Contains premultiplied alpha data CONV_FLAGS_L8 = 0x40000, // Source is a 8 luminance format CONV_FLAGS_L16 = 0x80000, // Source is a 16 luminance format CONV_FLAGS_A8L8 = 0x100000, // Source is a 8:8 luminance format }; struct LegacyDDS { DXGI_FORMAT format; DWORD convFlags; DDS_PIXELFORMAT ddpf; }; const LegacyDDS g_LegacyDDSMap[] = { { DXGI_FORMAT_BC1_UNORM, CONV_FLAGS_NONE, DDSPF_DXT1 }, // D3DFMT_DXT1 { DXGI_FORMAT_BC2_UNORM, CONV_FLAGS_NONE, DDSPF_DXT3 }, // D3DFMT_DXT3 { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, DDSPF_DXT5 }, // D3DFMT_DXT5 { DXGI_FORMAT_BC2_UNORM, CONV_FLAGS_PMALPHA, DDSPF_DXT2 }, // D3DFMT_DXT2 { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_PMALPHA, DDSPF_DXT4 }, // D3DFMT_DXT4 { DXGI_FORMAT_BC4_UNORM, CONV_FLAGS_NONE, DDSPF_BC4_UNORM }, { DXGI_FORMAT_BC4_SNORM, CONV_FLAGS_NONE, DDSPF_BC4_SNORM }, { DXGI_FORMAT_BC5_UNORM, CONV_FLAGS_NONE, DDSPF_BC5_UNORM }, { DXGI_FORMAT_BC5_SNORM, CONV_FLAGS_NONE, DDSPF_BC5_SNORM }, { DXGI_FORMAT_BC4_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('A', 'T', 'I', '1'), 0, 0, 0, 0, 0 } }, { DXGI_FORMAT_BC5_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('A', 'T', 'I', '2'), 0, 0, 0, 0, 0 } }, { DXGI_FORMAT_R8G8_B8G8_UNORM, CONV_FLAGS_NONE, DDSPF_R8G8_B8G8 }, // D3DFMT_R8G8_B8G8 { DXGI_FORMAT_G8R8_G8B8_UNORM, CONV_FLAGS_NONE, DDSPF_G8R8_G8B8 }, // D3DFMT_G8R8_G8B8 { DXGI_FORMAT_B8G8R8A8_UNORM, CONV_FLAGS_NONE, DDSPF_A8R8G8B8 }, // D3DFMT_A8R8G8B8 (uses DXGI 1.1 format) { DXGI_FORMAT_B8G8R8X8_UNORM, CONV_FLAGS_NONE, DDSPF_X8R8G8B8 }, // D3DFMT_X8R8G8B8 (uses DXGI 1.1 format) { DXGI_FORMAT_R8G8B8A8_UNORM, CONV_FLAGS_NONE, DDSPF_A8B8G8R8 }, // D3DFMT_A8B8G8R8 { DXGI_FORMAT_R8G8B8A8_UNORM, CONV_FLAGS_NOALPHA, DDSPF_X8B8G8R8 }, // D3DFMT_X8B8G8R8 { DXGI_FORMAT_R16G16_UNORM, CONV_FLAGS_NONE, DDSPF_G16R16 }, // D3DFMT_G16R16 { DXGI_FORMAT_R10G10B10A2_UNORM, CONV_FLAGS_SWIZZLE, { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x000003ff, 0x000ffc00, 0x3ff00000, 0xc0000000 } }, // D3DFMT_A2R10G10B10 (D3DX reversal issue workaround) { DXGI_FORMAT_R10G10B10A2_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000 } }, // D3DFMT_A2B10G10R10 (D3DX reversal issue workaround) { DXGI_FORMAT_R8G8B8A8_UNORM, CONV_FLAGS_EXPAND | CONV_FLAGS_NOALPHA | CONV_FLAGS_888, DDSPF_R8G8B8 }, // D3DFMT_R8G8B8 { DXGI_FORMAT_B5G6R5_UNORM, CONV_FLAGS_565, DDSPF_R5G6B5 }, // D3DFMT_R5G6B5 { DXGI_FORMAT_B5G5R5A1_UNORM, CONV_FLAGS_5551, DDSPF_A1R5G5B5 }, // D3DFMT_A1R5G5B5 { DXGI_FORMAT_B5G5R5A1_UNORM, CONV_FLAGS_5551 | CONV_FLAGS_NOALPHA, { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 16, 0x7c00, 0x03e0, 0x001f, 0x0000 } }, // D3DFMT_X1R5G5B5 { DXGI_FORMAT_R8G8B8A8_UNORM, CONV_FLAGS_EXPAND | CONV_FLAGS_8332, { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 16, 0x00e0, 0x001c, 0x0003, 0xff00 } }, // D3DFMT_A8R3G3B2 { DXGI_FORMAT_B5G6R5_UNORM, CONV_FLAGS_EXPAND | CONV_FLAGS_332, { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 8, 0xe0, 0x1c, 0x03, 0x00 } }, // D3DFMT_R3G3B2 { DXGI_FORMAT_R8_UNORM, CONV_FLAGS_NONE, DDSPF_L8 }, // D3DFMT_L8 { DXGI_FORMAT_R16_UNORM, CONV_FLAGS_NONE, DDSPF_L16 }, // D3DFMT_L16 { DXGI_FORMAT_R8G8_UNORM, CONV_FLAGS_NONE, DDSPF_A8L8 }, // D3DFMT_A8L8 { DXGI_FORMAT_R8G8_UNORM, CONV_FLAGS_NONE, DDSPF_A8L8_ALT }, // D3DFMT_A8L8 (alternative bitcount) { DXGI_FORMAT_A8_UNORM, CONV_FLAGS_NONE, DDSPF_A8 }, // D3DFMT_A8 { DXGI_FORMAT_R16G16B16A16_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, 36, 0, 0, 0, 0, 0 } }, // D3DFMT_A16B16G16R16 { DXGI_FORMAT_R16G16B16A16_SNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, 110, 0, 0, 0, 0, 0 } }, // D3DFMT_Q16W16V16U16 { DXGI_FORMAT_R16_FLOAT, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, 111, 0, 0, 0, 0, 0 } }, // D3DFMT_R16F { DXGI_FORMAT_R16G16_FLOAT, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, 112, 0, 0, 0, 0, 0 } }, // D3DFMT_G16R16F { DXGI_FORMAT_R16G16B16A16_FLOAT, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, 113, 0, 0, 0, 0, 0 } }, // D3DFMT_A16B16G16R16F { DXGI_FORMAT_R32_FLOAT, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, 114, 0, 0, 0, 0, 0 } }, // D3DFMT_R32F { DXGI_FORMAT_R32G32_FLOAT, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, 115, 0, 0, 0, 0, 0 } }, // D3DFMT_G32R32F { DXGI_FORMAT_R32G32B32A32_FLOAT, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, 116, 0, 0, 0, 0, 0 } }, // D3DFMT_A32B32G32R32F { DXGI_FORMAT_R32_FLOAT, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0xffffffff, 0x00000000, 0x00000000, 0x00000000 } }, // D3DFMT_R32F (D3DX uses FourCC 114 instead) { DXGI_FORMAT_R8G8B8A8_UNORM, CONV_FLAGS_EXPAND | CONV_FLAGS_PAL8 | CONV_FLAGS_A8P8, { sizeof(DDS_PIXELFORMAT), DDS_PAL8A, 0, 16, 0, 0, 0, 0 } }, // D3DFMT_A8P8 { DXGI_FORMAT_R8G8B8A8_UNORM, CONV_FLAGS_EXPAND | CONV_FLAGS_PAL8, { sizeof(DDS_PIXELFORMAT), DDS_PAL8, 0, 8, 0, 0, 0, 0 } }, // D3DFMT_P8 { DXGI_FORMAT_B4G4R4A4_UNORM, CONV_FLAGS_4444, DDSPF_A4R4G4B4 }, // D3DFMT_A4R4G4B4 (uses DXGI 1.2 format) { DXGI_FORMAT_B4G4R4A4_UNORM, CONV_FLAGS_NOALPHA | CONV_FLAGS_4444, { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 16, 0x0f00, 0x00f0, 0x000f, 0x0000 } }, // D3DFMT_X4R4G4B4 (uses DXGI 1.2 format) { DXGI_FORMAT_B4G4R4A4_UNORM, CONV_FLAGS_EXPAND | CONV_FLAGS_44, { sizeof(DDS_PIXELFORMAT), DDS_LUMINANCEA,0, 8, 0x0f, 0x00, 0x00, 0xf0 } }, // D3DFMT_A4L4 (uses DXGI 1.2 format) { DXGI_FORMAT_YUY2, CONV_FLAGS_NONE, DDSPF_YUY2 }, // D3DFMT_YUY2 (uses DXGI 1.2 format) { DXGI_FORMAT_YUY2, CONV_FLAGS_SWIZZLE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('U','Y','V','Y'), 0, 0, 0, 0, 0 } }, // D3DFMT_UYVY (uses DXGI 1.2 format) { DXGI_FORMAT_R8G8_SNORM, CONV_FLAGS_NONE, DDSPF_V8U8 }, // D3DFMT_V8U8 { DXGI_FORMAT_R8G8B8A8_SNORM, CONV_FLAGS_NONE, DDSPF_Q8W8V8U8 }, // D3DFMT_Q8W8V8U8 { DXGI_FORMAT_R16G16_SNORM, CONV_FLAGS_NONE, DDSPF_V16U16 }, // D3DFMT_V16U16 }; // Note that many common DDS reader/writers (including D3DX) swap the // the RED/BLUE masks for 10:10:10:2 formats. We assumme // 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 // We do not support the following legacy Direct3D 9 formats: // BumpDuDv D3DFMT_A2W10V10U10 // BumpLuminance D3DFMT_L6V5U5, D3DFMT_X8L8V8U8 // FourCC 117 D3DFMT_CxV8U8 // ZBuffer D3DFMT_D16_LOCKABLE // FourCC 82 D3DFMT_D32F_LOCKABLE // We do not support the following known FourCC codes: // FourCC CTX1 (Xbox 360 only) // FourCC EAR, EARG, ET2, ET2A (Ericsson Texture Compression) DXGI_FORMAT GetDXGIFormat(const DDS_HEADER& hdr, const DDS_PIXELFORMAT& ddpf, DWORD flags, _Inout_ DWORD& convFlags) { uint32_t ddpfFlags = ddpf.dwFlags; if (hdr.dwReserved1[9] == MAKEFOURCC('N', 'V', 'T', 'T')) { // Clear out non-standard nVidia DDS flags ddpfFlags &= ~0xC0000000 /* DDPF_SRGB | DDPF_NORMAL */; } const size_t MAP_SIZE = sizeof(g_LegacyDDSMap) / sizeof(LegacyDDS); size_t index = 0; for (index = 0; index < MAP_SIZE; ++index) { const LegacyDDS* entry = &g_LegacyDDSMap[index]; if (ddpfFlags == entry->ddpf.dwFlags) { if (entry->ddpf.dwFlags & DDS_FOURCC) { if (ddpf.dwFourCC == entry->ddpf.dwFourCC) break; } else if (entry->ddpf.dwFlags & DDS_PAL8) { if (ddpf.dwRGBBitCount == entry->ddpf.dwRGBBitCount) break; } else if (entry->ddpf.dwFlags & DDS_ALPHA) { if (ddpf.dwRGBBitCount == entry->ddpf.dwRGBBitCount && ddpf.dwABitMask == entry->ddpf.dwABitMask) break; } else if (entry->ddpf.dwFlags & DDS_LUMINANCE) { if (entry->ddpf.dwFlags & 0x1 /*DDPF_ALPHAPIXELS*/) { // LUMINANCEA if (ddpf.dwRGBBitCount == entry->ddpf.dwRGBBitCount && ddpf.dwRBitMask == entry->ddpf.dwRBitMask && ddpf.dwABitMask == entry->ddpf.dwABitMask) break; } else { // LUMINANCE if (ddpf.dwRGBBitCount == entry->ddpf.dwRGBBitCount && ddpf.dwRBitMask == entry->ddpf.dwRBitMask) break; } } else if (entry->ddpf.dwFlags & DDS_BUMPDUDV) { if (ddpf.dwRGBBitCount == entry->ddpf.dwRGBBitCount && ddpf.dwRBitMask == entry->ddpf.dwRBitMask && ddpf.dwGBitMask == entry->ddpf.dwGBitMask && ddpf.dwBBitMask == entry->ddpf.dwBBitMask && ddpf.dwABitMask == entry->ddpf.dwABitMask) break; } else if (ddpf.dwRGBBitCount == entry->ddpf.dwRGBBitCount) { if (entry->ddpf.dwFlags & 0x1 /*DDPF_ALPHAPIXELS*/) { // RGBA if (ddpf.dwRBitMask == entry->ddpf.dwRBitMask && ddpf.dwGBitMask == entry->ddpf.dwGBitMask && ddpf.dwBBitMask == entry->ddpf.dwBBitMask && ddpf.dwABitMask == entry->ddpf.dwABitMask) break; } else { // RGB if (ddpf.dwRBitMask == entry->ddpf.dwRBitMask && ddpf.dwGBitMask == entry->ddpf.dwGBitMask && ddpf.dwBBitMask == entry->ddpf.dwBBitMask) break; } } } } if (index >= MAP_SIZE) return DXGI_FORMAT_UNKNOWN; DWORD cflags = g_LegacyDDSMap[index].convFlags; DXGI_FORMAT format = g_LegacyDDSMap[index].format; if ((cflags & CONV_FLAGS_EXPAND) && (flags & DDS_FLAGS_NO_LEGACY_EXPANSION)) return DXGI_FORMAT_UNKNOWN; if ((format == DXGI_FORMAT_R10G10B10A2_UNORM) && (flags & DDS_FLAGS_NO_R10B10G10A2_FIXUP)) { cflags ^= CONV_FLAGS_SWIZZLE; } if ((hdr.dwReserved1[9] == MAKEFOURCC('N', 'V', 'T', 'T')) && (ddpf.dwFlags & 0x40000000 /* DDPF_SRGB */)) { format = MakeSRGB(format); } convFlags = cflags; return format; } //------------------------------------------------------------------------------------- // Decodes DDS header including optional DX10 extended header //------------------------------------------------------------------------------------- HRESULT DecodeDDSHeader( _In_reads_bytes_(size) const void* pSource, size_t size, DWORD flags, _Out_ TexMetadata& metadata, _Inout_ DWORD& convFlags) { if (!pSource) return E_INVALIDARG; memset(&metadata, 0, sizeof(TexMetadata)); if (size < (sizeof(DDS_HEADER) + sizeof(uint32_t))) { return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); } // DDS files always start with the same magic number ("DDS ") uint32_t dwMagicNumber = *reinterpret_cast(pSource); if (dwMagicNumber != DDS_MAGIC) { return E_FAIL; } auto pHeader = reinterpret_cast((const uint8_t*)pSource + sizeof(uint32_t)); // Verify header to validate DDS file if (pHeader->dwSize != sizeof(DDS_HEADER) || pHeader->ddspf.dwSize != sizeof(DDS_PIXELFORMAT)) { return E_FAIL; } metadata.mipLevels = pHeader->dwMipMapCount; if (metadata.mipLevels == 0) metadata.mipLevels = 1; // Check for DX10 extension if ((pHeader->ddspf.dwFlags & DDS_FOURCC) && (MAKEFOURCC('D', 'X', '1', '0') == pHeader->ddspf.dwFourCC)) { // Buffer must be big enough for both headers and magic value if (size < (sizeof(DDS_HEADER) + sizeof(uint32_t) + sizeof(DDS_HEADER_DXT10))) { return E_FAIL; } auto d3d10ext = reinterpret_cast((const uint8_t*)pSource + sizeof(uint32_t) + sizeof(DDS_HEADER)); convFlags |= CONV_FLAGS_DX10; metadata.arraySize = d3d10ext->arraySize; if (metadata.arraySize == 0) { return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); } metadata.format = d3d10ext->dxgiFormat; if (!IsValid(metadata.format) || IsPalettized(metadata.format)) { return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); } static_assert(static_cast(TEX_MISC_TEXTURECUBE) == static_cast(DDS_RESOURCE_MISC_TEXTURECUBE), "DDS header mismatch"); metadata.miscFlags = d3d10ext->miscFlag & ~TEX_MISC_TEXTURECUBE; switch (d3d10ext->resourceDimension) { case DDS_DIMENSION_TEXTURE1D: // D3DX writes 1D textures with a fixed Height of 1 if ((pHeader->dwFlags & DDS_HEIGHT) && pHeader->dwHeight != 1) { return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); } metadata.width = pHeader->dwWidth; metadata.height = 1; metadata.depth = 1; metadata.dimension = TEX_DIMENSION_TEXTURE1D; break; case DDS_DIMENSION_TEXTURE2D: if (d3d10ext->miscFlag & DDS_RESOURCE_MISC_TEXTURECUBE) { metadata.miscFlags |= TEX_MISC_TEXTURECUBE; metadata.arraySize *= 6; } metadata.width = pHeader->dwWidth; metadata.height = pHeader->dwHeight; metadata.depth = 1; metadata.dimension = TEX_DIMENSION_TEXTURE2D; break; case DDS_DIMENSION_TEXTURE3D: if (!(pHeader->dwFlags & DDS_HEADER_FLAGS_VOLUME)) { return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); } if (metadata.arraySize > 1) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); metadata.width = pHeader->dwWidth; metadata.height = pHeader->dwHeight; metadata.depth = pHeader->dwDepth; metadata.dimension = TEX_DIMENSION_TEXTURE3D; break; default: return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); } static_assert(static_cast(TEX_MISC2_ALPHA_MODE_MASK) == static_cast(DDS_MISC_FLAGS2_ALPHA_MODE_MASK), "DDS header mismatch"); static_assert(static_cast(TEX_ALPHA_MODE_UNKNOWN) == static_cast(DDS_ALPHA_MODE_UNKNOWN), "DDS header mismatch"); static_assert(static_cast(TEX_ALPHA_MODE_STRAIGHT) == static_cast(DDS_ALPHA_MODE_STRAIGHT), "DDS header mismatch"); static_assert(static_cast(TEX_ALPHA_MODE_PREMULTIPLIED) == static_cast(DDS_ALPHA_MODE_PREMULTIPLIED), "DDS header mismatch"); static_assert(static_cast(TEX_ALPHA_MODE_OPAQUE) == static_cast(DDS_ALPHA_MODE_OPAQUE), "DDS header mismatch"); static_assert(static_cast(TEX_ALPHA_MODE_CUSTOM) == static_cast(DDS_ALPHA_MODE_CUSTOM), "DDS header mismatch"); metadata.miscFlags2 = d3d10ext->miscFlags2; } else { metadata.arraySize = 1; if (pHeader->dwFlags & DDS_HEADER_FLAGS_VOLUME) { metadata.width = pHeader->dwWidth; metadata.height = pHeader->dwHeight; metadata.depth = pHeader->dwDepth; metadata.dimension = TEX_DIMENSION_TEXTURE3D; } else { if (pHeader->dwCaps2 & DDS_CUBEMAP) { // We require all six faces to be defined if ((pHeader->dwCaps2 & DDS_CUBEMAP_ALLFACES) != DDS_CUBEMAP_ALLFACES) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); metadata.arraySize = 6; metadata.miscFlags |= TEX_MISC_TEXTURECUBE; } metadata.width = pHeader->dwWidth; metadata.height = pHeader->dwHeight; metadata.depth = 1; metadata.dimension = TEX_DIMENSION_TEXTURE2D; // Note there's no way for a legacy Direct3D 9 DDS to express a '1D' texture } metadata.format = GetDXGIFormat(*pHeader, pHeader->ddspf, flags, convFlags); if (metadata.format == DXGI_FORMAT_UNKNOWN) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); if (convFlags & CONV_FLAGS_PMALPHA) metadata.miscFlags2 |= TEX_ALPHA_MODE_PREMULTIPLIED; // Special flag for handling LUMINANCE legacy formats if (flags & DDS_FLAGS_EXPAND_LUMINANCE) { switch (metadata.format) { case DXGI_FORMAT_R8_UNORM: metadata.format = DXGI_FORMAT_R8G8B8A8_UNORM; convFlags |= CONV_FLAGS_L8 | CONV_FLAGS_EXPAND; break; case DXGI_FORMAT_R8G8_UNORM: metadata.format = DXGI_FORMAT_R8G8B8A8_UNORM; convFlags |= CONV_FLAGS_A8L8 | CONV_FLAGS_EXPAND; break; case DXGI_FORMAT_R16_UNORM: metadata.format = DXGI_FORMAT_R16G16B16A16_UNORM; convFlags |= CONV_FLAGS_L16 | CONV_FLAGS_EXPAND; break; default: break; } } } // Special flag for handling BGR DXGI 1.1 formats if (flags & DDS_FLAGS_FORCE_RGB) { switch (metadata.format) { case DXGI_FORMAT_B8G8R8A8_UNORM: metadata.format = DXGI_FORMAT_R8G8B8A8_UNORM; convFlags |= CONV_FLAGS_SWIZZLE; break; case DXGI_FORMAT_B8G8R8X8_UNORM: metadata.format = DXGI_FORMAT_R8G8B8A8_UNORM; convFlags |= CONV_FLAGS_SWIZZLE | CONV_FLAGS_NOALPHA; break; case DXGI_FORMAT_B8G8R8A8_TYPELESS: metadata.format = DXGI_FORMAT_R8G8B8A8_TYPELESS; convFlags |= CONV_FLAGS_SWIZZLE; break; case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: metadata.format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; convFlags |= CONV_FLAGS_SWIZZLE; break; case DXGI_FORMAT_B8G8R8X8_TYPELESS: metadata.format = DXGI_FORMAT_R8G8B8A8_TYPELESS; convFlags |= CONV_FLAGS_SWIZZLE | CONV_FLAGS_NOALPHA; break; case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: metadata.format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; convFlags |= CONV_FLAGS_SWIZZLE | CONV_FLAGS_NOALPHA; break; default: break; } } // Special flag for handling 16bpp formats if (flags & DDS_FLAGS_NO_16BPP) { switch (metadata.format) { case DXGI_FORMAT_B5G6R5_UNORM: case DXGI_FORMAT_B5G5R5A1_UNORM: case DXGI_FORMAT_B4G4R4A4_UNORM: metadata.format = DXGI_FORMAT_R8G8B8A8_UNORM; convFlags |= CONV_FLAGS_EXPAND; if (metadata.format == DXGI_FORMAT_B5G6R5_UNORM) convFlags |= CONV_FLAGS_NOALPHA; default: break; } } return S_OK; } } //------------------------------------------------------------------------------------- // Encodes DDS file header (magic value, header, optional DX10 extended header) //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::_EncodeDDSHeader( const TexMetadata& metadata, DWORD flags, void* pDestination, size_t maxsize, size_t& required) { if (!IsValid(metadata.format)) return E_INVALIDARG; if (IsPalettized(metadata.format)) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); if (metadata.arraySize > 1) { if ((metadata.arraySize != 6) || (metadata.dimension != TEX_DIMENSION_TEXTURE2D) || !(metadata.IsCubemap())) { // Texture1D arrays, Texture2D arrays, and Cubemap arrays must be stored using 'DX10' extended header flags |= DDS_FLAGS_FORCE_DX10_EXT; } } if (flags & DDS_FLAGS_FORCE_DX10_EXT_MISC2) { flags |= DDS_FLAGS_FORCE_DX10_EXT; } DDS_PIXELFORMAT ddpf = { 0 }; if (!(flags & DDS_FLAGS_FORCE_DX10_EXT)) { switch (metadata.format) { case DXGI_FORMAT_R8G8B8A8_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_A8B8G8R8, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_R16G16_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_G16R16, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_R8G8_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_A8L8, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_R16_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_L16, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_R8_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_L8, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_A8_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_A8, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_R8G8_B8G8_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_R8G8_B8G8, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_G8R8_G8B8_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_G8R8_G8B8, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_BC1_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_DXT1, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_BC2_UNORM: memcpy_s(&ddpf, sizeof(ddpf), metadata.IsPMAlpha() ? (&DDSPF_DXT2) : (&DDSPF_DXT3), sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_BC3_UNORM: memcpy_s(&ddpf, sizeof(ddpf), metadata.IsPMAlpha() ? (&DDSPF_DXT4) : (&DDSPF_DXT5), sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_BC4_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_BC4_UNORM, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_BC4_SNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_BC4_SNORM, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_BC5_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_BC5_UNORM, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_BC5_SNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_BC5_SNORM, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_B5G6R5_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_R5G6B5, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_B5G5R5A1_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_A1R5G5B5, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_R8G8_SNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_V8U8, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_R8G8B8A8_SNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_Q8W8V8U8, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_R16G16_SNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_V16U16, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_B8G8R8A8_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_A8R8G8B8, sizeof(DDS_PIXELFORMAT)); break; // DXGI 1.1 case DXGI_FORMAT_B8G8R8X8_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_X8R8G8B8, sizeof(DDS_PIXELFORMAT)); break; // DXGI 1.1 case DXGI_FORMAT_B4G4R4A4_UNORM: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_A4R4G4B4, sizeof(DDS_PIXELFORMAT)); break; // DXGI 1.2 case DXGI_FORMAT_YUY2: memcpy_s(&ddpf, sizeof(ddpf), &DDSPF_YUY2, sizeof(DDS_PIXELFORMAT)); break; // DXGI 1.2 // Legacy D3DX formats using D3DFMT enum value as FourCC case DXGI_FORMAT_R32G32B32A32_FLOAT: ddpf.dwSize = sizeof(DDS_PIXELFORMAT); ddpf.dwFlags = DDS_FOURCC; ddpf.dwFourCC = 116; // D3DFMT_A32B32G32R32F break; case DXGI_FORMAT_R16G16B16A16_FLOAT: ddpf.dwSize = sizeof(DDS_PIXELFORMAT); ddpf.dwFlags = DDS_FOURCC; ddpf.dwFourCC = 113; // D3DFMT_A16B16G16R16F break; case DXGI_FORMAT_R16G16B16A16_UNORM: ddpf.dwSize = sizeof(DDS_PIXELFORMAT); ddpf.dwFlags = DDS_FOURCC; ddpf.dwFourCC = 36; // D3DFMT_A16B16G16R16 break; case DXGI_FORMAT_R16G16B16A16_SNORM: ddpf.dwSize = sizeof(DDS_PIXELFORMAT); ddpf.dwFlags = DDS_FOURCC; ddpf.dwFourCC = 110; // D3DFMT_Q16W16V16U16 break; case DXGI_FORMAT_R32G32_FLOAT: ddpf.dwSize = sizeof(DDS_PIXELFORMAT); ddpf.dwFlags = DDS_FOURCC; ddpf.dwFourCC = 115; // D3DFMT_G32R32F break; case DXGI_FORMAT_R16G16_FLOAT: ddpf.dwSize = sizeof(DDS_PIXELFORMAT); ddpf.dwFlags = DDS_FOURCC; ddpf.dwFourCC = 112; // D3DFMT_G16R16F break; case DXGI_FORMAT_R32_FLOAT: ddpf.dwSize = sizeof(DDS_PIXELFORMAT); ddpf.dwFlags = DDS_FOURCC; ddpf.dwFourCC = 114; // D3DFMT_R32F break; case DXGI_FORMAT_R16_FLOAT: ddpf.dwSize = sizeof(DDS_PIXELFORMAT); ddpf.dwFlags = DDS_FOURCC; ddpf.dwFourCC = 111; // D3DFMT_R16F break; default: break; } } required = sizeof(uint32_t) + sizeof(DDS_HEADER); if (ddpf.dwSize == 0) required += sizeof(DDS_HEADER_DXT10); if (!pDestination) return S_OK; if (maxsize < required) return E_NOT_SUFFICIENT_BUFFER; *reinterpret_cast(pDestination) = DDS_MAGIC; auto header = reinterpret_cast(reinterpret_cast(pDestination) + sizeof(uint32_t)); assert(header); memset(header, 0, sizeof(DDS_HEADER)); header->dwSize = sizeof(DDS_HEADER); header->dwFlags = DDS_HEADER_FLAGS_TEXTURE; header->dwCaps = DDS_SURFACE_FLAGS_TEXTURE; if (metadata.mipLevels > 0) { header->dwFlags |= DDS_HEADER_FLAGS_MIPMAP; if (metadata.mipLevels > UINT16_MAX) return E_INVALIDARG; header->dwMipMapCount = static_cast(metadata.mipLevels); if (header->dwMipMapCount > 1) header->dwCaps |= DDS_SURFACE_FLAGS_MIPMAP; } switch (metadata.dimension) { case TEX_DIMENSION_TEXTURE1D: if (metadata.width > UINT32_MAX) return E_INVALIDARG; header->dwWidth = static_cast(metadata.width); header->dwHeight = header->dwDepth = 1; break; case TEX_DIMENSION_TEXTURE2D: if (metadata.height > UINT32_MAX || metadata.width > UINT32_MAX) return E_INVALIDARG; header->dwHeight = static_cast(metadata.height); header->dwWidth = static_cast(metadata.width); header->dwDepth = 1; if (metadata.IsCubemap()) { header->dwCaps |= DDS_SURFACE_FLAGS_CUBEMAP; header->dwCaps2 |= DDS_CUBEMAP_ALLFACES; } break; case TEX_DIMENSION_TEXTURE3D: if (metadata.height > UINT32_MAX || metadata.width > UINT32_MAX || metadata.depth > UINT16_MAX) return E_INVALIDARG; header->dwFlags |= DDS_HEADER_FLAGS_VOLUME; header->dwCaps2 |= DDS_FLAGS_VOLUME; header->dwHeight = static_cast(metadata.height); header->dwWidth = static_cast(metadata.width); header->dwDepth = static_cast(metadata.depth); break; default: return E_FAIL; } size_t rowPitch, slicePitch; ComputePitch(metadata.format, metadata.width, metadata.height, rowPitch, slicePitch, CP_FLAGS_NONE); if (slicePitch > UINT32_MAX || rowPitch > UINT32_MAX) return E_FAIL; if (IsCompressed(metadata.format)) { header->dwFlags |= DDS_HEADER_FLAGS_LINEARSIZE; header->dwPitchOrLinearSize = static_cast(slicePitch); } else { header->dwFlags |= DDS_HEADER_FLAGS_PITCH; header->dwPitchOrLinearSize = static_cast(rowPitch); } if (ddpf.dwSize == 0) { memcpy_s(&header->ddspf, sizeof(header->ddspf), &DDSPF_DX10, sizeof(DDS_PIXELFORMAT)); auto ext = reinterpret_cast(reinterpret_cast(header) + sizeof(DDS_HEADER)); assert(ext); memset(ext, 0, sizeof(DDS_HEADER_DXT10)); ext->dxgiFormat = metadata.format; ext->resourceDimension = metadata.dimension; if (metadata.arraySize > UINT16_MAX) return E_INVALIDARG; static_assert(static_cast(TEX_MISC_TEXTURECUBE) == static_cast(DDS_RESOURCE_MISC_TEXTURECUBE), "DDS header mismatch"); ext->miscFlag = metadata.miscFlags & ~TEX_MISC_TEXTURECUBE; if (metadata.miscFlags & TEX_MISC_TEXTURECUBE) { ext->miscFlag |= TEX_MISC_TEXTURECUBE; assert((metadata.arraySize % 6) == 0); ext->arraySize = static_cast(metadata.arraySize / 6); } else { ext->arraySize = static_cast(metadata.arraySize); } static_assert(static_cast(TEX_MISC2_ALPHA_MODE_MASK) == static_cast(DDS_MISC_FLAGS2_ALPHA_MODE_MASK), "DDS header mismatch"); static_assert(static_cast(TEX_ALPHA_MODE_UNKNOWN) == static_cast(DDS_ALPHA_MODE_UNKNOWN), "DDS header mismatch"); static_assert(static_cast(TEX_ALPHA_MODE_STRAIGHT) == static_cast(DDS_ALPHA_MODE_STRAIGHT), "DDS header mismatch"); static_assert(static_cast(TEX_ALPHA_MODE_PREMULTIPLIED) == static_cast(DDS_ALPHA_MODE_PREMULTIPLIED), "DDS header mismatch"); static_assert(static_cast(TEX_ALPHA_MODE_OPAQUE) == static_cast(DDS_ALPHA_MODE_OPAQUE), "DDS header mismatch"); static_assert(static_cast(TEX_ALPHA_MODE_CUSTOM) == static_cast(DDS_ALPHA_MODE_CUSTOM), "DDS header mismatch"); if (flags & DDS_FLAGS_FORCE_DX10_EXT_MISC2) { // This was formerly 'reserved'. D3DX10 and D3DX11 will fail if this value is anything other than 0 ext->miscFlags2 = metadata.miscFlags2; } } else { memcpy_s(&header->ddspf, sizeof(header->ddspf), &ddpf, sizeof(ddpf)); } return S_OK; } namespace { //------------------------------------------------------------------------------------- // Converts an image row with optional clearing of alpha value to 1.0 // Returns true if supported, false if expansion case not supported //------------------------------------------------------------------------------------- enum TEXP_LEGACY_FORMAT { TEXP_LEGACY_UNKNOWN = 0, TEXP_LEGACY_R8G8B8, TEXP_LEGACY_R3G3B2, TEXP_LEGACY_A8R3G3B2, TEXP_LEGACY_P8, TEXP_LEGACY_A8P8, TEXP_LEGACY_A4L4, TEXP_LEGACY_B4G4R4A4, TEXP_LEGACY_L8, TEXP_LEGACY_L16, TEXP_LEGACY_A8L8 }; inline TEXP_LEGACY_FORMAT _FindLegacyFormat(DWORD flags) { TEXP_LEGACY_FORMAT lformat = TEXP_LEGACY_UNKNOWN; if (flags & CONV_FLAGS_PAL8) { lformat = (flags & CONV_FLAGS_A8P8) ? TEXP_LEGACY_A8P8 : TEXP_LEGACY_P8; } else if (flags & CONV_FLAGS_888) lformat = TEXP_LEGACY_R8G8B8; else if (flags & CONV_FLAGS_332) lformat = TEXP_LEGACY_R3G3B2; else if (flags & CONV_FLAGS_8332) lformat = TEXP_LEGACY_A8R3G3B2; else if (flags & CONV_FLAGS_44) lformat = TEXP_LEGACY_A4L4; else if (flags & CONV_FLAGS_4444) lformat = TEXP_LEGACY_B4G4R4A4; else if (flags & CONV_FLAGS_L8) lformat = TEXP_LEGACY_L8; else if (flags & CONV_FLAGS_L16) lformat = TEXP_LEGACY_L16; else if (flags & CONV_FLAGS_A8L8) lformat = TEXP_LEGACY_A8L8; return lformat; } _Success_(return != false) bool LegacyExpandScanline( _Out_writes_bytes_(outSize) void* pDestination, size_t outSize, _In_ DXGI_FORMAT outFormat, _In_reads_bytes_(inSize) const void* pSource, size_t inSize, _In_ TEXP_LEGACY_FORMAT inFormat, _In_reads_opt_(256) const uint32_t* pal8, _In_ DWORD flags) { assert(pDestination && outSize > 0); assert(pSource && inSize > 0); assert(IsValid(outFormat) && !IsPlanar(outFormat) && !IsPalettized(outFormat)); switch (inFormat) { case TEXP_LEGACY_R8G8B8: if (outFormat != DXGI_FORMAT_R8G8B8A8_UNORM) return false; // D3DFMT_R8G8B8 -> DXGI_FORMAT_R8G8B8A8_UNORM if (inSize >= 3 && outSize >= 4) { const uint8_t * __restrict sPtr = reinterpret_cast(pSource); uint32_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < (inSize - 2)) && (ocount < (outSize - 3))); icount += 3, ocount += 4) { // 24bpp Direct3D 9 files are actually BGR, so need to swizzle as well uint32_t t1 = (*(sPtr) << 16); uint32_t t2 = (*(sPtr + 1) << 8); uint32_t t3 = *(sPtr + 2); *(dPtr++) = t1 | t2 | t3 | 0xff000000; sPtr += 3; } return true; } return false; case TEXP_LEGACY_R3G3B2: switch (outFormat) { case DXGI_FORMAT_R8G8B8A8_UNORM: // D3DFMT_R3G3B2 -> DXGI_FORMAT_R8G8B8A8_UNORM if (inSize >= 1 && outSize >= 4) { const uint8_t* __restrict sPtr = reinterpret_cast(pSource); uint32_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < inSize) && (ocount < (outSize - 3))); ++icount, ocount += 4) { uint8_t t = *(sPtr++); uint32_t t1 = (t & 0xe0) | ((t & 0xe0) >> 3) | ((t & 0xc0) >> 6); uint32_t t2 = ((t & 0x1c) << 11) | ((t & 0x1c) << 8) | ((t & 0x18) << 5); uint32_t t3 = ((t & 0x03) << 22) | ((t & 0x03) << 20) | ((t & 0x03) << 18) | ((t & 0x03) << 16); *(dPtr++) = t1 | t2 | t3 | 0xff000000; } return true; } return false; case DXGI_FORMAT_B5G6R5_UNORM: // D3DFMT_R3G3B2 -> DXGI_FORMAT_B5G6R5_UNORM if (inSize >= 1 && outSize >= 2) { const uint8_t* __restrict sPtr = reinterpret_cast(pSource); uint16_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < inSize) && (ocount < (outSize - 1))); ++icount, ocount += 2) { uint8_t t = *(sPtr++); uint16_t t1 = ((t & 0xe0) << 8) | ((t & 0xc0) << 5); uint16_t t2 = ((t & 0x1c) << 6) | ((t & 0x1c) << 3); uint16_t t3 = ((t & 0x03) << 3) | ((t & 0x03) << 1) | ((t & 0x02) >> 1); *(dPtr++) = t1 | t2 | t3; } return true; } return false; default: return false; } break; case TEXP_LEGACY_A8R3G3B2: if (outFormat != DXGI_FORMAT_R8G8B8A8_UNORM) return false; // D3DFMT_A8R3G3B2 -> DXGI_FORMAT_R8G8B8A8_UNORM if (inSize >= 2 && outSize >= 4) { const uint16_t* __restrict sPtr = reinterpret_cast(pSource); uint32_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < (inSize - 1)) && (ocount < (outSize - 3))); icount += 2, ocount += 4) { uint16_t t = *(sPtr++); uint32_t t1 = (t & 0x00e0) | ((t & 0x00e0) >> 3) | ((t & 0x00c0) >> 6); uint32_t t2 = ((t & 0x001c) << 11) | ((t & 0x001c) << 8) | ((t & 0x0018) << 5); uint32_t t3 = ((t & 0x0003) << 22) | ((t & 0x0003) << 20) | ((t & 0x0003) << 18) | ((t & 0x0003) << 16); uint32_t ta = (flags & TEXP_SCANLINE_SETALPHA) ? 0xff000000 : ((t & 0xff00) << 16); *(dPtr++) = t1 | t2 | t3 | ta; } return true; } return false; case TEXP_LEGACY_P8: if ((outFormat != DXGI_FORMAT_R8G8B8A8_UNORM) || !pal8) return false; // D3DFMT_P8 -> DXGI_FORMAT_R8G8B8A8_UNORM if (inSize >= 1 && outSize >= 4) { const uint8_t* __restrict sPtr = reinterpret_cast(pSource); uint32_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < inSize) && (ocount < (outSize - 3))); ++icount, ocount += 4) { uint8_t t = *(sPtr++); *(dPtr++) = pal8[t]; } return true; } return false; case TEXP_LEGACY_A8P8: if ((outFormat != DXGI_FORMAT_R8G8B8A8_UNORM) || !pal8) return false; // D3DFMT_A8P8 -> DXGI_FORMAT_R8G8B8A8_UNORM if (inSize >= 2 && outSize >= 4) { const uint16_t* __restrict sPtr = reinterpret_cast(pSource); uint32_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < (inSize - 1)) && (ocount < (outSize - 3))); icount += 2, ocount += 4) { uint16_t t = *(sPtr++); uint32_t t1 = pal8[t & 0xff]; uint32_t ta = (flags & TEXP_SCANLINE_SETALPHA) ? 0xff000000 : ((t & 0xff00) << 16); *(dPtr++) = t1 | ta; } return true; } return false; case TEXP_LEGACY_A4L4: switch (outFormat) { case DXGI_FORMAT_B4G4R4A4_UNORM: // D3DFMT_A4L4 -> DXGI_FORMAT_B4G4R4A4_UNORM if (inSize >= 1 && outSize >= 2) { const uint8_t * __restrict sPtr = reinterpret_cast(pSource); uint16_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < inSize) && (ocount < (outSize - 1))); ++icount, ocount += 2) { uint8_t t = *(sPtr++); uint16_t t1 = (t & 0x0f); uint16_t ta = (flags & TEXP_SCANLINE_SETALPHA) ? 0xf000 : ((t & 0xf0) << 8); *(dPtr++) = t1 | (t1 << 4) | (t1 << 8) | ta; } return true; } return false; case DXGI_FORMAT_R8G8B8A8_UNORM: // D3DFMT_A4L4 -> DXGI_FORMAT_R8G8B8A8_UNORM if (inSize >= 1 && outSize >= 4) { const uint8_t * __restrict sPtr = reinterpret_cast(pSource); uint32_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < inSize) && (ocount < (outSize - 3))); ++icount, ocount += 4) { uint8_t t = *(sPtr++); uint32_t t1 = ((t & 0x0f) << 4) | (t & 0x0f); uint32_t ta = (flags & TEXP_SCANLINE_SETALPHA) ? 0xff000000 : (((t & 0xf0) << 24) | ((t & 0xf0) << 20)); *(dPtr++) = t1 | (t1 << 8) | (t1 << 16) | ta; } return true; } return false; default: return false; } break; case TEXP_LEGACY_B4G4R4A4: if (outFormat != DXGI_FORMAT_R8G8B8A8_UNORM) return false; // D3DFMT_A4R4G4B4 -> DXGI_FORMAT_R8G8B8A8_UNORM if (inSize >= 2 && outSize >= 4) { const uint16_t * __restrict sPtr = reinterpret_cast(pSource); uint32_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < (inSize - 1)) && (ocount < (outSize - 3))); icount += 2, ocount += 4) { uint16_t t = *(sPtr++); uint32_t t1 = ((t & 0x0f00) >> 4) | ((t & 0x0f00) >> 8); uint32_t t2 = ((t & 0x00f0) << 8) | ((t & 0x00f0) << 4); uint32_t t3 = ((t & 0x000f) << 20) | ((t & 0x000f) << 16); uint32_t ta = (flags & TEXP_SCANLINE_SETALPHA) ? 0xff000000 : (((t & 0xf000) << 16) | ((t & 0xf000) << 12)); *(dPtr++) = t1 | t2 | t3 | ta; } return true; } return false; case TEXP_LEGACY_L8: if (outFormat != DXGI_FORMAT_R8G8B8A8_UNORM) return false; // D3DFMT_L8 -> DXGI_FORMAT_R8G8B8A8_UNORM if (inSize >= 1 && outSize >= 4) { const uint8_t * __restrict sPtr = reinterpret_cast(pSource); uint32_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < inSize) && (ocount < (outSize - 3))); ++icount, ocount += 4) { uint32_t t1 = *(sPtr++); uint32_t t2 = (t1 << 8); uint32_t t3 = (t1 << 16); *(dPtr++) = t1 | t2 | t3 | 0xff000000; } return true; } return false; case TEXP_LEGACY_L16: if (outFormat != DXGI_FORMAT_R16G16B16A16_UNORM) return false; // D3DFMT_L16 -> DXGI_FORMAT_R16G16B16A16_UNORM if (inSize >= 2 && outSize >= 8) { const uint16_t* __restrict sPtr = reinterpret_cast(pSource); uint64_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < (inSize - 1)) && (ocount < (outSize - 7))); icount += 2, ocount += 8) { uint16_t t = *(sPtr++); uint64_t t1 = t; uint64_t t2 = (t1 << 16); uint64_t t3 = (t1 << 32); *(dPtr++) = t1 | t2 | t3 | 0xffff000000000000; } return true; } return false; case TEXP_LEGACY_A8L8: if (outFormat != DXGI_FORMAT_R8G8B8A8_UNORM) return false; // D3DFMT_A8L8 -> DXGI_FORMAT_R8G8B8A8_UNORM if (inSize >= 2 && outSize >= 4) { const uint16_t* __restrict sPtr = reinterpret_cast(pSource); uint32_t * __restrict dPtr = reinterpret_cast(pDestination); for (size_t ocount = 0, icount = 0; ((icount < (inSize - 1)) && (ocount < (outSize - 3))); icount += 2, ocount += 4) { uint16_t t = *(sPtr++); uint32_t t1 = (t & 0xff); uint32_t t2 = (t1 << 8); uint32_t t3 = (t1 << 16); uint32_t ta = (flags & TEXP_SCANLINE_SETALPHA) ? 0xff000000 : ((t & 0xff00) << 16); *(dPtr++) = t1 | t2 | t3 | ta; } return true; } return false; default: return false; } } //------------------------------------------------------------------------------------- // Converts or copies image data from pPixels into scratch image data //------------------------------------------------------------------------------------- HRESULT CopyImage( _In_reads_bytes_(size) const void* pPixels, _In_ size_t size, _In_ const TexMetadata& metadata, _In_ DWORD cpFlags, _In_ DWORD convFlags, _In_reads_opt_(256) const uint32_t *pal8, _In_ const ScratchImage& image) { assert(pPixels); assert(image.GetPixels()); if (!size) return E_FAIL; if (convFlags & CONV_FLAGS_EXPAND) { if (convFlags & CONV_FLAGS_888) cpFlags |= CP_FLAGS_24BPP; else if (convFlags & (CONV_FLAGS_565 | CONV_FLAGS_5551 | CONV_FLAGS_4444 | CONV_FLAGS_8332 | CONV_FLAGS_A8P8 | CONV_FLAGS_L16 | CONV_FLAGS_A8L8)) cpFlags |= CP_FLAGS_16BPP; else if (convFlags & (CONV_FLAGS_44 | CONV_FLAGS_332 | CONV_FLAGS_PAL8 | CONV_FLAGS_L8)) cpFlags |= CP_FLAGS_8BPP; } size_t pixelSize, nimages; _DetermineImageArray(metadata, cpFlags, nimages, pixelSize); if ((nimages == 0) || (nimages != image.GetImageCount())) { return E_FAIL; } if (pixelSize > size) { return HRESULT_FROM_WIN32( ERROR_HANDLE_EOF ); } std::unique_ptr timages(new (std::nothrow) Image[nimages]); if (!timages) { return E_OUTOFMEMORY; } if (!_SetupImageArray((uint8_t*)pPixels, pixelSize, metadata, cpFlags, timages.get(), nimages)) { return E_FAIL; } if (nimages != image.GetImageCount()) { return E_FAIL; } const Image* images = image.GetImages(); if (!images) { return E_FAIL; } DWORD tflags = (convFlags & CONV_FLAGS_NOALPHA) ? TEXP_SCANLINE_SETALPHA : 0; if (convFlags & CONV_FLAGS_SWIZZLE) tflags |= TEXP_SCANLINE_LEGACY; switch (metadata.dimension) { case TEX_DIMENSION_TEXTURE1D: case TEX_DIMENSION_TEXTURE2D: { size_t index = 0; for (size_t item = 0; item < metadata.arraySize; ++item) { size_t lastgood = 0; for (size_t level = 0; level < metadata.mipLevels; ++level, ++index) { if (index >= nimages) return E_FAIL; if (images[index].height != timages[index].height) return E_FAIL; size_t dpitch = images[index].rowPitch; size_t spitch = timages[index].rowPitch; const uint8_t *pSrc = const_cast(timages[index].pixels); if (!pSrc) return E_POINTER; uint8_t *pDest = images[index].pixels; if (!pDest) return E_POINTER; if (IsCompressed(metadata.format)) { size_t csize = std::min(images[index].slicePitch, timages[index].slicePitch); memcpy_s(pDest, images[index].slicePitch, pSrc, csize); if (cpFlags & CP_FLAGS_BAD_DXTN_TAILS) { if (images[index].width < 4 || images[index].height < 4) { csize = std::min(images[index].slicePitch, timages[lastgood].slicePitch); memcpy_s(pDest, images[index].slicePitch, timages[lastgood].pixels, csize); } else { lastgood = index; } } } else if (IsPlanar(metadata.format)) { size_t count = ComputeScanlines(metadata.format, images[index].height); if (!count) return E_UNEXPECTED; size_t csize = std::min(dpitch, spitch); for (size_t h = 0; h < count; ++h) { memcpy_s(pDest, dpitch, pSrc, csize); pSrc += spitch; pDest += dpitch; } } else { for (size_t h = 0; h < images[index].height; ++h) { if (convFlags & CONV_FLAGS_EXPAND) { if (convFlags & (CONV_FLAGS_565 | CONV_FLAGS_5551 | CONV_FLAGS_4444)) { if (!_ExpandScanline(pDest, dpitch, DXGI_FORMAT_R8G8B8A8_UNORM, pSrc, spitch, (convFlags & CONV_FLAGS_565) ? DXGI_FORMAT_B5G6R5_UNORM : DXGI_FORMAT_B5G5R5A1_UNORM, tflags)) return E_FAIL; } else { TEXP_LEGACY_FORMAT lformat = _FindLegacyFormat(convFlags); if (!LegacyExpandScanline(pDest, dpitch, metadata.format, pSrc, spitch, lformat, pal8, tflags)) return E_FAIL; } } else if (convFlags & CONV_FLAGS_SWIZZLE) { _SwizzleScanline(pDest, dpitch, pSrc, spitch, metadata.format, tflags); } else { _CopyScanline(pDest, dpitch, pSrc, spitch, metadata.format, tflags); } pSrc += spitch; pDest += dpitch; } } } } } break; case TEX_DIMENSION_TEXTURE3D: { size_t index = 0; size_t d = metadata.depth; size_t lastgood = 0; for (size_t level = 0; level < metadata.mipLevels; ++level) { for (size_t slice = 0; slice < d; ++slice, ++index) { if (index >= nimages) return E_FAIL; if (images[index].height != timages[index].height) return E_FAIL; size_t dpitch = images[index].rowPitch; size_t spitch = timages[index].rowPitch; const uint8_t *pSrc = const_cast(timages[index].pixels); if (!pSrc) return E_POINTER; uint8_t *pDest = images[index].pixels; if (!pDest) return E_POINTER; if (IsCompressed(metadata.format)) { size_t csize = std::min(images[index].slicePitch, timages[index].slicePitch); memcpy_s(pDest, images[index].slicePitch, pSrc, csize); if (cpFlags & CP_FLAGS_BAD_DXTN_TAILS) { if (images[index].width < 4 || images[index].height < 4) { csize = std::min(images[index].slicePitch, timages[lastgood + slice].slicePitch); memcpy_s(pDest, images[index].slicePitch, timages[lastgood + slice].pixels, csize); } else if (!slice) { lastgood = index; } } } else if (IsPlanar(metadata.format)) { // Direct3D does not support any planar formats for Texture3D return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); } else { for (size_t h = 0; h < images[index].height; ++h) { if (convFlags & CONV_FLAGS_EXPAND) { if (convFlags & (CONV_FLAGS_565 | CONV_FLAGS_5551 | CONV_FLAGS_4444)) { if (!_ExpandScanline(pDest, dpitch, DXGI_FORMAT_R8G8B8A8_UNORM, pSrc, spitch, (convFlags & CONV_FLAGS_565) ? DXGI_FORMAT_B5G6R5_UNORM : DXGI_FORMAT_B5G5R5A1_UNORM, tflags)) return E_FAIL; } else { TEXP_LEGACY_FORMAT lformat = _FindLegacyFormat(convFlags); if (!LegacyExpandScanline(pDest, dpitch, metadata.format, pSrc, spitch, lformat, pal8, tflags)) return E_FAIL; } } else if (convFlags & CONV_FLAGS_SWIZZLE) { _SwizzleScanline(pDest, dpitch, pSrc, spitch, metadata.format, tflags); } else { _CopyScanline(pDest, dpitch, pSrc, spitch, metadata.format, tflags); } pSrc += spitch; pDest += dpitch; } } } if (d > 1) d >>= 1; } } break; default: return E_FAIL; } return S_OK; } HRESULT CopyImageInPlace(DWORD convFlags, _In_ const ScratchImage& image) { if (!image.GetPixels()) return E_FAIL; const Image* images = image.GetImages(); if (!images) return E_FAIL; const TexMetadata& metadata = image.GetMetadata(); if (IsPlanar(metadata.format)) return HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); DWORD tflags = (convFlags & CONV_FLAGS_NOALPHA) ? TEXP_SCANLINE_SETALPHA : 0; if (convFlags & CONV_FLAGS_SWIZZLE) tflags |= TEXP_SCANLINE_LEGACY; for (size_t i = 0; i < image.GetImageCount(); ++i) { const Image* img = &images[i]; uint8_t *pPixels = img->pixels; if (!pPixels) return E_POINTER; size_t rowPitch = img->rowPitch; for (size_t h = 0; h < img->height; ++h) { if (convFlags & CONV_FLAGS_SWIZZLE) { _SwizzleScanline(pPixels, rowPitch, pPixels, rowPitch, metadata.format, tflags); } else { _CopyScanline(pPixels, rowPitch, pPixels, rowPitch, metadata.format, tflags); } pPixels += rowPitch; } } return S_OK; } } //===================================================================================== // Entry-points //===================================================================================== //------------------------------------------------------------------------------------- // Obtain metadata from DDS file in memory/on disk //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::GetMetadataFromDDSMemory( const void* pSource, size_t size, DWORD flags, TexMetadata& metadata) { if (!pSource || size == 0) return E_INVALIDARG; DWORD convFlags = 0; return DecodeDDSHeader(pSource, size, flags, metadata, convFlags); } _Use_decl_annotations_ HRESULT DirectX::GetMetadataFromDDSFile( const wchar_t* szFile, DWORD flags, TexMetadata& metadata) { if (!szFile) return E_INVALIDARG; #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) ScopedHandle hFile(safe_handle(CreateFile2(szFile, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr))); #else ScopedHandle hFile(safe_handle(CreateFileW(szFile, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr))); #endif if (!hFile) { return HRESULT_FROM_WIN32(GetLastError()); } // Get the file size FILE_STANDARD_INFO fileInfo; if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo))) { return HRESULT_FROM_WIN32(GetLastError()); } // File is too big for 32-bit allocation, so reject read (4 GB should be plenty large enough for a valid DDS file) if (fileInfo.EndOfFile.HighPart > 0) { return HRESULT_FROM_WIN32(ERROR_FILE_TOO_LARGE); } // Need at least enough data to fill the standard header and magic number to be a valid DDS if (fileInfo.EndOfFile.LowPart < (sizeof(DDS_HEADER) + sizeof(uint32_t))) { return E_FAIL; } // Read the header in (including extended header if present) const size_t MAX_HEADER_SIZE = sizeof(uint32_t) + sizeof(DDS_HEADER) + sizeof(DDS_HEADER_DXT10); uint8_t header[MAX_HEADER_SIZE]; DWORD bytesRead = 0; if (!ReadFile(hFile.get(), header, MAX_HEADER_SIZE, &bytesRead, nullptr)) { return HRESULT_FROM_WIN32(GetLastError()); } DWORD convFlags = 0; return DecodeDDSHeader(header, bytesRead, flags, metadata, convFlags); } //------------------------------------------------------------------------------------- // Load a DDS file in memory //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::LoadFromDDSMemory( const void* pSource, size_t size, DWORD flags, TexMetadata* metadata, ScratchImage& image) { if (!pSource || size == 0) return E_INVALIDARG; image.Release(); DWORD convFlags = 0; TexMetadata mdata; HRESULT hr = DecodeDDSHeader(pSource, size, flags, mdata, convFlags); if (FAILED(hr)) return hr; size_t offset = sizeof(uint32_t) + sizeof(DDS_HEADER); if (convFlags & CONV_FLAGS_DX10) offset += sizeof(DDS_HEADER_DXT10); assert(offset <= size); const uint32_t *pal8 = nullptr; if (convFlags & CONV_FLAGS_PAL8) { pal8 = reinterpret_cast(reinterpret_cast(pSource) + offset); assert(pal8); offset += (256 * sizeof(uint32_t)); if (size < offset) return E_FAIL; } hr = image.Initialize(mdata); if (FAILED(hr)) return hr; DWORD cflags = CP_FLAGS_NONE; if (flags & DDS_FLAGS_LEGACY_DWORD) { cflags |= CP_FLAGS_LEGACY_DWORD; } if (flags & DDS_FLAGS_BAD_DXTN_TAILS) { cflags |= CP_FLAGS_BAD_DXTN_TAILS; } auto pPixels = reinterpret_cast(reinterpret_cast(pSource) + offset); assert(pPixels); hr = CopyImage(pPixels, size - offset, mdata, cflags, convFlags, pal8, image); if (FAILED(hr)) { image.Release(); return hr; } if (metadata) memcpy(metadata, &mdata, sizeof(TexMetadata)); return S_OK; } //------------------------------------------------------------------------------------- // Load a DDS file from disk //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::LoadFromDDSFile( const wchar_t* szFile, DWORD flags, TexMetadata* metadata, ScratchImage& image) { if (!szFile) return E_INVALIDARG; image.Release(); #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) ScopedHandle hFile(safe_handle(CreateFile2(szFile, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, nullptr))); #else ScopedHandle hFile(safe_handle(CreateFileW(szFile, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr))); #endif if (!hFile) { return HRESULT_FROM_WIN32(GetLastError()); } // Get the file size FILE_STANDARD_INFO fileInfo; if (!GetFileInformationByHandleEx(hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo))) { return HRESULT_FROM_WIN32(GetLastError()); } // File is too big for 32-bit allocation, so reject read (4 GB should be plenty large enough for a valid DDS file) if (fileInfo.EndOfFile.HighPart > 0) { return HRESULT_FROM_WIN32(ERROR_FILE_TOO_LARGE); } // Need at least enough data to fill the standard header and magic number to be a valid DDS if (fileInfo.EndOfFile.LowPart < (sizeof(DDS_HEADER) + sizeof(uint32_t))) { return E_FAIL; } // Read the header in (including extended header if present) const size_t MAX_HEADER_SIZE = sizeof(uint32_t) + sizeof(DDS_HEADER) + sizeof(DDS_HEADER_DXT10); uint8_t header[MAX_HEADER_SIZE]; DWORD bytesRead = 0; if (!ReadFile(hFile.get(), header, MAX_HEADER_SIZE, &bytesRead, nullptr)) { return HRESULT_FROM_WIN32(GetLastError()); } DWORD convFlags = 0; TexMetadata mdata; HRESULT hr = DecodeDDSHeader(header, bytesRead, flags, mdata, convFlags); if (FAILED(hr)) return hr; DWORD offset = MAX_HEADER_SIZE; if (!(convFlags & CONV_FLAGS_DX10)) { // Must reset file position since we read more than the standard header above LARGE_INTEGER filePos = { { sizeof(uint32_t) + sizeof(DDS_HEADER), 0 } }; if (!SetFilePointerEx(hFile.get(), filePos, 0, FILE_BEGIN)) { return HRESULT_FROM_WIN32(GetLastError()); } offset = sizeof(uint32_t) + sizeof(DDS_HEADER); } std::unique_ptr pal8; if (convFlags & CONV_FLAGS_PAL8) { pal8.reset(new (std::nothrow) uint32_t[256]); if (!pal8) { return E_OUTOFMEMORY; } if (!ReadFile(hFile.get(), pal8.get(), 256 * sizeof(uint32_t), &bytesRead, nullptr)) { return HRESULT_FROM_WIN32(GetLastError()); } if (bytesRead != (256 * sizeof(uint32_t))) { return E_FAIL; } offset += (256 * sizeof(uint32_t)); } DWORD remaining = fileInfo.EndOfFile.LowPart - offset; if (remaining == 0) return E_FAIL; hr = image.Initialize(mdata); if (FAILED(hr)) return hr; if ((convFlags & CONV_FLAGS_EXPAND) || (flags & (DDS_FLAGS_LEGACY_DWORD | DDS_FLAGS_BAD_DXTN_TAILS))) { std::unique_ptr temp(new (std::nothrow) uint8_t[remaining]); if (!temp) { image.Release(); return E_OUTOFMEMORY; } if (!ReadFile(hFile.get(), temp.get(), remaining, &bytesRead, nullptr)) { image.Release(); return HRESULT_FROM_WIN32(GetLastError()); } if (bytesRead != remaining) { image.Release(); return E_FAIL; } DWORD cflags = CP_FLAGS_NONE; if (flags & DDS_FLAGS_LEGACY_DWORD) { cflags |= CP_FLAGS_LEGACY_DWORD; } if (flags & DDS_FLAGS_BAD_DXTN_TAILS) { cflags |= CP_FLAGS_BAD_DXTN_TAILS; } hr = CopyImage(temp.get(), remaining, mdata, cflags, convFlags, pal8.get(), image); if (FAILED(hr)) { image.Release(); return hr; } } else { if (remaining < image.GetPixelsSize()) { image.Release(); return HRESULT_FROM_WIN32( ERROR_HANDLE_EOF ); } if (!ReadFile(hFile.get(), image.GetPixels(), static_cast(image.GetPixelsSize()), &bytesRead, nullptr)) { image.Release(); return HRESULT_FROM_WIN32(GetLastError()); } if (convFlags & (CONV_FLAGS_SWIZZLE | CONV_FLAGS_NOALPHA)) { // Swizzle/copy image in place hr = CopyImageInPlace(convFlags, image); if (FAILED(hr)) { image.Release(); return hr; } } } if (metadata) memcpy(metadata, &mdata, sizeof(TexMetadata)); return S_OK; } //------------------------------------------------------------------------------------- // Save a DDS file to memory //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::SaveToDDSMemory( const Image* images, size_t nimages, const TexMetadata& metadata, DWORD flags, Blob& blob) { if (!images || (nimages == 0)) return E_INVALIDARG; // Determine memory required size_t required = 0; HRESULT hr = _EncodeDDSHeader(metadata, flags, 0, 0, required); if (FAILED(hr)) return hr; bool fastpath = true; for (size_t i = 0; i < nimages; ++i) { if (!images[i].pixels) return E_POINTER; if (images[i].format != metadata.format) return E_FAIL; size_t ddsRowPitch, ddsSlicePitch; ComputePitch(metadata.format, images[i].width, images[i].height, ddsRowPitch, ddsSlicePitch, CP_FLAGS_NONE); assert(images[i].rowPitch > 0); assert(images[i].slicePitch > 0); if ((images[i].rowPitch != ddsRowPitch) || (images[i].slicePitch != ddsSlicePitch)) { fastpath = false; } required += ddsSlicePitch; } assert(required > 0); blob.Release(); hr = blob.Initialize(required); if (FAILED(hr)) return hr; auto pDestination = reinterpret_cast(blob.GetBufferPointer()); assert(pDestination); hr = _EncodeDDSHeader(metadata, flags, pDestination, blob.GetBufferSize(), required); if (FAILED(hr)) { blob.Release(); return hr; } size_t remaining = blob.GetBufferSize() - required; pDestination += required; if (!remaining) { blob.Release(); return E_FAIL; } switch (metadata.dimension) { case DDS_DIMENSION_TEXTURE1D: case DDS_DIMENSION_TEXTURE2D: { size_t index = 0; for (size_t item = 0; item < metadata.arraySize; ++item) { for (size_t level = 0; level < metadata.mipLevels; ++level) { if (index >= nimages) { blob.Release(); return E_FAIL; } if (fastpath) { size_t pixsize = images[index].slicePitch; if (memcpy_s(pDestination, remaining, images[index].pixels, pixsize)) { blob.Release(); return E_FAIL; } pDestination += pixsize; remaining -= pixsize; } else { size_t ddsRowPitch, ddsSlicePitch; ComputePitch(metadata.format, images[index].width, images[index].height, ddsRowPitch, ddsSlicePitch, CP_FLAGS_NONE); size_t rowPitch = images[index].rowPitch; const uint8_t * __restrict sPtr = images[index].pixels; uint8_t * __restrict dPtr = reinterpret_cast(pDestination); size_t lines = ComputeScanlines(metadata.format, images[index].height); size_t csize = std::min(rowPitch, ddsRowPitch); size_t tremaining = remaining; for (size_t j = 0; j < lines; ++j) { if (memcpy_s(dPtr, tremaining, sPtr, csize)) { blob.Release(); return E_FAIL; } sPtr += rowPitch; dPtr += ddsRowPitch; tremaining -= ddsRowPitch; } pDestination += ddsSlicePitch; remaining -= ddsSlicePitch; } ++index; } } } break; case DDS_DIMENSION_TEXTURE3D: { if (metadata.arraySize != 1) { blob.Release(); return E_FAIL; } size_t d = metadata.depth; size_t index = 0; for (size_t level = 0; level < metadata.mipLevels; ++level) { for (size_t slice = 0; slice < d; ++slice) { if (index >= nimages) { blob.Release(); return E_FAIL; } if (fastpath) { size_t pixsize = images[index].slicePitch; if (memcpy_s(pDestination, remaining, images[index].pixels, pixsize)) { blob.Release(); return E_FAIL; } pDestination += pixsize; remaining -= pixsize; } else { size_t ddsRowPitch, ddsSlicePitch; ComputePitch(metadata.format, images[index].width, images[index].height, ddsRowPitch, ddsSlicePitch, CP_FLAGS_NONE); size_t rowPitch = images[index].rowPitch; const uint8_t * __restrict sPtr = images[index].pixels; uint8_t * __restrict dPtr = reinterpret_cast(pDestination); size_t lines = ComputeScanlines(metadata.format, images[index].height); size_t csize = std::min(rowPitch, ddsRowPitch); size_t tremaining = remaining; for (size_t j = 0; j < lines; ++j) { if (memcpy_s(dPtr, tremaining, sPtr, csize)) { blob.Release(); return E_FAIL; } sPtr += rowPitch; dPtr += ddsRowPitch; tremaining -= ddsRowPitch; } pDestination += ddsSlicePitch; remaining -= ddsSlicePitch; } ++index; } if (d > 1) d >>= 1; } } break; default: blob.Release(); return E_FAIL; } return S_OK; } //------------------------------------------------------------------------------------- // Save a DDS file to disk //------------------------------------------------------------------------------------- _Use_decl_annotations_ HRESULT DirectX::SaveToDDSFile( const Image* images, size_t nimages, const TexMetadata& metadata, DWORD flags, const wchar_t* szFile) { if (!szFile) return E_INVALIDARG; // Create DDS Header const size_t MAX_HEADER_SIZE = sizeof(uint32_t) + sizeof(DDS_HEADER) + sizeof(DDS_HEADER_DXT10); uint8_t header[MAX_HEADER_SIZE]; size_t required; HRESULT hr = _EncodeDDSHeader(metadata, flags, header, MAX_HEADER_SIZE, required); if (FAILED(hr)) return hr; // Create file and write header #if (_WIN32_WINNT >= _WIN32_WINNT_WIN8) ScopedHandle hFile(safe_handle(CreateFile2(szFile, GENERIC_WRITE | DELETE, 0, CREATE_ALWAYS, nullptr))); #else ScopedHandle hFile(safe_handle(CreateFileW(szFile, GENERIC_WRITE | DELETE, 0, nullptr, CREATE_ALWAYS, 0, nullptr))); #endif if (!hFile) { return HRESULT_FROM_WIN32(GetLastError()); } auto_delete_file delonfail(hFile.get()); DWORD bytesWritten; if (!WriteFile(hFile.get(), header, static_cast(required), &bytesWritten, nullptr)) { return HRESULT_FROM_WIN32(GetLastError()); } if (bytesWritten != required) { return E_FAIL; } // Write images switch (metadata.dimension) { case DDS_DIMENSION_TEXTURE1D: case DDS_DIMENSION_TEXTURE2D: { size_t index = 0; for (size_t item = 0; item < metadata.arraySize; ++item) { for (size_t level = 0; level < metadata.mipLevels; ++level, ++index) { if (index >= nimages) return E_FAIL; if (!images[index].pixels) return E_POINTER; assert(images[index].rowPitch > 0); assert(images[index].slicePitch > 0); size_t ddsRowPitch, ddsSlicePitch; ComputePitch(metadata.format, images[index].width, images[index].height, ddsRowPitch, ddsSlicePitch, CP_FLAGS_NONE); if (images[index].slicePitch == ddsSlicePitch) { if (!WriteFile(hFile.get(), images[index].pixels, static_cast(ddsSlicePitch), &bytesWritten, nullptr)) { return HRESULT_FROM_WIN32(GetLastError()); } if (bytesWritten != ddsSlicePitch) { return E_FAIL; } } else { size_t rowPitch = images[index].rowPitch; if (rowPitch < ddsRowPitch) { // DDS uses 1-byte alignment, so if this is happening then the input pitch isn't actually a full line of data return E_FAIL; } const uint8_t * __restrict sPtr = images[index].pixels; size_t lines = ComputeScanlines(metadata.format, images[index].height); for (size_t j = 0; j < lines; ++j) { if (!WriteFile(hFile.get(), sPtr, static_cast(ddsRowPitch), &bytesWritten, nullptr)) { return HRESULT_FROM_WIN32(GetLastError()); } if (bytesWritten != ddsRowPitch) { return E_FAIL; } sPtr += rowPitch; } } } } } break; case DDS_DIMENSION_TEXTURE3D: { if (metadata.arraySize != 1) return E_FAIL; size_t d = metadata.depth; size_t index = 0; for (size_t level = 0; level < metadata.mipLevels; ++level) { for (size_t slice = 0; slice < d; ++slice, ++index) { if (index >= nimages) return E_FAIL; if (!images[index].pixels) return E_POINTER; assert(images[index].rowPitch > 0); assert(images[index].slicePitch > 0); size_t ddsRowPitch, ddsSlicePitch; ComputePitch(metadata.format, images[index].width, images[index].height, ddsRowPitch, ddsSlicePitch, CP_FLAGS_NONE); if (images[index].slicePitch == ddsSlicePitch) { if (!WriteFile(hFile.get(), images[index].pixels, static_cast(ddsSlicePitch), &bytesWritten, nullptr)) { return HRESULT_FROM_WIN32(GetLastError()); } if (bytesWritten != ddsSlicePitch) { return E_FAIL; } } else { size_t rowPitch = images[index].rowPitch; if (rowPitch < ddsRowPitch) { // DDS uses 1-byte alignment, so if this is happening then the input pitch isn't actually a full line of data return E_FAIL; } const uint8_t * __restrict sPtr = images[index].pixels; size_t lines = ComputeScanlines(metadata.format, images[index].height); for (size_t j = 0; j < lines; ++j) { if (!WriteFile(hFile.get(), sPtr, static_cast(ddsRowPitch), &bytesWritten, nullptr)) { return HRESULT_FROM_WIN32(GetLastError()); } if (bytesWritten != ddsRowPitch) { return E_FAIL; } sPtr += rowPitch; } } } if (d > 1) d >>= 1; } } break; default: return E_FAIL; } delonfail.clear(); return S_OK; }