diff --git a/DirectXTex/DirectXTex.h b/DirectXTex/DirectXTex.h index b514a1c..01bbee7 100644 --- a/DirectXTex/DirectXTex.h +++ b/DirectXTex/DirectXTex.h @@ -70,6 +70,7 @@ namespace DirectX CP_FLAGS_YMM = 0x4, // Assume pitch is 32-byte aligned instead of BYTE aligned CP_FLAGS_ZMM = 0x8, // Assume pitch is 64-byte aligned instead of BYTE aligned CP_FLAGS_PAGE4K = 0x200, // Assume pitch is 4096-byte aligned instead of BYTE aligned + CP_FLAGS_BAD_DXTN_TAILS = 0x1000, // BC formats with malformed mipchain blocks smaller than 4x4 CP_FLAGS_24BPP = 0x10000, // Override with a legacy 24 bits-per-pixel format size CP_FLAGS_16BPP = 0x20000, // Override with a legacy 16 bits-per-pixel format size CP_FLAGS_8BPP = 0x40000, // Override with a legacy 8 bits-per-pixel format size @@ -164,6 +165,9 @@ namespace DirectX DDS_FLAGS_EXPAND_LUMINANCE = 0x20, // When loading legacy luminance formats expand replicating the color channels rather than leaving them packed (L8, L16, A8L8) + DDS_FLAGS_BAD_DXTN_TAILS = 0x40, + // Some older DXTn DDS files incorrectly handle mipchain tails for blocks smaller than 4x4 + DDS_FLAGS_FORCE_DX10_EXT = 0x10000, // Always use the 'DX10' header extension for DDS writer (i.e. don't try to write DX9 compatible DDS files) diff --git a/DirectXTex/DirectXTexDDS.cpp b/DirectXTex/DirectXTexDDS.cpp index fff9d53..dff233a 100644 --- a/DirectXTex/DirectXTexDDS.cpp +++ b/DirectXTex/DirectXTexDDS.cpp @@ -1068,7 +1068,7 @@ namespace if (pixelSize > size) { - return E_FAIL; + return HRESULT_FROM_WIN32( ERROR_HANDLE_EOF ); } std::unique_ptr timages(new (std::nothrow) Image[nimages]); @@ -1077,7 +1077,7 @@ namespace return E_OUTOFMEMORY; } - if (!_SetupImageArray((uint8_t*)pPixels, size, metadata, cpFlags, timages.get(), nimages)) + if (!_SetupImageArray((uint8_t*)pPixels, pixelSize, metadata, cpFlags, timages.get(), nimages)) { return E_FAIL; } @@ -1105,6 +1105,7 @@ namespace 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) @@ -1128,6 +1129,19 @@ namespace { size_t csize = std::min(images[index].slicePitch, timages[index].slicePitch); memcpy_s(pDest, images[index].slicePitch, pSrc, csize); + + if (cpFlags & CP_FLAGS_BAD_DXTN_TAILS) + { + if (images[index].width < 4 || images[index].height < 4) + { + csize = std::min(images[index].slicePitch, timages[lastgood].slicePitch); + memcpy_s(pDest, images[index].slicePitch, timages[lastgood].pixels, csize); + } + else + { + lastgood = index; + } + } } else if (IsPlanar(metadata.format)) { @@ -1191,6 +1205,7 @@ namespace 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) @@ -1216,6 +1231,19 @@ namespace { size_t csize = std::min(images[index].slicePitch, timages[index].slicePitch); memcpy_s(pDest, images[index].slicePitch, pSrc, csize); + + if (cpFlags & CP_FLAGS_BAD_DXTN_TAILS) + { + if (images[index].width < 4 || images[index].height < 4) + { + csize = std::min(images[index].slicePitch, timages[lastgood + slice].slicePitch); + memcpy_s(pDest, images[index].slicePitch, timages[lastgood + slice].pixels, csize); + } + else if (!slice) + { + lastgood = index; + } + } } else if (IsPlanar(metadata.format)) { @@ -1438,10 +1466,25 @@ HRESULT DirectX::LoadFromDDSMemory( if (FAILED(hr)) return hr; + DWORD cflags = CP_FLAGS_NONE; + if (flags & DDS_FLAGS_LEGACY_DWORD) + { + cflags |= CP_FLAGS_LEGACY_DWORD; + } + if (flags & DDS_FLAGS_BAD_DXTN_TAILS) + { + cflags |= CP_FLAGS_BAD_DXTN_TAILS; + } + auto pPixels = reinterpret_cast(reinterpret_cast(pSource) + offset); assert(pPixels); - hr = CopyImage(pPixels, size - offset, mdata, - (flags & DDS_FLAGS_LEGACY_DWORD) ? CP_FLAGS_LEGACY_DWORD : CP_FLAGS_NONE, convFlags, pal8, image); + hr = CopyImage(pPixels, + size - offset, + mdata, + cflags, + convFlags, + pal8, + image); if (FAILED(hr)) { image.Release(); @@ -1560,7 +1603,7 @@ HRESULT DirectX::LoadFromDDSFile( if (FAILED(hr)) return hr; - if ((convFlags & CONV_FLAGS_EXPAND) || (flags & DDS_FLAGS_LEGACY_DWORD)) + if ((convFlags & CONV_FLAGS_EXPAND) || (flags & (DDS_FLAGS_LEGACY_DWORD | DDS_FLAGS_BAD_DXTN_TAILS))) { std::unique_ptr temp(new (std::nothrow) uint8_t[remaining]); if (!temp) @@ -1581,9 +1624,23 @@ HRESULT DirectX::LoadFromDDSFile( 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); + 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(); @@ -1595,7 +1652,7 @@ HRESULT DirectX::LoadFromDDSFile( if (remaining < image.GetPixelsSize()) { image.Release(); - return E_FAIL; + return HRESULT_FROM_WIN32( ERROR_HANDLE_EOF ); } if (!ReadFile(hFile.get(), image.GetPixels(), static_cast(image.GetPixelsSize()), &bytesRead, nullptr)) diff --git a/DirectXTex/DirectXTexUtil.cpp b/DirectXTex/DirectXTexUtil.cpp index ea3f055..1d88ba3 100644 --- a/DirectXTex/DirectXTexUtil.cpp +++ b/DirectXTex/DirectXTexUtil.cpp @@ -881,11 +881,20 @@ void DirectX::ComputePitch(DXGI_FORMAT fmt, size_t width, size_t height, case DXGI_FORMAT_BC4_SNORM: assert(IsCompressed(fmt)); { - size_t nbw = std::max(1, (width + 3) / 4); - size_t nbh = std::max(1, (height + 3) / 4); - rowPitch = nbw * 8; - - slicePitch = rowPitch * nbh; + if (flags & CP_FLAGS_BAD_DXTN_TAILS) + { + size_t nbw = width >> 2; + size_t nbh = height >> 2; + rowPitch = std::max(1, nbw * 8); + slicePitch = std::max(1, rowPitch * nbh); + } + else + { + size_t nbw = std::max(1, (width + 3) / 4); + size_t nbh = std::max(1, (height + 3) / 4); + rowPitch = nbw * 8; + slicePitch = rowPitch * nbh; + } } break; @@ -906,11 +915,20 @@ void DirectX::ComputePitch(DXGI_FORMAT fmt, size_t width, size_t height, case DXGI_FORMAT_BC7_UNORM_SRGB: assert(IsCompressed(fmt)); { - size_t nbw = std::max(1, (width + 3) / 4); - size_t nbh = std::max(1, (height + 3) / 4); - rowPitch = nbw * 16; - - slicePitch = rowPitch * nbh; + if (flags & CP_FLAGS_BAD_DXTN_TAILS) + { + size_t nbw = width >> 2; + size_t nbh = height >> 2; + rowPitch = std::max(1, nbw * 16); + slicePitch = std::max(1, rowPitch * nbh); + } + else + { + size_t nbw = std::max(1, (width + 3) / 4); + size_t nbh = std::max(1, (height + 3) / 4); + rowPitch = nbw * 16; + slicePitch = rowPitch * nbh; + } } break; diff --git a/Texconv/texconv.cpp b/Texconv/texconv.cpp index ff4a0ef..7c6f2e9 100644 --- a/Texconv/texconv.cpp +++ b/Texconv/texconv.cpp @@ -55,6 +55,7 @@ enum OPTIONS // Note: dwOptions below assumes 64 or less options. OPT_HFLIP, OPT_VFLIP, OPT_DDS_DWORD_ALIGN, + OPT_DDS_BAD_DXTN_TAILS, OPT_USE_DX10, OPT_NOLOGO, OPT_TIMING, @@ -116,6 +117,7 @@ SValue g_pOptions[] = { L"hflip", OPT_HFLIP }, { L"vflip", OPT_VFLIP }, { L"dword", OPT_DDS_DWORD_ALIGN }, + { L"badtails", OPT_DDS_BAD_DXTN_TAILS }, { L"dx10", OPT_USE_DX10 }, { L"nologo", OPT_NOLOGO }, { L"timing", OPT_TIMING }, @@ -529,6 +531,7 @@ void PrintUsage() wprintf( L"\n (DDS input only)\n"); wprintf( L" -t{u|f} TYPELESS format is treated as UNORM or FLOAT\n"); wprintf( L" -dword Use DWORD instead of BYTE alignment\n"); + wprintf( L" -badtails Fix for older DXTn with bad mipchain tails\n"); wprintf( L" -xlum expand legacy L8, L16, and A8P8 formats\n"); wprintf( L"\n (DDS output only)\n"); wprintf( L" -dx10 Force use of 'DX10' extended header\n"); @@ -1178,6 +1181,8 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) ddsFlags |= DDS_FLAGS_LEGACY_DWORD; if ( dwOptions & (DWORD64(1) << OPT_EXPAND_LUMINANCE) ) ddsFlags |= DDS_FLAGS_EXPAND_LUMINANCE; + if ( dwOptions & (DWORD64(1) << OPT_DDS_BAD_DXTN_TAILS) ) + ddsFlags |= DDS_FLAGS_BAD_DXTN_TAILS; hr = LoadFromDDSFile( pConv->szSrc, ddsFlags, &info, *image ); if ( FAILED(hr) )