[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

@@ -200,7 +200,7 @@ fn resolve_vertex_window(
// exec clauses exactly as the translator does (`translator.rs::emit_exec`)
// and take the FIRST sequence-flagged *vertex* fetch.
let instrs = &parsed_vs.instructions;
let mut fetch_const: Option<u8> = None;
let mut const_off: Option<u32> = None;
'clauses: for clause in &parsed_vs.cf {
let crate::ucode::control_flow::ControlFlowInstruction::Exec {
address,
@@ -223,14 +223,27 @@ fn resolve_vertex_window(
if let crate::ucode::fetch::FetchInstruction::Vertex(vf) =
crate::ucode::fetch::decode_fetch([instrs[base], instrs[base + 1], instrs[base + 2]])
{
fetch_const = Some(vf.fetch_const);
const_off = Some(vf.const_reg_offset());
break 'clauses;
}
}
}
let fc = fetch_const? as u32;
let dword0 = rf.read(CONST_BASE_FETCH + fc * 6);
let dword1 = rf.read(CONST_BASE_FETCH + fc * 6 + 1);
// iterate-3X (GPUBUG-110): vertex fetch constants are addressed by
// `const_index * 3 + const_index_sel` (canary `ucode.h:700` —
// `VertexFetchInstruction::fetch_constant_index`), NOT by `const_index`
// alone. The register region packs 3 two-dword vertex-fetch constants per
// 6-dword group, so the constant lives at
// `0x4800 + const_index*6 + const_index_sel*2`. The previous decode dropped
// `const_index_sel` and read sub-slot 0 (`fc*6`), which for the publisher
// logo (`const_index=31, sel=2`) held `0x00000001` (an unused slot) instead
// of the real vertex-buffer base at sub-slot 2 (`0x48BE`). That made
// `has_real_vertices=false` → the logo fell to the procedural fullscreen
// magenta fallback. (Refutes iterate-3W's "geometry is auto-generated from
// vertex_id" — measured: the real fetch constant is a 4-vertex QuadList
// buffer at `0x0adf60f0`.)
let const_reg = CONST_BASE_FETCH + const_off?;
let dword0 = rf.read(const_reg);
let dword1 = rf.read(const_reg + 1);
// address:30 at bits[31:2] of dword0 (in bytes once masked). The fetch
// constant carries a guest *physical* dword address — canary reads the
// vertex buffer via `Memory::TranslatePhysical(fetch.address * 4)`