Major HLE buildout in exports.rs: KeInitializeSemaphore now seeds
count/limit, XexGet{Module,Procedure}Address use distinct
HMODULE_XBOXKRNL/HMODULE_XAM pseudo-handles with a reverse
(ModuleId,ordinal)→thunk_addr map, plus sweeping additions across
sync primitives, file I/O, semaphores, events, threads, and
allocator paths needed to advance Sylpheed past VdSwap=2.
New modules:
- thread.rs — ThreadRef + per-thread suspension/wake plumbing
- interrupts.rs — IRQ delivery, pending-IRQ slots, IPI helpers
- path.rs — guest path normalization (D:\\, game:\\, etc.)
- audit.rs — --trace-handles harness backing the handle audit
- ui_bridge.rs — kernel-side endpoint of the xenia-ui bridge
(input snapshots, framebuffer publish handles)
state.rs grows to own the HW-slot scheduler state, the new audit /
UI bridge handles, and the per-handle reverse maps. xam.rs and
objects.rs follow suit for the HLE additions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
186 lines
8.4 KiB
Rust
186 lines
8.4 KiB
Rust
//! 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<dyn Fn() -> 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<dyn Fn(SwapInfo, &dyn MemoryAccess) + Send + Sync>,
|
|
/// Indicates the UI wants the CPU loop to stop. Checked periodically by
|
|
/// the interpreter loop.
|
|
pub shutdown: Arc<AtomicBool>,
|
|
/// Set to `true` when a gamepad is present. `XamInputGetState` returns
|
|
/// `ERROR_DEVICE_NOT_CONNECTED` when this is `false`.
|
|
pub gamepad_connected: Arc<AtomicBool>,
|
|
/// 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<AtomicU64>,
|
|
/// 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<dyn Fn(HashMap<u32, Vec<u32>>, 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<dyn Fn(u32, u32, Vec<u8>) + 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<dyn Fn(Option<(TextureKey, Vec<u8>)>) + 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<u32, Vec<u32>>,
|
|
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<u8>) {
|
|
(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<u8>)>) {
|
|
(self.publish_texture)(tex);
|
|
}
|
|
}
|