[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:
@@ -519,7 +519,12 @@ impl EmitCtx {
|
||||
// iterate-3T: per-attribute dword offset within the vertex (vfetches
|
||||
// sharing one fetch constant read different attributes).
|
||||
let attr_off = vf.offset;
|
||||
let fetch_const = (vf.raw[0] >> 5) & 0x1F;
|
||||
// iterate-3X (GPUBUG-110): index the fetch-constant region by the full
|
||||
// `const_index*3 + const_index_sel` mapping (canary `ucode.h:700`),
|
||||
// packed as `const_index*6 + sel*2` dwords. The previous expression
|
||||
// `(vf.raw[0] >> 5) & 0x1F` read the *src_reg* bits, not the const
|
||||
// index — wrong for the endian term and the no-window fallback base.
|
||||
let const_off = vf.const_reg_offset();
|
||||
let src_reg = vf.src_register & 0x7F;
|
||||
let dst_reg = vf.dest_register & 0x7F;
|
||||
// is_signed selects [-1,1] vs [0,1] for normalized integer formats.
|
||||
@@ -573,7 +578,7 @@ impl EmitCtx {
|
||||
// every quad collapsed to ~one pixel at the origin. Index from 0 when a
|
||||
// real window is present (`vertex_base_dwords != 0`); only the
|
||||
// synthetic/no-window fallback consults the uniform fetch constant.
|
||||
let endian_term = format!("xenos_consts.fetch[{}u] & 0x3u", fetch_const * 2 + 1);
|
||||
let endian_term = format!("xenos_consts.fetch[{}u] & 0x3u", const_off + 1);
|
||||
// For packed-16 we read one dword into `w16` (post endian-swap) and the
|
||||
// `lane()` exprs above unpack the two halfwords.
|
||||
let w16_decl = if pack == Pack::Norm16x2 {
|
||||
@@ -594,7 +599,7 @@ impl EmitCtx {
|
||||
{w16_decl}\
|
||||
r[{dst_reg}u] = vec4<f32>({l0}, {l1}, {l2}, {l3}); \
|
||||
}} }}",
|
||||
fc0_idx = fetch_const * 2,
|
||||
fc0_idx = const_off,
|
||||
l0 = lane(0),
|
||||
l1 = lane(1),
|
||||
l2 = lane(2),
|
||||
@@ -960,6 +965,7 @@ mod tests {
|
||||
let mut ctx = EmitCtx::new(Stage::Vertex);
|
||||
let vf = crate::ucode::fetch::VertexFetch {
|
||||
fetch_const: 0,
|
||||
const_index_sel: 0,
|
||||
src_register: 0,
|
||||
dest_register: 0,
|
||||
dest_write_mask: 0xF,
|
||||
|
||||
Reference in New Issue
Block a user