//------------------------------------------------------------------------------------- // 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" namespace { class auto_delete_file { public: auto_delete_file(HANDLE hFile) : m_handle(hFile) {} ~auto_delete_file() { if (m_handle) { FILE_DISPOSITION_INFO info = {0}; info.DeleteFile = TRUE; (void)SetFileInformationByHandle(m_handle, FileDispositionInfo, &info, sizeof(info)); } } void clear() { m_handle = 0; } private: HANDLE m_handle; auto_delete_file(const auto_delete_file&) DIRECTX_CTOR_DELETE; auto_delete_file& operator=(const auto_delete_file&) DIRECTX_CTOR_DELETE; }; } namespace DirectX { //------------------------------------------------------------------------------------- // 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 static 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 //------------------------------------------------------------------------------------- static HRESULT _DecodeDDSHeader( _In_reads_bytes_(size) LPCVOID 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( 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 _EncodeDDSHeader( const TexMetadata& metadata, DWORD flags, LPVOID 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(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; #ifdef _M_X64 if ( metadata.mipLevels > 0xFFFFFFFF ) return E_INVALIDARG; #endif header->dwMipMapCount = static_cast( metadata.mipLevels ); if ( header->dwMipMapCount > 1 ) header->dwCaps |= DDS_SURFACE_FLAGS_MIPMAP; } switch( metadata.dimension ) { case TEX_DIMENSION_TEXTURE1D: #ifdef _M_X64 if ( metadata.width > 0xFFFFFFFF ) return E_INVALIDARG; #endif header->dwWidth = static_cast( metadata.width ); header->dwHeight = header->dwDepth = 1; break; case TEX_DIMENSION_TEXTURE2D: #ifdef _M_X64 if ( metadata.height > 0xFFFFFFFF || metadata.width > 0xFFFFFFFF) return E_INVALIDARG; #endif 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: #ifdef _M_X64 if ( metadata.height > 0xFFFFFFFF || metadata.width > 0xFFFFFFFF || metadata.depth > 0xFFFFFFFF ) return E_INVALIDARG; #endif 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 ); #ifdef _M_X64 if ( slicePitch > 0xFFFFFFFF || rowPitch > 0xFFFFFFFF ) return E_FAIL; #endif 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; #ifdef _M_X64 if ( metadata.arraySize > 0xFFFFFFFF ) return E_INVALIDARG; #endif 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( metadata.arraySize / 6 ); } else { ext->arraySize = static_cast( 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; } //------------------------------------------------------------------------------------- // 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 static 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) static bool _LegacyExpandScanline( _Out_writes_bytes_(outSize) LPVOID pDestination, size_t outSize, _In_ DXGI_FORMAT outFormat, _In_reads_bytes_(inSize) LPCVOID 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; } 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; } 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; } return false; } //------------------------------------------------------------------------------------- // Converts or copies image data from pPixels into scratch image data //------------------------------------------------------------------------------------- static 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; } assert( pixelSize <= size ); std::unique_ptr timages( new (std::nothrow) Image[nimages] ); if ( !timages ) { return E_OUTOFMEMORY; } if ( !_SetupImageArray( (uint8_t*)pPixels, size, 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 ) { 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 ); } 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; 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 ); } 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; } static 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 GetMetadataFromDDSMemory( LPCVOID 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 GetMetadataFromDDSFile( LPCWSTR 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, 0 ) ) ); #else ScopedHandle hFile( safe_handle( CreateFileW( szFile, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0 ) ) ); #endif if ( !hFile ) { return HRESULT_FROM_WIN32( GetLastError() ); } // Get the file size LARGE_INTEGER fileSize = {0}; #if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) FILE_STANDARD_INFO fileInfo; if ( !GetFileInformationByHandleEx( hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo) ) ) { return HRESULT_FROM_WIN32( GetLastError() ); } fileSize = fileInfo.EndOfFile; #else if ( !GetFileSizeEx( hFile.get(), &fileSize ) ) { return HRESULT_FROM_WIN32( GetLastError() ); } #endif // File is too big for 32-bit allocation, so reject read (4 GB should be plenty large enough for a valid DDS file) if ( fileSize.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 ( fileSize.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, 0 ) ) { 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 LoadFromDDSMemory( LPCVOID 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; auto pPixels = reinterpret_cast( reinterpret_cast(pSource) + offset ); assert( pPixels ); hr = _CopyImage( pPixels, size - offset, mdata, (flags & DDS_FLAGS_LEGACY_DWORD) ? CP_FLAGS_LEGACY_DWORD : CP_FLAGS_NONE, 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 LoadFromDDSFile( LPCWSTR 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, 0 ) ) ); #else ScopedHandle hFile( safe_handle ( CreateFileW( szFile, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0 ) ) ); #endif if ( !hFile ) { return HRESULT_FROM_WIN32( GetLastError() ); } // Get the file size LARGE_INTEGER fileSize = {0}; #if (_WIN32_WINNT >= _WIN32_WINNT_VISTA) FILE_STANDARD_INFO fileInfo; if ( !GetFileInformationByHandleEx( hFile.get(), FileStandardInfo, &fileInfo, sizeof(fileInfo) ) ) { return HRESULT_FROM_WIN32( GetLastError() ); } fileSize = fileInfo.EndOfFile; #else if ( !GetFileSizeEx( hFile.get(), &fileSize ) ) { return HRESULT_FROM_WIN32( GetLastError() ); } #endif // File is too big for 32-bit allocation, so reject read (4 GB should be plenty large enough for a valid DDS file) if ( fileSize.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 ( fileSize.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, 0 ) ) { 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, 0 ) ) { return HRESULT_FROM_WIN32( GetLastError() ); } if ( bytesRead != (256 * sizeof(uint32_t)) ) { return E_FAIL; } offset += ( 256 * sizeof(uint32_t) ); } DWORD remaining = fileSize.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) ) { 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, 0 ) ) { image.Release(); return HRESULT_FROM_WIN32( GetLastError() ); } if ( bytesRead != remaining ) { image.Release(); return E_FAIL; } hr = _CopyImage( temp.get(), remaining, mdata, (flags & DDS_FLAGS_LEGACY_DWORD) ? CP_FLAGS_LEGACY_DWORD : CP_FLAGS_NONE, convFlags, pal8.get(), image ); if ( FAILED(hr) ) { image.Release(); return hr; } } else { if ( remaining < image.GetPixelsSize() ) { image.Release(); return E_FAIL; } if ( !ReadFile( hFile.get(), image.GetPixels(), static_cast( image.GetPixelsSize() ), &bytesRead, 0 ) ) { 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 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 = reinterpret_cast(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 = reinterpret_cast(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 SaveToDDSFile( const Image* images, size_t nimages, const TexMetadata& metadata, DWORD flags, LPCWSTR 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, 0 ) ) ); #else ScopedHandle hFile( safe_handle( CreateFileW( szFile, GENERIC_WRITE | DELETE, 0, 0, CREATE_ALWAYS, 0, 0 ) ) ); #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, 0 ) ) { 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, 0 ) ) { 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(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, 0 ) ) { 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, 0 ) ) { 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(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, 0 ) ) { 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; } }; // namespace