[iterate-3X] Real splash logo geometry renders: fix vertex-fetch const_index_sel + per-draw submit

Two readback-proven root-cause fixes make the publisher-logo QuadList draw
land its REAL captured vertex buffer (the texture was already correct from
3V). REFUTES iterate-3W's "logo geometry is auto-generated from vertex_id":
the logo IS sourced from a 4-vertex QuadList buffer at guest physical
0x0adf60f0 (measured), it was just resolved at the wrong fetch-constant
register.

GPUBUG-110 (vertex fetch const_index_sel dropped). The Xenos vertex-fetch
instruction encodes const_index (w0[20:24]) AND const_index_sel (w0[25:26]);
the full constant index is const_index*3 + const_index_sel (canary
ucode.h:700), packed 3 two-dword constants per 6-dword register group.
ucode/fetch.rs decoded only const_index and read sub-slot 0 (fc*6). The logo
vfetch is const_index=31, sel=2 -> the real base lives at reg 0x48BE, but
ours read 0x48BA which held an unused 0x00000001 (base=0,size=0) slot. So
resolve_vertex_window returned None -> has_real_vertices=false -> the logo
fell to the procedural fullscreen magenta fallback. Fix: decode
const_index_sel, add VertexFetch::const_reg_offset() = const_index*6 + sel*2,
and use it in both draw_capture.rs (capture) and translator.rs (the WGSL
endian term + no-window fallback base; the old expression there read the
src_reg bits, not the const index). Measured: logo now resolves a 24-dword
(4 verts x stride 6) window, base 0x0adf60f0.

GPUBUG-111 (single batch encoder = last-draw-wins vertex data). In wgpu every
queue.write_buffer staged before a single queue.submit is applied before ANY
command in that submit runs. dispatch_xenos_captures recorded the whole batch
into one encoder + one submit, so every draw read only the LAST draw's vertex
buffer / per-draw uniforms. The logo quad therefore sampled the trailing
fullscreen background quad's vertices and rasterized nothing where the logo
was. Fix: submit one encoder per draw (frontbuffer LoadOp::Load composites
identically). Measured (env-gated readback, removed): with this fix the logo
draw in isolation renders real varied texels (e.g. (225,17,22)/(255,255,0))
in a centered strip (~20k px), vs 100% navy before.

Determinism: all changes are UI-side (xenia-ui replay) or the UI translator /
capture path (frame_captures None in headless); the fetch.rs field addition
is purely additive and does not change any existing decoded value. Verified
the deterministic core unchanged: check -n50M --gpu-inline --stable-digest
exit 0 and all 136 metric counters byte-identical across two runs. All temp
probes removed. cargo test --workspace green; new regression test
vertex_fetch_const_index_sel_and_reg_offset.

Known remaining (next iterate): a fullscreen flat QuadList (ps 0x03b79081,
vertex color green, no texture) and other textureless draws overpaint the
logo in the full composite (their per-draw blend/alpha render state is not
yet replayed, and draw order alternates bg/logo). The logo artwork renders
correctly in isolation; the composite is not yet clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-18 19:25:50 +02:00
parent 39723dfe37
commit 89b5c39d8a
4 changed files with 90 additions and 15 deletions

View File

@@ -17,8 +17,16 @@ pub enum FetchInstruction {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VertexFetch {
/// Vertex fetch constant index (0..=95).
/// Vertex fetch *const_index* (5 bits, w0[20:24]). The full fetch-constant
/// index is `const_index * 3 + const_index_sel` (canary `ucode.h:700`); use
/// [`VertexFetch::const_reg_offset`] for the register-region dword offset.
pub fetch_const: u8,
/// iterate-3X (GPUBUG-110): `const_index_sel` (2 bits, w0[25:26]) — selects
/// one of the 3 two-dword vertex-fetch constants packed in each 6-dword
/// register group. Dropping this read sub-slot 0 of the group, missing the
/// real vertex-buffer base for shaders that use sub-slot 1/2 (the publisher
/// logo uses `const_index=31, sel=2`).
pub const_index_sel: u8,
/// Source register index (vertex index in r#).
pub src_register: u8,
/// Destination register for the fetched value.
@@ -50,6 +58,17 @@ pub struct VertexFetch {
pub raw: [u32; 3],
}
impl VertexFetch {
/// Dword offset of this fetch's 2-dword constant within the fetch-constant
/// register region (`CONST_BASE_FETCH`). Vertex fetch constants are packed
/// 3 per 6-dword group: `const_index * 6 + const_index_sel * 2`
/// (canary `ucode.h:700` `fetch_constant_index = const_index*3 + sel`,
/// each constant 2 dwords).
pub fn const_reg_offset(&self) -> u32 {
self.fetch_const as u32 * 6 + self.const_index_sel as u32 * 2
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TextureFetch {
/// Texture fetch constant index (0..=31).
@@ -91,6 +110,7 @@ pub fn decode_fetch(words: [u32; 3]) -> FetchInstruction {
match opcode {
op::VERTEX_FETCH => FetchInstruction::Vertex(VertexFetch {
fetch_const: ((w0 >> 20) & 0x1F) as u8,
const_index_sel: ((w0 >> 25) & 0x3) as u8,
src_register: ((w0 >> 5) & 0x3F) as u8,
dest_register: ((w0 >> 12) & 0x3F) as u8,
dest_write_mask: (w1 & 0xF) as u8,
@@ -137,6 +157,32 @@ mod tests {
}
}
#[test]
fn vertex_fetch_const_index_sel_and_reg_offset() {
// iterate-3X (GPUBUG-110): the real publisher-logo vfetch (w0 =
// 0x2DF82000) encodes const_index=31, const_index_sel=2. Its fetch
// constant lives at dword offset `31*6 + 2*2 = 190` (reg 0x48BE), not
// `31*6 = 186` (reg 0x48BA, which held the unused 0x1 slot). Dropping
// the sel field made the logo geometry resolve as "no vertex buffer".
let v = decode_fetch([0x2DF8_2000, 0, 0]);
match v {
FetchInstruction::Vertex(vf) => {
assert_eq!(vf.fetch_const, 31, "const_index");
assert_eq!(vf.const_index_sel, 2, "const_index_sel");
assert_eq!(vf.const_reg_offset(), 190, "reg offset = 31*6 + 2*2");
}
other => panic!("expected Vertex, got {other:?}"),
}
// sel=0 collapses to the legacy `fetch_const*6` offset (back-compat).
let v0 = decode_fetch([0u32 | (5 << 20), 0, 0]);
if let FetchInstruction::Vertex(vf) = v0 {
assert_eq!(vf.const_index_sel, 0);
assert_eq!(vf.const_reg_offset(), 30);
} else {
panic!("expected Vertex");
}
}
#[test]
fn decode_texture_fetch() {
// opcode=1 (texture). const_index@bit20=3, src@bit5=1, dst@bit12=4.