xenia-kernel: HLE expansion, scheduler integration, audit + UI bridge

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>
This commit is contained in:
MechaCat02
2026-05-01 16:29:00 +02:00
parent f1fadb5398
commit 5f0d6487ea
11 changed files with 6369 additions and 270 deletions

View File

@@ -0,0 +1,185 @@
//! 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);
}
}