fix(texture): crack X360 DXT1 dword-swap; wire correct decode into viewer
DXT1 (108/137 textures in Base.xpr — the bulk of picture assets) decoded to noise. Root cause found by RE: Xbox 360 BCn blocks store the two 32-bit words of each 64-bit sub-block in the opposite order to the PC/DDS layout — colour endpoints live in the HIGH dword, indices in the low one. The endian field (k8in16) only fixes byte order within the 16-bit words; it does not reorder the dwords, so without this every DXT texture transposed endpoints/indices → noise. Isolation that led here: - de-tile proven correct for bpb=8 (coherent per-block signature map; the linear read is scrambled) — same faithful Xenos Tiled2D as the verified bpb=4 ARGB path (green Acheron backdrop). - inspecting a smooth region, coherent colour endpoints appeared only in bytes[4..8], with the high-entropy indices in bytes[0..4]. Fix: swap_bc_block_dwords() swaps the two dwords within each 64-bit unit after the byte-level endian swap, for every BCn format. Verified in the real Rust CLI: weapon skins (rou_f001_wep_*) now decode to clean, recognisable images. Viewer: DXT1 is fixed transparently (from_xpr2 feeds tex.data straight to the GPU as Bc1). Also corrected the uncompressed path — post-swap k_8_8_8_8 is [A,R,G,B]; reorder to [R,G,B,A] and upload as Rgba8UnormSrgb (was Bgra8, wrong). Knob: XPR_NO_BC_DWORD_SWAP disables the swap for A/B validation. KNOWN REMAINING: 16-byte blocks (BC2/DXT3, BC3/DXT5) and BC4/BC5 (DXT5A/DXN) still need their alpha+colour half layout worked out — they decode to noise for now. DXT1 + uncompressed + cubemaps are correct. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -93,8 +93,28 @@ impl AssetLoader for Xpr2TextureLoader {
|
||||
pub fn x360_texture_to_bevy_image(tex: X360Texture) -> Result<Image, Xpr2LoadError> {
|
||||
let format = x360_format_to_wgpu(&tex.format)?;
|
||||
|
||||
// Uncompressed k_8_8_8_8: after `from_xpr2`'s k8in32 endian swap the bytes
|
||||
// are in [A,R,G,B] order (verified against the retail Acheron backdrop).
|
||||
// wgpu has no ARGB format, so reorder to [R,G,B,A] and upload as Rgba8
|
||||
// (see `x360_format_to_wgpu`). BCn data is already GPU-ready.
|
||||
let data = match tex.format {
|
||||
X360TextureFormat::A8R8G8B8 | X360TextureFormat::X8R8G8B8 => {
|
||||
let opaque = matches!(tex.format, X360TextureFormat::X8R8G8B8);
|
||||
let mut out = tex.data;
|
||||
for px in out.chunks_exact_mut(4) {
|
||||
let (a, r, g, b) = (px[0], px[1], px[2], px[3]);
|
||||
px[0] = r;
|
||||
px[1] = g;
|
||||
px[2] = b;
|
||||
px[3] = if opaque { 0xFF } else { a };
|
||||
}
|
||||
out
|
||||
}
|
||||
_ => tex.data,
|
||||
};
|
||||
|
||||
Ok(Image {
|
||||
data: tex.data,
|
||||
data,
|
||||
texture_descriptor: TextureDescriptor {
|
||||
label: None,
|
||||
size: Extent3d {
|
||||
@@ -132,9 +152,9 @@ fn x360_format_to_wgpu(format: &X360TextureFormat) -> Result<TextureFormat, Xpr2
|
||||
X360TextureFormat::Dxn => TextureFormat::Bc5RgUnorm,
|
||||
// DXT5A / BC4 — single-channel (gloss, specular, luminance maps)
|
||||
X360TextureFormat::Dxt5A => TextureFormat::Bc4RUnorm,
|
||||
// Uncompressed ARGB — Xbox 360 stores as BGRA in memory
|
||||
// Uncompressed — reordered to [R,G,B,A] by `x360_texture_to_bevy_image`.
|
||||
X360TextureFormat::A8R8G8B8 | X360TextureFormat::X8R8G8B8 => {
|
||||
TextureFormat::Bgra8UnormSrgb
|
||||
TextureFormat::Rgba8UnormSrgb
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user