Add flag to write BGRX DDS content using R8G8B8 24bpp legacy DX9 (#641)

This commit is contained in:
Chuck Walbourn 2025-10-27 15:48:01 -07:00 committed by GitHub
parent f04d5cb47f
commit f180374ba5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 207 additions and 5 deletions

View File

@ -268,6 +268,9 @@ namespace DirectX
DDS_FLAGS_FORCE_DXT5_RXGB = 0x80000, DDS_FLAGS_FORCE_DXT5_RXGB = 0x80000,
// Force use of 'RXGB' instead of 'DXT5' for DDS write of BC3_UNORM data // Force use of 'RXGB' instead of 'DXT5' for DDS write of BC3_UNORM data
DDS_FLAGS_FORCE_24BPP_RGB = 0x100000,
// Force use of 'RGB' 24bpp legacy Direct3D 9 format for DDS write of B8G8R8X8_UNORM data
DDS_FLAGS_ALLOW_LARGE_FILES = 0x1000000, DDS_FLAGS_ALLOW_LARGE_FILES = 0x1000000,
// Enables the loader to read large dimension .dds files (i.e. greater than known hardware requirements) // Enables the loader to read large dimension .dds files (i.e. greater than known hardware requirements)
}; };

View File

@ -669,6 +669,22 @@ namespace
return S_OK; return S_OK;
} }
inline void CopyScanline24bpp(
_Out_writes_bytes_(width * 3) uint8_t* pDestination,
_In_reads_bytes_(width * 4) const uint8_t* pSource,
size_t width) noexcept
{
for (size_t x = 0; x < width; ++x)
{
pDestination[0] = pSource[0]; // B
pDestination[1] = pSource[1]; // G
pDestination[2] = pSource[2]; // R
pSource += 4;
pDestination += 3;
}
}
} }
@ -706,6 +722,7 @@ HRESULT DirectX::EncodeDDSHeader(
flags |= DDS_FLAGS_FORCE_DX10_EXT; flags |= DDS_FLAGS_FORCE_DX10_EXT;
} }
CP_FLAGS pitchFlags = CP_FLAGS_NONE;
DDS_PIXELFORMAT ddpf = {}; DDS_PIXELFORMAT ddpf = {};
if (!(flags & DDS_FLAGS_FORCE_DX10_EXT)) if (!(flags & DDS_FLAGS_FORCE_DX10_EXT))
{ {
@ -729,7 +746,18 @@ HRESULT DirectX::EncodeDDSHeader(
case DXGI_FORMAT_R8G8B8A8_SNORM: memcpy(&ddpf, &DDSPF_Q8W8V8U8, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_R8G8B8A8_SNORM: memcpy(&ddpf, &DDSPF_Q8W8V8U8, sizeof(DDS_PIXELFORMAT)); break;
case DXGI_FORMAT_R16G16_SNORM: memcpy(&ddpf, &DDSPF_V16U16, sizeof(DDS_PIXELFORMAT)); break; case DXGI_FORMAT_R16G16_SNORM: memcpy(&ddpf, &DDSPF_V16U16, sizeof(DDS_PIXELFORMAT)); break;
case DXGI_FORMAT_B8G8R8A8_UNORM: memcpy(&ddpf, &DDSPF_A8R8G8B8, sizeof(DDS_PIXELFORMAT)); break; // DXGI 1.1 case DXGI_FORMAT_B8G8R8A8_UNORM: memcpy(&ddpf, &DDSPF_A8R8G8B8, sizeof(DDS_PIXELFORMAT)); break; // DXGI 1.1
case DXGI_FORMAT_B8G8R8X8_UNORM: memcpy(&ddpf, &DDSPF_X8R8G8B8, sizeof(DDS_PIXELFORMAT)); break; // DXGI 1.1 case DXGI_FORMAT_B8G8R8X8_UNORM:
if (flags & DDS_FLAGS_FORCE_24BPP_RGB)
{
memcpy(&ddpf, &DDSPF_R8G8B8, sizeof(DDS_PIXELFORMAT)); // No DXGI equivalent
pitchFlags |= CP_FLAGS_24BPP;
}
else
{
memcpy(&ddpf, &DDSPF_X8R8G8B8, sizeof(DDS_PIXELFORMAT)); // DXGI 1.1
}
break;
case DXGI_FORMAT_B4G4R4A4_UNORM: memcpy(&ddpf, &DDSPF_A4R4G4B4, sizeof(DDS_PIXELFORMAT)); break; // DXGI 1.2 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_YUY2: memcpy(&ddpf, &DDSPF_YUY2, sizeof(DDS_PIXELFORMAT)); break; // DXGI 1.2
@ -922,7 +950,9 @@ HRESULT DirectX::EncodeDDSHeader(
} }
size_t rowPitch, slicePitch; size_t rowPitch, slicePitch;
HRESULT hr = ComputePitch(metadata.format, metadata.width, metadata.height, rowPitch, slicePitch, CP_FLAGS_NONE); HRESULT hr = ComputePitch(metadata.format,
metadata.width, metadata.height,
rowPitch, slicePitch, pitchFlags);
if (FAILED(hr)) if (FAILED(hr))
return hr; return hr;
@ -2371,6 +2401,9 @@ HRESULT DirectX::SaveToDDSMemory(
return hr; return hr;
bool fastpath = true; bool fastpath = true;
const bool use24bpp = ((metadata.format == DXGI_FORMAT_B8G8R8X8_UNORM)
&& (flags & DDS_FLAGS_FORCE_24BPP_RGB)
&& !(flags & (DDS_FLAGS_FORCE_DX10_EXT | DDS_FLAGS_FORCE_DX10_EXT_MISC2))) != 0;
for (size_t i = 0; i < nimages; ++i) for (size_t i = 0; i < nimages; ++i)
{ {
@ -2381,7 +2414,10 @@ HRESULT DirectX::SaveToDDSMemory(
return E_FAIL; return E_FAIL;
size_t ddsRowPitch, ddsSlicePitch; size_t ddsRowPitch, ddsSlicePitch;
hr = ComputePitch(metadata.format, images[i].width, images[i].height, ddsRowPitch, ddsSlicePitch, CP_FLAGS_NONE); hr = ComputePitch(metadata.format,
images[i].width, images[i].height,
ddsRowPitch, ddsSlicePitch,
(use24bpp) ? CP_FLAGS_24BPP : CP_FLAGS_NONE);
if (FAILED(hr)) if (FAILED(hr))
return hr; return hr;
@ -2447,6 +2483,40 @@ HRESULT DirectX::SaveToDDSMemory(
pDestination += pixsize; pDestination += pixsize;
remaining -= pixsize; remaining -= pixsize;
} }
else if (use24bpp)
{
size_t ddsRowPitch, ddsSlicePitch;
hr = ComputePitch(metadata.format, images[index].width, images[index].height, ddsRowPitch, ddsSlicePitch, CP_FLAGS_24BPP);
if (FAILED(hr))
{
blob.Release();
return hr;
}
const size_t rowPitch = images[index].rowPitch;
const uint8_t * __restrict sPtr = images[index].pixels;
uint8_t * __restrict dPtr = pDestination;
const size_t csize = std::min<size_t>(metadata.width * 3, ddsRowPitch);
size_t tremaining = remaining;
for (size_t j = 0; j < images[index].height; ++j)
{
if (tremaining < csize)
{
blob.Release();
return E_FAIL;
}
CopyScanline24bpp(dPtr, sPtr, images[index].width);
sPtr += rowPitch;
dPtr += ddsRowPitch;
tremaining -= ddsRowPitch;
}
pDestination += ddsSlicePitch;
remaining -= ddsSlicePitch;
}
else else
{ {
size_t ddsRowPitch, ddsSlicePitch; size_t ddsRowPitch, ddsSlicePitch;
@ -2519,6 +2589,40 @@ HRESULT DirectX::SaveToDDSMemory(
pDestination += pixsize; pDestination += pixsize;
remaining -= pixsize; remaining -= pixsize;
} }
else if (use24bpp)
{
size_t ddsRowPitch, ddsSlicePitch;
hr = ComputePitch(metadata.format, images[index].width, images[index].height, ddsRowPitch, ddsSlicePitch, CP_FLAGS_24BPP);
if (FAILED(hr))
{
blob.Release();
return hr;
}
const size_t rowPitch = images[index].rowPitch;
const uint8_t * __restrict sPtr = images[index].pixels;
uint8_t * __restrict dPtr = pDestination;
const size_t csize = std::min<size_t>(metadata.width * 3, ddsRowPitch);
size_t tremaining = remaining;
for (size_t j = 0; j < images[index].height; ++j)
{
if (tremaining < csize)
{
blob.Release();
return E_FAIL;
}
CopyScanline24bpp(dPtr, sPtr, images[index].width);
sPtr += rowPitch;
dPtr += ddsRowPitch;
tremaining -= ddsRowPitch;
}
pDestination += ddsSlicePitch;
remaining -= ddsSlicePitch;
}
else else
{ {
size_t ddsRowPitch, ddsSlicePitch; size_t ddsRowPitch, ddsSlicePitch;
@ -2627,6 +2731,26 @@ HRESULT DirectX::SaveToDDSFile(
return E_FAIL; return E_FAIL;
#endif #endif
const bool use24bpp = ((metadata.format == DXGI_FORMAT_B8G8R8X8_UNORM)
&& (flags & DDS_FLAGS_FORCE_24BPP_RGB)
&& !(flags & (DDS_FLAGS_FORCE_DX10_EXT | DDS_FLAGS_FORCE_DX10_EXT_MISC2))) != 0;
std::unique_ptr<uint8_t[]> tempRow;
if (use24bpp)
{
uint64_t lineSize = uint64_t(metadata.width) * 3;
if (lineSize > UINT32_MAX)
{
return HRESULT_E_ARITHMETIC_OVERFLOW;
}
tempRow.reset(new (std::nothrow) uint8_t[static_cast<size_t>(lineSize)]);
if (!tempRow)
{
return E_OUTOFMEMORY;
}
}
// Write images // Write images
switch (static_cast<DDS_RESOURCE_DIMENSION>(metadata.dimension)) switch (static_cast<DDS_RESOURCE_DIMENSION>(metadata.dimension))
{ {
@ -2648,7 +2772,10 @@ HRESULT DirectX::SaveToDDSFile(
assert(images[index].slicePitch > 0); assert(images[index].slicePitch > 0);
size_t ddsRowPitch, ddsSlicePitch; size_t ddsRowPitch, ddsSlicePitch;
hr = ComputePitch(metadata.format, images[index].width, images[index].height, ddsRowPitch, ddsSlicePitch, CP_FLAGS_NONE); hr = ComputePitch(metadata.format,
images[index].width, images[index].height,
ddsRowPitch, ddsSlicePitch,
(use24bpp) ? CP_FLAGS_24BPP : CP_FLAGS_NONE);
if (FAILED(hr)) if (FAILED(hr))
return hr; return hr;
@ -2670,6 +2797,35 @@ HRESULT DirectX::SaveToDDSFile(
return E_FAIL; return E_FAIL;
#endif #endif
} }
else if (use24bpp)
{
const size_t rowPitch = images[index].rowPitch;
const uint8_t * __restrict sPtr = images[index].pixels;
assert(ddsRowPitch <= metadata.width * 3u);
for (size_t j = 0; j < images[index].height; ++j)
{
CopyScanline24bpp(tempRow.get(), sPtr, images[index].width);
#ifdef _WIN32
if (!WriteFile(hFile.get(), tempRow.get(), static_cast<DWORD>(ddsRowPitch), &bytesWritten, nullptr))
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (bytesWritten != ddsRowPitch)
{
return E_FAIL;
}
#else
outFile.write(reinterpret_cast<const char*>(tempRow.get()), static_cast<std::streamsize>(ddsRowPitch));
if (!outFile)
return E_FAIL;
#endif
sPtr += rowPitch;
}
}
else else
{ {
const size_t rowPitch = images[index].rowPitch; const size_t rowPitch = images[index].rowPitch;
@ -2733,7 +2889,10 @@ HRESULT DirectX::SaveToDDSFile(
assert(images[index].slicePitch > 0); assert(images[index].slicePitch > 0);
size_t ddsRowPitch, ddsSlicePitch; size_t ddsRowPitch, ddsSlicePitch;
hr = ComputePitch(metadata.format, images[index].width, images[index].height, ddsRowPitch, ddsSlicePitch, CP_FLAGS_NONE); hr = ComputePitch(metadata.format,
images[index].width, images[index].height,
ddsRowPitch, ddsSlicePitch,
(use24bpp) ? CP_FLAGS_24BPP : CP_FLAGS_NONE);
if (FAILED(hr)) if (FAILED(hr))
return hr; return hr;
@ -2755,6 +2914,34 @@ HRESULT DirectX::SaveToDDSFile(
return E_FAIL; return E_FAIL;
#endif #endif
} }
else if (use24bpp)
{
const size_t rowPitch = images[index].rowPitch;
const uint8_t * __restrict sPtr = images[index].pixels;
assert(ddsRowPitch <= metadata.width * 3u);
for (size_t j = 0; j < images[index].height; ++j)
{
CopyScanline24bpp(tempRow.get(), sPtr, images[index].width);
#ifdef _WIN32
if (!WriteFile(hFile.get(), tempRow.get(), static_cast<DWORD>(ddsRowPitch), &bytesWritten, nullptr))
{
return HRESULT_FROM_WIN32(GetLastError());
}
if (bytesWritten != ddsRowPitch)
{
return E_FAIL;
}
#else
outFile.write(reinterpret_cast<const char*>(tempRow.get()), static_cast<std::streamsize>(ddsRowPitch));
if (!outFile)
return E_FAIL;
#endif
sPtr += rowPitch;
}
}
else else
{ {
const size_t rowPitch = images[index].rowPitch; const size_t rowPitch = images[index].rowPitch;

View File

@ -189,6 +189,7 @@ namespace
{ {
FORMAT_DXT5_NM = 1, FORMAT_DXT5_NM = 1,
FORMAT_DXT5_RXGB, FORMAT_DXT5_RXGB,
FORMAT_24BPP_LEGACY,
}; };
static_assert(OPT_FLAGS_MAX <= 64, "dwOptions is a unsigned int bitfield"); static_assert(OPT_FLAGS_MAX <= 64, "dwOptions is a unsigned int bitfield");
@ -450,6 +451,7 @@ namespace
{ L"BC3n", FORMAT_DXT5_NM }, { L"BC3n", FORMAT_DXT5_NM },
{ L"DXT5nm", FORMAT_DXT5_NM }, { L"DXT5nm", FORMAT_DXT5_NM },
{ L"RXGB", FORMAT_DXT5_RXGB }, { L"RXGB", FORMAT_DXT5_RXGB },
{ L"RGB24", FORMAT_24BPP_LEGACY },
{ nullptr, 0 } { nullptr, 0 }
}; };
@ -1271,6 +1273,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
bool keepRecursiveDirs = false; bool keepRecursiveDirs = false;
bool dxt5nm = false; bool dxt5nm = false;
bool dxt5rxgb = false; bool dxt5rxgb = false;
bool use24bpp = false;
uint32_t swizzleElements[4] = { 0, 1, 2, 3 }; uint32_t swizzleElements[4] = { 0, 1, 2, 3 };
uint32_t zeroElements[4] = {}; uint32_t zeroElements[4] = {};
uint32_t oneElements[4] = {}; uint32_t oneElements[4] = {};
@ -1488,6 +1491,11 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
dxt5rxgb = true; dxt5rxgb = true;
break; break;
case FORMAT_24BPP_LEGACY:
format = DXGI_FORMAT_B8G8R8X8_UNORM;
use24bpp = true;
break;
default: default:
wprintf(L"Invalid value specified with -f (%ls)\n\n", pValue); wprintf(L"Invalid value specified with -f (%ls)\n\n", pValue);
PrintUsage(); PrintUsage();
@ -3775,6 +3783,10 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[])
{ {
ddsFlags |= DDS_FLAGS_FORCE_DXT5_RXGB; ddsFlags |= DDS_FLAGS_FORCE_DXT5_RXGB;
} }
else if (use24bpp)
{
ddsFlags |= DDS_FLAGS_FORCE_24BPP_RGB;
}
ddsFlags |= DDS_FLAGS_FORCE_DX9_LEGACY; ddsFlags |= DDS_FLAGS_FORCE_DX9_LEGACY;
} }