Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

Module present

Module present 

Source
Expand description

VBlank-kicked presentation: a one-frame-deep queue between the CPU and the GPU. Built with the present-queue feature.

A game publishes each finished frame’s DMA chain together with the GP1(05h) word that shows the frame before it, and returns at once. psx-rt’s VBlank handler kicks the chain on the first edge where the previous chain’s closing GP0(1Fh) has run (GPUSTAT bit 24) and DMA channel 2 is idle: it flips the display to the finished frame, acknowledges the flag with GP1(02h), and starts the new walk. A flip therefore lands only on a blank edge and only on a fully drawn frame, and the CPU builds the next frame while the GPU draws this one. The CPU waits only when it is a whole frame ahead, and before reusing memory a walk may still read.

Ported from quake-psx’s platform.rs (3ca915c, 16e27a9: E1M1 chain bench +25.5%, monster route +46.1% over its blocking present) so every game shares one queue.

§Protocol

  1. Call start once, after crate::interrupts::install_vblank_counter, with nothing queued and channel 2 idle. It raises the draw-done flag so the first published frame finds it set.
  2. Every published chain must end on GP0(1Fh) (psx_gpu::DRAW_DONE_NODE, OrderingTable::end_with_draw_done), and must carry its own draw area, offset and clear as leading packets: the handler writes only GP1. psx_io::gpu::start_recording_raw records those from the usual immediate calls.
  3. Before publish_raw, call wait_slot_empty. Before rebuilding memory that the frame before the last published one used, call wait_arena_free.
  4. Direct command or display-control writes and GPU DMA starts are safe at any time: publish_raw arms psx-io’s direct-access guard, so the first such access after it runs wait_idle. Code that only records keeps the overlap.

Keep interrupt source 1 (GPU) masked in I_MASK: GP0(1Fh) raises it and the handler, which owns VBlank only, would acknowledge it as a stray. The handler returns past a GTE command an edge interrupted (8055e87f6), so publishing from GTE-heavy code is safe.

§With psx_gpu::frame

A frame kicked from the CPU (psx_gpu::frame::OtFrame::submit, FrameStorage::draw_async, or any other GPU DMA start) first runs the guard, so it starts only once every published frame has been kicked and has finished drawing: the channel is never kicked twice, and no queued walk reads memory the CPU path is about to reuse. The reverse also holds: the handler never kicks while a CPU-kicked walk runs, since it needs channel 2 idle. A queued flip waits on GPUSTAT bit 24, which only chains ending in GP0(1Fh) raise, so a CPU-kicked chain should end the same way when a published frame follows it.

Structs§

PresentStats
Counters for telemetry and A/B reports.

Constants§

STALL_VBLANKS
Edges a published frame may wait before the CPU treats the chain ahead of it as stalled (its walk wedged, or it lost its GP0(1Fh)).

Functions§

is_slot_full
True while a published chain is waiting for the handler to kick it.
publish_raw⚠
Hand one chain, and the GP1(05h) word that shows the frame before it, to the VBlank handler. Pass display 0 when nothing is on screen yet.
start
Register the direct-access guard and raise the draw-done flag once, so the first published frame’s edge finds it set. Nothing may be queued and channel 2 must be idle.
stats
Kick, skip and recovery counts since boot.
wait_arena_free
Wait until memory used by the frame before the most recently published one is no longer walked. Once the published frame has been kicked (slot empty), the handler saw the older frame’s GP0(1Fh), so its walk had ended; while it is still queued, the only walk that can be running is the older frame’s. Bounded like wait_slot_empty.
wait_idle
Wait until the GPU is completely idle: nothing queued, no walk running and the last chain’s drawing done. The direct-access guard runs this before the first direct GPU access after a publish. Bounded like wait_slot_empty; a walk it gives up on is stopped by hand.
wait_slot_empty
Block until the handler has kicked the published frame. Spins only when the CPU is a whole frame ahead of presentation. Not inlined, so a profile can tell the spin from work.