[iterate-3O] Real-render slice: replay guest geometry in --ui (Route A)

Replace the synthetic placeholder triangle in the --ui window with the
splash's REAL guest geometry, proving the faithful-render pipe end to end.

Architecture: Route A (UI-side replay). A per-draw capture channel carries
each PM4_DRAW_INDX*'s real state to the UI, which replays it through the
existing wgpu Xenos pipeline. The deterministic headless core is untouched:
capture is gated on an Option<Vec<DrawCapture>> that is None in headless
mode and only enabled on the --ui path, so the --gpu-inline n50m golden is
byte-identical (verified 2x).

The hard part was sourcing real vertices. The WGSL VS already does
format-aware vertex fetch from the b4 storage buffer at the address from the
fetch constant -- but b4 was never populated and the fetch address is an
absolute guest dword address. The slice:
  * xenia-gpu/draw_capture.rs: parse the active VS, find its first vertex
    fetch, read that fetch constant, copy a bounded window of guest memory
    at the fetch base. Best-effort: has_real_vertices=false falls back to
    procedural geometry (never fabricated pixels).
  * gpu_system.rs: accumulate one DrawCapture per draw into frame_captures.
  * exports.rs (vd_swap): drain + publish the frame's captures to the UI.
  * ui_bridge/bridge.rs: new publish_geometry channel + UiHandles.geometry.
  * WGSL (interp + translator): rebase the absolute fetch address by a new
    DrawConstants.vertex_base_dwords so it indexes the uploaded window.
  * render.rs: dispatch_xenos_captures uploads each draw's real vertex
    window + matching shader, issues real DrawRequests (real prim type,
    host vertex count, vs/ps keys).
  * app.rs: prefer the real-capture replay; HUD adds real-geo=N counter.

Verified in --ui on Sylpheed: "first Xenos capture batch replayed (real
geometry) captures=24 real_vertex_draws=24" -- all draws resolved a real
guest vertex window; WGSL compiles; no validation errors over 1616 swaps.

Still synthetic-free but not yet pixel-perfect: textures/UVs, DMA index
buffers (auto-index only for now), and kCopy resolve routing are staged
for follow-ups. Faithful: real vertex data, prim types, shaders, constants.

cargo test --workspace green; n50m golden unchanged (2x byte-identical).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-17 22:38:46 +02:00
parent 6bb4355e3d
commit 504592ac13
12 changed files with 509 additions and 65 deletions

View File

@@ -3231,6 +3231,16 @@ fn vd_swap(ctx: &mut PpcContext, mem: &GuestMemory, state: &mut KernelState) {
metrics::gauge!("gpu.texture_cache.entries")
.set(gpu_inline.texture_cache.len() as f64);
ui.publish_texture(published);
// iterate-3O: publish this frame's captured per-draw geometry and
// reset the accumulator for the next frame. The UI replays these as
// real guest draws (real vertices + prim type) instead of synthetic
// placeholder shapes. `frame_captures` is `Some` only under `--ui`.
if let Some(caps) = gpu_inline.frame_captures.as_mut() {
let drained = std::mem::take(caps);
metrics::counter!("gpu.geometry.published").increment(drained.len() as u64);
ui.publish_geometry(drained);
}
}
// Notify the UI.
if let Some(ui) = state.ui.clone() {

View File

@@ -14,6 +14,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64};
use xenia_gpu::draw_capture::DrawCapture;
use xenia_gpu::texture_cache::TextureKey;
use xenia_gpu::xenos_constants::XenosConstantsBlock;
use xenia_hid::GamepadState;
@@ -133,6 +134,14 @@ pub struct UiBridge {
/// reverts to its magenta stub.
pub publish_texture:
Arc<dyn Fn(Option<(TextureKey, Vec<u8>)>) + Send + Sync>,
/// iterate-3O real-render slice: at each `VdSwap`, the kernel hands the
/// UI the per-draw geometry captured this frame (one [`DrawCapture`] per
/// `PM4_DRAW_INDX*`), including the real guest vertex window. The UI
/// replays them through the Xenos wgpu pipeline so the splash renders its
/// actual geometry instead of synthetic placeholder shapes. Empty in the
/// degenerate case (no draws or capture disabled).
pub publish_geometry:
Arc<dyn Fn(Vec<DrawCapture>) + Send + Sync>,
}
impl UiBridge {
@@ -182,4 +191,9 @@ impl UiBridge {
pub fn publish_texture(&self, tex: Option<(TextureKey, Vec<u8>)>) {
(self.publish_texture)(tex);
}
/// Hand this frame's captured per-draw geometry to the UI.
pub fn publish_geometry(&self, caps: Vec<DrawCapture>) {
(self.publish_geometry)(caps);
}
}