crossxtex/DirectXTex/DirectXTexDDS.cpp

2091 lines
79 KiB
C++

//-------------------------------------------------------------------------------------
// 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;
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_RGB, 0, 32, 0x000003ff, 0x000ffc00, 0x3ff00000, 0xc0000000 } }, // D3DFMT_A2R10G10B10 (D3DX reversal issue workaround)
{ DXGI_FORMAT_R10G10B10A2_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_RGB, 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_RGB, 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_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_PAL8, 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_LUMINANCE, 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
DXGI_FORMAT GetDXGIFormat(const DDS_PIXELFORMAT& ddpf, DWORD flags, _Inout_ DWORD& convFlags)
{
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 (ddpf.dwFlags & 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 (ddpf.dwRGBBitCount == entry->ddpf.dwRGBBitCount)
{
// RGB, RGBA, ALPHA, LUMINANCE
if (ddpf.dwRBitMask == entry->ddpf.dwRBitMask
&& ddpf.dwGBitMask == entry->ddpf.dwGBitMask
&& ddpf.dwBBitMask == entry->ddpf.dwBBitMask
&& ddpf.dwABitMask == entry->ddpf.dwABitMask)
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;
}
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<const uint32_t*>(pSource);
if (dwMagicNumber != DDS_MAGIC)
{
return E_FAIL;
}
auto pHeader = reinterpret_cast<const DDS_HEADER*>((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 DDS_HEADER_DXT10*>((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(TEX_MISC_TEXTURECUBE == 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(TEX_MISC2_ALPHA_MODE_MASK == DDS_MISC_FLAGS2_ALPHA_MODE_MASK, "DDS header mismatch");
static_assert(TEX_ALPHA_MODE_UNKNOWN == DDS_ALPHA_MODE_UNKNOWN, "DDS header mismatch");
static_assert(TEX_ALPHA_MODE_STRAIGHT == DDS_ALPHA_MODE_STRAIGHT, "DDS header mismatch");
static_assert(TEX_ALPHA_MODE_PREMULTIPLIED == DDS_ALPHA_MODE_PREMULTIPLIED, "DDS header mismatch");
static_assert(TEX_ALPHA_MODE_OPAQUE == DDS_ALPHA_MODE_OPAQUE, "DDS header mismatch");
static_assert(TEX_ALPHA_MODE_CUSTOM == 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->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;
}
}
}
// 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;
}
}
// 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;
}
}
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;
}
}
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<uint32_t*>(pDestination) = DDS_MAGIC;
auto header = reinterpret_cast<DDS_HEADER*>(reinterpret_cast<uint8_t*>(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 > UINT32_MAX)
return E_INVALIDARG;
header->dwMipMapCount = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(metadata.height);
header->dwWidth = static_cast<uint32_t>(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 > UINT32_MAX)
return E_INVALIDARG;
header->dwFlags |= DDS_HEADER_FLAGS_VOLUME;
header->dwCaps2 |= DDS_FLAGS_VOLUME;
header->dwHeight = static_cast<uint32_t>(metadata.height);
header->dwWidth = static_cast<uint32_t>(metadata.width);
header->dwDepth = static_cast<uint32_t>(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<uint32_t>(slicePitch);
}
else
{
header->dwFlags |= DDS_HEADER_FLAGS_PITCH;
header->dwPitchOrLinearSize = static_cast<uint32_t>(rowPitch);
}
if (ddpf.dwSize == 0)
{
memcpy_s(&header->ddspf, sizeof(header->ddspf), &DDSPF_DX10, sizeof(DDS_PIXELFORMAT));
auto ext = reinterpret_cast<DDS_HEADER_DXT10*>(reinterpret_cast<uint8_t*>(header) + sizeof(DDS_HEADER));
assert(ext);
memset(ext, 0, sizeof(DDS_HEADER_DXT10));
ext->dxgiFormat = metadata.format;
ext->resourceDimension = metadata.dimension;
if (metadata.arraySize > UINT32_MAX)
return E_INVALIDARG;
static_assert(TEX_MISC_TEXTURECUBE == 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<UINT>(metadata.arraySize / 6);
}
else
{
ext->arraySize = static_cast<UINT>(metadata.arraySize);
}
static_assert(TEX_MISC2_ALPHA_MODE_MASK == DDS_MISC_FLAGS2_ALPHA_MODE_MASK, "DDS header mismatch");
static_assert(TEX_ALPHA_MODE_UNKNOWN == DDS_ALPHA_MODE_UNKNOWN, "DDS header mismatch");
static_assert(TEX_ALPHA_MODE_STRAIGHT == DDS_ALPHA_MODE_STRAIGHT, "DDS header mismatch");
static_assert(TEX_ALPHA_MODE_PREMULTIPLIED == DDS_ALPHA_MODE_PREMULTIPLIED, "DDS header mismatch");
static_assert(TEX_ALPHA_MODE_OPAQUE == DDS_ALPHA_MODE_OPAQUE, "DDS header mismatch");
static_assert(TEX_ALPHA_MODE_CUSTOM == 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<const uint8_t*>(pSource);
uint32_t * __restrict dPtr = reinterpret_cast<uint32_t*>(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<const uint8_t*>(pSource);
uint32_t * __restrict dPtr = reinterpret_cast<uint32_t*>(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<const uint8_t*>(pSource);
uint16_t * __restrict dPtr = reinterpret_cast<uint16_t*>(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;
}
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<const uint16_t*>(pSource);
uint32_t * __restrict dPtr = reinterpret_cast<uint32_t*>(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<const uint8_t*>(pSource);
uint32_t * __restrict dPtr = reinterpret_cast<uint32_t*>(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<const uint16_t*>(pSource);
uint32_t * __restrict dPtr = reinterpret_cast<uint32_t*>(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<const uint8_t*>(pSource);
uint16_t * __restrict dPtr = reinterpret_cast<uint16_t*>(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<const uint8_t*>(pSource);
uint32_t * __restrict dPtr = reinterpret_cast<uint32_t*>(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;
}
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<const uint16_t*>(pSource);
uint32_t * __restrict dPtr = reinterpret_cast<uint32_t*>(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<const uint8_t*>(pSource);
uint32_t * __restrict dPtr = reinterpret_cast<uint32_t*>(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<const uint16_t*>(pSource);
uint64_t * __restrict dPtr = reinterpret_cast<uint64_t*>(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<const uint16_t*>(pSource);
uint32_t * __restrict dPtr = reinterpret_cast<uint32_t*>(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;
}
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<Image[]> 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<const uint8_t*>(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<size_t>(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<size_t>(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<size_t>(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<const uint8_t*>(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<size_t>(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<size_t>(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<const uint32_t*>(reinterpret_cast<const uint8_t*>(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<const void*>(reinterpret_cast<const uint8_t*>(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<uint32_t[]> 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<uint8_t[]> 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<DWORD>(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<uint8_t*>(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 = reinterpret_cast<const uint8_t*>(images[index].pixels);
uint8_t * __restrict dPtr = reinterpret_cast<uint8_t*>(pDestination);
size_t lines = ComputeScanlines(metadata.format, images[index].height);
size_t csize = std::min<size_t>(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 = reinterpret_cast<const uint8_t*>(images[index].pixels);
uint8_t * __restrict dPtr = reinterpret_cast<uint8_t*>(pDestination);
size_t lines = ComputeScanlines(metadata.format, images[index].height);
size_t csize = std::min<size_t>(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<DWORD>(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<DWORD>(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 = reinterpret_cast<const uint8_t*>(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<DWORD>(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<DWORD>(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 = reinterpret_cast<const uint8_t*>(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<DWORD>(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;
}