diff --git a/DirectXTex/DirectXTex.h b/DirectXTex/DirectXTex.h index e4e71ff..1edb16b 100644 --- a/DirectXTex/DirectXTex.h +++ b/DirectXTex/DirectXTex.h @@ -221,6 +221,9 @@ namespace DirectX DDS_FLAGS_FORCE_DX9_LEGACY = 0x40000, // Force use of legacy header for DDS writer (will fail if unable to write as such) + DDS_FLAGS_FORCE_DXT5_RXGB = 0x80000, + // Force use of 'RXGB' instead of 'DXT5' for DDS write of BC3_UNORM data + DDS_FLAGS_ALLOW_LARGE_FILES = 0x1000000, // Enables the loader to read large dimension .dds files (i.e. greater than known hardware requirements) }; diff --git a/DirectXTex/DirectXTexDDS.cpp b/DirectXTex/DirectXTexDDS.cpp index 054b674..9c042b4 100644 --- a/DirectXTex/DirectXTexDDS.cpp +++ b/DirectXTex/DirectXTexDDS.cpp @@ -66,6 +66,19 @@ namespace { DXGI_FORMAT_BC2_UNORM, CONV_FLAGS_PMALPHA, DDSPF_DXT2 }, // D3DFMT_DXT2 { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_PMALPHA, DDSPF_DXT4 }, // D3DFMT_DXT4 + // These DXT5 variants have various swizzled channels. They are returned 'as is' to the client as BC3. + { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('A', '2', 'D', '5'), 0, 0, 0, 0, 0 } }, + { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('x', 'G', 'B', 'R'), 0, 0, 0, 0, 0 } }, + { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('R', 'x', 'B', 'G'), 0, 0, 0, 0, 0 } }, + { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('R', 'B', 'x', 'G'), 0, 0, 0, 0, 0 } }, + { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('x', 'R', 'B', 'G'), 0, 0, 0, 0, 0 } }, + { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('R', 'G', 'x', 'B'), 0, 0, 0, 0, 0 } }, + { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('x', 'G', 'x', 'R'), 0, 0, 0, 0, 0 } }, + { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('G', 'X', 'R', 'B'), 0, 0, 0, 0, 0 } }, + { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('G', 'R', 'X', 'B'), 0, 0, 0, 0, 0 } }, + { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('R', 'X', 'G', 'B'), 0, 0, 0, 0, 0 } }, + { DXGI_FORMAT_BC3_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('B', 'R', 'G', 'X'), 0, 0, 0, 0, 0 } }, + { 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 }, @@ -73,6 +86,7 @@ namespace { 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_BC5_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('A', '2', 'X', 'Y'), 0, 0, 0, 0, 0 } }, { DXGI_FORMAT_BC6H_UF16, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('B', 'C', '6', 'H'), 0, 0, 0, 0, 0 } }, { DXGI_FORMAT_BC7_UNORM, CONV_FLAGS_NONE, { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('B', 'C', '7', 'L'), 0, 0, 0, 0, 0 } }, @@ -614,7 +628,6 @@ HRESULT DirectX::EncodeDDSHeader( case DXGI_FORMAT_G8R8_G8B8_UNORM: memcpy(&ddpf, &DDSPF_G8R8_G8B8, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_BC1_UNORM: memcpy(&ddpf, &DDSPF_DXT1, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_BC2_UNORM: memcpy(&ddpf, metadata.IsPMAlpha() ? (&DDSPF_DXT2) : (&DDSPF_DXT3), sizeof(DDS_PIXELFORMAT)); break; - case DXGI_FORMAT_BC3_UNORM: memcpy(&ddpf, metadata.IsPMAlpha() ? (&DDSPF_DXT4) : (&DDSPF_DXT5), sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_BC4_SNORM: memcpy(&ddpf, &DDSPF_BC4_SNORM, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_BC5_SNORM: memcpy(&ddpf, &DDSPF_BC5_SNORM, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_B5G6R5_UNORM: memcpy(&ddpf, &DDSPF_R5G6B5, sizeof(DDS_PIXELFORMAT)); break; @@ -627,6 +640,14 @@ HRESULT DirectX::EncodeDDSHeader( case DXGI_FORMAT_B4G4R4A4_UNORM: memcpy(&ddpf, &DDSPF_A4R4G4B4, sizeof(DDS_PIXELFORMAT)); break; // DXGI 1.2 case DXGI_FORMAT_YUY2: memcpy(&ddpf, &DDSPF_YUY2, sizeof(DDS_PIXELFORMAT)); break; // DXGI 1.2 + case DXGI_FORMAT_BC3_UNORM: + memcpy(&ddpf, metadata.IsPMAlpha() ? (&DDSPF_DXT4) : (&DDSPF_DXT5), sizeof(DDS_PIXELFORMAT)); + if (flags & DDS_FLAGS_FORCE_DXT5_RXGB) + { + ddpf.fourCC = MAKEFOURCC('R', 'X', 'G', 'B'); + } + break; + // Legacy D3DX formats using D3DFMT enum value as FourCC case DXGI_FORMAT_R32G32B32A32_FLOAT: ddpf.size = sizeof(DDS_PIXELFORMAT); ddpf.flags = DDS_FOURCC; ddpf.fourCC = 116; // D3DFMT_A32B32G32R32F diff --git a/Texconv/texconv.cpp b/Texconv/texconv.cpp index 5cb80b6..d82a092 100644 --- a/Texconv/texconv.cpp +++ b/Texconv/texconv.cpp @@ -147,6 +147,12 @@ namespace ROTATE_P3D65_TO_709, }; + enum + { + FORMAT_DXT5_NM = 1, + FORMAT_DXT5_RXGB, + }; + static_assert(OPT_MAX <= 64, "dwOptions is a unsigned int bitfield"); struct SConversion @@ -338,6 +344,15 @@ namespace { nullptr, DXGI_FORMAT_UNKNOWN } }; + const SValue g_pSpecialFormats[] = + { + { L"BC3n", FORMAT_DXT5_NM }, + { L"DXT5nm", FORMAT_DXT5_NM }, + { L"RXGB", FORMAT_DXT5_RXGB }, + + { nullptr, 0 } + }; + const SValue g_pReadOnlyFormats[] = { DEFFMT(R32G32B32A32_TYPELESS), @@ -997,6 +1012,8 @@ namespace PrintList(13, g_pFormats); wprintf(L" "); PrintList(13, g_pFormatAliases); + wprintf(L" "); + PrintList(13, g_pSpecialFormats); wprintf(L"\n : "); PrintList(13, g_pFilters); @@ -1428,6 +1445,8 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) float paperWhiteNits = 200.f; float preserveAlphaCoverageRef = 0.0f; bool keepRecursiveDirs = false; + bool dxt5nm = false; + bool dxt5rxgb = false; uint32_t swizzleElements[4] = { 0, 1, 2, 3 }; uint32_t zeroElements[4] = {}; uint32_t oneElements[4] = {}; @@ -1581,10 +1600,24 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) format = static_cast(LookupByName(pValue, g_pFormatAliases)); if (!format) { - wprintf(L"Invalid value specified with -f (%ls)\n", pValue); - wprintf(L"\n"); - PrintUsage(); - return 1; + switch (LookupByName(pValue, g_pSpecialFormats)) + { + case FORMAT_DXT5_NM: + format = DXGI_FORMAT_BC3_UNORM; + dxt5nm = true; + break; + + case FORMAT_DXT5_RXGB: + format = DXGI_FORMAT_BC3_UNORM; + dxt5rxgb = true; + break; + + default: + wprintf(L"Invalid value specified with -f (%ls)\n", pValue); + wprintf(L"\n"); + PrintUsage(); + return 1; + } } } break; @@ -3454,36 +3487,12 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) } // --- Compress ---------------------------------------------------------------- - if (IsCompressed(tformat) && (FileType == CODEC_DDS)) + if (FileType == CODEC_DDS) { - if (cimage && (cimage->GetMetadata().format == tformat)) + if (dxt5nm || dxt5rxgb) { - // We never changed the image and it was already compressed in our desired format, use original data - image.reset(cimage.release()); - - auto& tinfo = image->GetMetadata(); - - if ((tinfo.width % 4) != 0 || (tinfo.height % 4) != 0) - { - non4bc = true; - } - - info.format = tinfo.format; - assert(info.width == tinfo.width); - assert(info.height == tinfo.height); - assert(info.depth == tinfo.depth); - assert(info.arraySize == tinfo.arraySize); - assert(info.mipLevels == tinfo.mipLevels); - assert(info.miscFlags == tinfo.miscFlags); - assert(info.dimension == tinfo.dimension); - } - else - { - cimage.reset(); - - auto img = image->GetImage(0, 0, 0); - assert(img); - const size_t nimg = image->GetImageCount(); + // Prepare for DXT5nm/RXGB + assert(tformat == DXGI_FORMAT_BC3_UNORM); std::unique_ptr timage(new (std::nothrow) ScratchImage); if (!timage) @@ -3492,93 +3501,188 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) return 1; } - bool bc6hbc7 = false; - switch (tformat) + if (dxt5nm) { - case DXGI_FORMAT_BC6H_TYPELESS: - case DXGI_FORMAT_BC6H_UF16: - case DXGI_FORMAT_BC6H_SF16: - case DXGI_FORMAT_BC7_TYPELESS: - case DXGI_FORMAT_BC7_UNORM: - case DXGI_FORMAT_BC7_UNORM_SRGB: - bc6hbc7 = true; - - { - static bool s_tryonce = false; - - if (!s_tryonce) + hr = TransformImage(image->GetImages(), image->GetImageCount(), image->GetMetadata(), + [=](XMVECTOR* outPixels, const XMVECTOR* inPixels, size_t w, size_t y) { - s_tryonce = true; + UNREFERENCED_PARAMETER(y); - if (!(dwOptions & (uint64_t(1) << OPT_NOGPU))) + for (size_t j = 0; j < w; ++j) { - if (!CreateDevice(adapter, pDevice.GetAddressOf())) - wprintf(L"\nWARNING: DirectCompute is not available, using BC6H / BC7 CPU codec\n"); + outPixels[j] = XMVectorPermute<4, 1, 5, 0>(inPixels[j], g_XMIdentityR0); } - else - { - wprintf(L"\nWARNING: using BC6H / BC7 CPU codec\n"); - } - } + }, *timage); + if (FAILED(hr)) + { + wprintf(L" FAILED [DXT5nm] (%08X%ls)\n", static_cast(hr), GetErrorDesc(hr)); + return 1; } - break; - - default: - break; - } - - TEX_COMPRESS_FLAGS cflags = dwCompress; - #ifdef _OPENMP - if (!(dwOptions & (uint64_t(1) << OPT_FORCE_SINGLEPROC))) - { - cflags |= TEX_COMPRESS_PARALLEL; - } - #endif - - if ((img->width % 4) != 0 || (img->height % 4) != 0) - { - non4bc = true; - } - - if (bc6hbc7 && pDevice) - { - hr = Compress(pDevice.Get(), img, nimg, info, tformat, dwCompress | dwSRGB, alphaWeight, *timage); } else { - hr = Compress(img, nimg, info, tformat, cflags | dwSRGB, alphaThreshold, *timage); - } - if (FAILED(hr)) - { - wprintf(L" FAILED [compress] (%08X%ls)\n", static_cast(hr), GetErrorDesc(hr)); - retVal = 1; - continue; + hr = TransformImage(image->GetImages(), image->GetImageCount(), image->GetMetadata(), + [=](XMVECTOR* outPixels, const XMVECTOR* inPixels, size_t w, size_t y) + { + UNREFERENCED_PARAMETER(y); + + for (size_t j = 0; j < w; ++j) + { + outPixels[j] = XMVectorSwizzle<3, 1, 2, 0>(inPixels[j]); + } + }, *timage); + if (FAILED(hr)) + { + wprintf(L" FAILED [DXT5 RXGB] (%08X%ls)\n", static_cast(hr), GetErrorDesc(hr)); + return 1; + } } + #ifndef NDEBUG auto& tinfo = timage->GetMetadata(); + #endif - info.format = tinfo.format; assert(info.width == tinfo.width); assert(info.height == tinfo.height); assert(info.depth == tinfo.depth); assert(info.arraySize == tinfo.arraySize); assert(info.mipLevels == tinfo.mipLevels); assert(info.miscFlags == tinfo.miscFlags); + assert(info.format == tinfo.format); assert(info.dimension == tinfo.dimension); image.swap(timage); + cimage.reset(); + } + + if (IsCompressed(tformat)) + { + if (cimage && (cimage->GetMetadata().format == tformat)) + { + // We never changed the image and it was already compressed in our desired format, use original data + image.reset(cimage.release()); + + auto& tinfo = image->GetMetadata(); + + if ((tinfo.width % 4) != 0 || (tinfo.height % 4) != 0) + { + non4bc = true; + } + + info.format = tinfo.format; + assert(info.width == tinfo.width); + assert(info.height == tinfo.height); + assert(info.depth == tinfo.depth); + assert(info.arraySize == tinfo.arraySize); + assert(info.mipLevels == tinfo.mipLevels); + assert(info.miscFlags == tinfo.miscFlags); + assert(info.dimension == tinfo.dimension); + } + else + { + cimage.reset(); + + auto img = image->GetImage(0, 0, 0); + assert(img); + const size_t nimg = image->GetImageCount(); + + std::unique_ptr timage(new (std::nothrow) ScratchImage); + if (!timage) + { + wprintf(L"\nERROR: Memory allocation failed\n"); + return 1; + } + + bool bc6hbc7 = false; + switch (tformat) + { + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + bc6hbc7 = true; + + { + static bool s_tryonce = false; + + if (!s_tryonce) + { + s_tryonce = true; + + if (!(dwOptions & (uint64_t(1) << OPT_NOGPU))) + { + if (!CreateDevice(adapter, pDevice.GetAddressOf())) + wprintf(L"\nWARNING: DirectCompute is not available, using BC6H / BC7 CPU codec\n"); + } + else + { + wprintf(L"\nWARNING: using BC6H / BC7 CPU codec\n"); + } + } + } + break; + + default: + break; + } + + TEX_COMPRESS_FLAGS cflags = dwCompress; + #ifdef _OPENMP + if (!(dwOptions & (uint64_t(1) << OPT_FORCE_SINGLEPROC))) + { + cflags |= TEX_COMPRESS_PARALLEL; + } + #endif + + if ((img->width % 4) != 0 || (img->height % 4) != 0) + { + non4bc = true; + } + + if (bc6hbc7 && pDevice) + { + hr = Compress(pDevice.Get(), img, nimg, info, tformat, dwCompress | dwSRGB, alphaWeight, *timage); + } + else + { + hr = Compress(img, nimg, info, tformat, cflags | dwSRGB, alphaThreshold, *timage); + } + if (FAILED(hr)) + { + wprintf(L" FAILED [compress] (%08X%ls)\n", static_cast(hr), GetErrorDesc(hr)); + retVal = 1; + continue; + } + + auto& tinfo = timage->GetMetadata(); + + info.format = tinfo.format; + assert(info.width == tinfo.width); + assert(info.height == tinfo.height); + assert(info.depth == tinfo.depth); + assert(info.arraySize == tinfo.arraySize); + assert(info.mipLevels == tinfo.mipLevels); + assert(info.miscFlags == tinfo.miscFlags); + assert(info.dimension == tinfo.dimension); + + image.swap(timage); + } } } - else - { - cimage.reset(); - } + + cimage.reset(); // --- Set alpha mode ---------------------------------------------------------- if (HasAlpha(info.format) && info.format != DXGI_FORMAT_A8_UNORM) { - if (image->IsAlphaAllOpaque()) + if (dxt5nm || dxt5rxgb) + { + info.SetAlphaMode(TEX_ALPHA_MODE_CUSTOM); + } + else if (image->IsAlphaAllOpaque()) { info.SetAlphaMode(TEX_ALPHA_MODE_OPAQUE); } @@ -3693,6 +3797,11 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) } else if (dwOptions & (uint64_t(1) << OPT_USE_DX9)) { + if (dxt5rxgb) + { + ddsFlags |= DDS_FLAGS_FORCE_DXT5_RXGB; + } + ddsFlags |= DDS_FLAGS_FORCE_DX9_LEGACY; }