fix(texture): make BC dword-swap format-aware (don't mangle DXT5A)
The dword-swap that fixes DXT1 is a property of the BC1 COLOUR block, not of
BCn in general. RE on a DXT5A (BC4) mask showed the alpha block is byte-indexed
and wants the endian swap ALONE — dword-swapping it makes it worse. The blanket
swap from the previous commit was therefore breaking the (common) DXT5A/DXN
alpha & normal masks.
Now targeted by format:
- DXT1 : swap dwords of each 8-byte colour block (verified).
- DXT2_3 / DXT4_5 : swap only the colour half (bytes 8..16) of each 16-byte
block; alpha half endian-only. TENTATIVE — these formats
are near-absent in the game assets, so unvalidated against
a clear image.
- DXT5A / DXN : no dword swap (endian only).
DXT1 export re-verified clean (weapon skins); 16 formats tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -313,18 +313,33 @@ impl X360Texture {
|
|||||||
apply_endian_swap(&mut linear_data, endianness);
|
apply_endian_swap(&mut linear_data, endianness);
|
||||||
}
|
}
|
||||||
|
|
||||||
// X360 stores BCn (DXT) blocks with the two 32-bit words of each 64-bit
|
// X360 stores the BC1 *colour* block with its two 32-bit words swapped
|
||||||
// sub-block in swapped order relative to the PC/DDS layout: the color
|
// relative to the PC/DDS layout — the colour endpoints live in the high
|
||||||
// endpoints live in the *high* dword, the indices in the low one. The
|
// dword, the indices in the low one. The endian field (k8in16) only
|
||||||
// endian field (k8in16) only fixes byte order *within* the 16-bit words;
|
// fixes byte order *within* the 16-bit words; it does not reorder the
|
||||||
// it does not reorder the dwords. Without this swap the endpoints and
|
// dwords, so without this the endpoints/indices are transposed and the
|
||||||
// indices are transposed and every DXT texture decodes to noise. This is
|
// texture decodes to noise. (Discovered by RE: de-tiled DXT1 blocks had
|
||||||
// an intrinsic property of the format's guest storage, so it is applied
|
// coherent endpoints only in bytes[4..8].)
|
||||||
// for every BCn texture regardless of the endian field.
|
//
|
||||||
// (Discovered by RE: de-tiled DXT1 blocks had coherent endpoints only in
|
// This applies to the colour block only. The BC4-style alpha block
|
||||||
// bytes[4..8], with the high-entropy indices in bytes[0..4].)
|
// (DXT5A, and the alpha half of DXT2_3/DXT4_5) is byte-indexed and needs
|
||||||
if format.is_block_compressed() && std::env::var("XPR_NO_BC_DWORD_SWAP").is_err() {
|
// the endian swap alone — RE-confirmed: dword-swapping DXT5A makes it
|
||||||
swap_bc_block_dwords(&mut linear_data);
|
// *worse*. So the swap is format-targeted, not blanket.
|
||||||
|
if std::env::var("XPR_NO_BC_DWORD_SWAP").is_err() {
|
||||||
|
match format {
|
||||||
|
// Pure colour block, 8 bytes.
|
||||||
|
X360TextureFormat::Dxt1 => swap_bc_block_dwords(&mut linear_data),
|
||||||
|
// 16-byte block = [alpha 8B][colour 8B]; swap only the colour
|
||||||
|
// half. TENTATIVE: DXT2_3/DXT4_5 are near-absent in the game's
|
||||||
|
// assets, so this half-swap is unvalidated against a clear image.
|
||||||
|
X360TextureFormat::Dxt3 | X360TextureFormat::Dxt5 => {
|
||||||
|
for block in linear_data.chunks_exact_mut(16) {
|
||||||
|
swap_bc_block_dwords(&mut block[8..16]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// BC4 / BC5 (DXT5A / DXN): alpha/normal blocks, endian swap only.
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(X360Texture { width, height, format, mip_levels: mip_count, is_cubemap, data: linear_data })
|
Ok(X360Texture { width, height, format, mip_levels: mip_count, is_cubemap, data: linear_data })
|
||||||
@@ -375,12 +390,11 @@ pub fn apply_endian_swap(data: &mut [u8], endianness: u8) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Swap the two 32-bit dwords within each 64-bit sub-block, in place.
|
/// Swap the two 32-bit dwords within each 64-bit unit of `data`, in place.
|
||||||
///
|
///
|
||||||
/// Xbox 360 BCn (DXT) textures store each 64-bit block-half with its two 32-bit
|
/// Xbox 360 stores the BC1 *colour* block with its two 32-bit words in the
|
||||||
/// words in the opposite order to the PC/DDS layout. For BC1/DXT5A (8-byte
|
/// opposite order to the PC/DDS layout, putting the colour endpoints ahead of
|
||||||
/// blocks) this puts the colour endpoints ahead of the indices; for BC2/BC3/DXN
|
/// the indices. Apply to a BC1 buffer (or the colour half of a BC2/BC3 block)
|
||||||
/// (16-byte blocks) it fixes both the alpha half and the colour half. Apply
|
|
||||||
/// after the byte-level endian swap. Any trailing bytes that don't fill a full
|
/// after the byte-level endian swap. Any trailing bytes that don't fill a full
|
||||||
/// 8-byte group are left untouched.
|
/// 8-byte group are left untouched.
|
||||||
pub fn swap_bc_block_dwords(data: &mut [u8]) {
|
pub fn swap_bc_block_dwords(data: &mut [u8]) {
|
||||||
|
|||||||
Reference in New Issue
Block a user