//! Bridge between the kernel (CPU-thread side) and a host UI (main-thread side). //! //! The kernel side needs to: //! - snapshot the latest host gamepad each time a guest calls //! `XamInputGetState`, and //! - signal the UI when the guest calls `VdSwap` so the UI can upload the //! guest's frontbuffer to a wgpu texture and present it. //! //! Both directions are expressed as trait-object closures so that `xenia-kernel` //! does not have to depend on winit/wgpu/gilrs. The [`UiBridge`] is installed //! on [`KernelState::ui`] by `cmd_exec` when `--ui` is passed. use std::collections::HashMap; use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicU64}; use xenia_gpu::texture_cache::TextureKey; use xenia_gpu::xenos_constants::XenosConstantsBlock; use xenia_hid::GamepadState; use xenia_memory::MemoryAccess; /// Information surfaced to the UI each time the guest presents a frame. /// /// Fields mirror the seven "interesting" arguments to `VdSwap` in /// `xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_video.cc`: the raw /// frontbuffer pointer, its dimensions, and the format/color-space enum values /// the guest passed through. #[derive(Clone, Copy, Debug)] pub struct SwapInfo { /// Guest physical/virtual address of the frontbuffer to present. pub frontbuffer_addr: u32, /// Width in pixels as reported by the guest. pub width: u32, /// Height in pixels as reported by the guest. pub height: u32, /// Xenos texture format enum (the guest passes a pointer; we dereference /// it here). 0 means "unknown / guest passed a null pointer". pub texture_format: u32, /// Color-space enum (sRGB / BT.709 / …). pub color_space: u32, /// Monotonically increasing frame counter maintained by the kernel; useful /// for HUD display and deduping. pub frame_index: u64, /// Total PM4 `DRAW_INDX*` packets the GPU has captured since boot. /// Surfaced so the UI HUD can show progress even before the full /// uber-shader pipeline is wired in. pub draws_total: u64, /// Total PM4 packets executed, across all opcodes — useful signal for /// "is the GPU actually getting anything at all to consume?". pub packets_total: u64, /// Most-recent draw's Xenos primitive-type code (0 = none yet). pub last_draw_prim: u32, /// Most-recent draw's vertex count. pub last_draw_vertex_count: u32, /// Indirect-buffer jumps so far (useful "is the game driving the ring /// buffer through IBs?" signal). pub indirect_buffer_jumps: u64, /// WAIT_REG_MEM stalls observed on the GPU slot. pub wait_reg_mem_blocks: u64, /// Summed CPU instruction count across all 6 HW threads. Mirrors the /// `cycle_count` field each `PpcContext` maintains; gives the HUD a live /// "how far has the guest run?" readout. pub instructions_total: u64, /// Active VS shader blob key at the most recent DRAW_INDX* (0 = none). /// P3b: the UI uses this to index into `handles.shader_blobs` so the /// Xenos uber-shader interpreter can upload the matching microcode. pub vs_blob_key: u32, /// Active PS shader blob key at the most recent DRAW_INDX*. pub ps_blob_key: u32, /// P4: total EDRAM→memory resolves fired since boot (TILE_FLUSH /// events). Non-zero means the game is committing pixels. pub resolves_total: u64, /// Subset of `resolves_total` whose byte-copy path succeeded and wrote /// at least one sample into guest memory. pub resolves_copied_total: u64, /// Subset of `resolves_total` that were skipped by the byte-copy path /// due to an unsupported format / MSAA mode / 3D destination. pub resolves_skipped_total: u64, /// P4: unique RT keys seen (from the GPU's internal render-target /// cache). Grows as the game exercises new RT footprints. pub unique_render_targets: u64, /// P6: total graphics-interrupt callbacks delivered (v-sync + CP). /// Non-zero means `VdSetGraphicsInterruptCallback` has been wired end /// to end and callbacks are actually running. pub interrupts_delivered: u64, /// P6: graphics-interrupts queued but dropped (callback unset, /// thread 0 blocked, or already inside another callback). pub interrupts_dropped: u64, } /// Handles the kernel uses to talk to a running host UI. /// /// None of the closures are allowed to block for long — they are called from /// the CPU interpreter thread on the hot path. #[derive(Clone)] pub struct UiBridge { /// Snapshot the host gamepad. Called from `XamInputGetState`. pub gamepad: Arc GamepadState + Send + Sync>, /// Report that the guest completed a frame. The closure gets the swap /// metadata plus a borrow of guest memory so it can copy the frontbuffer /// bytes into a UI-owned staging buffer before returning. Called from /// `VdSwap` on the CPU thread. pub post_swap: Arc, /// Indicates the UI wants the CPU loop to stop. Checked periodically by /// the interpreter loop. pub shutdown: Arc, /// Set to `true` when a gamepad is present. `XamInputGetState` returns /// `ERROR_DEVICE_NOT_CONNECTED` when this is `false`. pub gamepad_connected: Arc, /// Live CPU instruction counter mirror. The app's run loop publishes /// the sum of `ctx.cycle_count` across HW threads here every ~8k /// instructions so the HUD can report progress between VdSwap events. pub instructions_counter: Arc, /// P3b asset publish: `vd_swap` snapshots the GPU's `shader_blobs` and /// constants register region and feeds them to the UI so the Xenos /// uber-shader interpreter has the microcode + constants needed to /// execute the guest draw. Split from `post_swap` so the asset wire /// stays optional — if the UI doesn't need them (headless mode) the /// closure is a no-op. pub publish_xenos_assets: Arc>, XenosConstantsBlock) + Send + Sync>, /// P4 frontbuffer publish: at each `VdSwap`, the kernel CPU-side /// detiles the guest frontbuffer (k_8_8_8_8 Tiled2D) into a linear /// RGBA8 buffer and hands it to the UI. The closure receives /// `(width, height, bytes)` — the UI uploads it as a texture. pub publish_frontbuffer: Arc) + Send + Sync>, /// P5 primary texture publish: at each `VdSwap`, the kernel thread /// decodes the PS shader's primary-texture fetch constant (slot 0 /// for now) and hands the decoded linear bytes + key to the UI so /// the xenos pipeline can bind a real texture at `@group(1)`. /// Receives `(TextureKey, bytes)`; when `None` is sent the UI /// reverts to its magenta stub. pub publish_texture: Arc)>) + Send + Sync>, } impl UiBridge { /// Snapshot input state (user 0 only; higher indices are unconnected). pub fn snapshot_gamepad(&self) -> GamepadState { (self.gamepad)() } /// True iff a gamepad is connected for user 0. pub fn is_connected(&self, user_index: u32) -> bool { user_index == 0 && self .gamepad_connected .load(std::sync::atomic::Ordering::Relaxed) } /// Push a swap event to the UI thread. pub fn notify_swap(&self, info: SwapInfo, mem: &dyn MemoryAccess) { (self.post_swap)(info, mem); } /// Snapshot current shader blobs + constants and hand them to the UI. /// Call from `vd_swap` so the UI has the matching assets for every /// draw captured in this frame. pub fn publish_assets( &self, blobs: HashMap>, constants: XenosConstantsBlock, ) { (self.publish_xenos_assets)(blobs, constants); } /// True iff the UI asked for shutdown. pub fn should_shutdown(&self) -> bool { self.shutdown.load(std::sync::atomic::Ordering::Relaxed) } /// Hand a detiled frontbuffer frame to the UI. Called at most once per /// `VdSwap`. `bytes` must be `width * height * 4` bytes in /// `Rgba8Unorm` order (the UI pipeline's expected layout). pub fn publish_frontbuffer(&self, width: u32, height: u32, bytes: Vec) { (self.publish_frontbuffer)(width, height, bytes); } /// Hand one decoded guest texture to the UI. `Some` = update the bound /// slot; `None` = revert to the magenta stub. pub fn publish_texture(&self, tex: Option<(TextureKey, Vec)>) { (self.publish_texture)(tex); } }