Expand description
Borrow-checked frame building over an ordering table.
The GPU’s linked-list DMA reads every packet of a frame after the CPU has
handed it over, so a packet must outlive the walk and must not change
during it. This module states that with lifetimes instead of unsafe:
OrderingTable::frameclears the table and borrows it as anOtFrame<'f, N>for'f.OtFrame::addtakes each packet as&'f mut P, so the packet stays borrowed, and unmovable, for as long as the frame can be walked.OtFrame::submitandOtFrame::submit_withwait for the walk before they return, which is what lets'fend.PrimitiveArenasplits caller storage into&'f mut Ppackets.- For a walk that outlives the call that kicked it (CPU frame N+1 built
while the GPU walks frame N),
FrameStorage::draw_asyncneeds the table and packet storage&'static mutand returns anInFlightthat owns them, and theGpuDmatoken, untilInFlight::wait. Forgetting it leaks the storage rather than freeing it under the walk.FramePairruns the usual two-buffer ping-pong on top.
Every type here is a thin wrapper over the same stores and kicks as
OtFrame::add_raw and crate::chain::submit_raw.
§Usage
use psx_gpu::frame::{FramePair, FrameStorage, PrimitiveArena};
use psx_gpu::{ot::OrderingTable, prim::TriFlat};
fn blocking(ot: &mut OrderingTable<8>, dma: &mut psx_io::periph::GpuDma) {
let mut storage: [TriFlat; 4] =
core::array::from_fn(|_| TriFlat::new([(0, 0); 3], 0, 0, 0));
let mut frame = ot.frame();
let mut arena = PrimitiveArena::new(&mut storage);
if let Some(tri) = arena.push(TriFlat::new([(0, 0), (8, 0), (0, 8)], 255, 0, 0)) {
frame.add(1, tri);
}
frame.submit(dma);
// The walk is over: the table and the storage are free again.
ot.clear();
storage[0] = TriFlat::new([(0, 0); 3], 0, 0, 0);
}
fn overlapped(
storage: &'static mut FrameStorage<8, [TriFlat; 4]>,
dma: psx_io::periph::GpuDma,
) {
let (in_flight, added) = storage.draw_async(dma, |frame, packets| {
let mut arena = PrimitiveArena::new(packets);
let tri = arena.push(TriFlat::new([(0, 0), (8, 0), (0, 8)], 0, 255, 0));
tri.map(|tri| frame.add(1, tri)).is_some()
});
// ... CPU work while the GPU walks ...
let (storage, dma) = in_flight.wait();
let mut pair = FramePair::new(storage, other_storage(), dma);
pair.present(|_frame, _packets| {});
}§What the borrow checker rejects
A packet that dies before the frame is submitted:
ⓘ
let mut ot = OrderingTable::<8>::new();
let mut frame = ot.frame();
{
let mut tri = TriFlat::new([(0, 0), (8, 0), (0, 8)], 255, 0, 0);
frame.add(1, &mut tri);
} // `tri` dropped here while the frame still links it
frame.submit(dma);Clearing (or otherwise touching) the table while its walk is in flight:
ⓘ
let mut ot = OrderingTable::<8>::new();
let frame = ot.frame();
frame.submit_with(dma, || ot.clear());Reusing the storage of an asynchronously kicked frame before waiting:
ⓘ
let (in_flight, ()) = storage.draw_async(dma, |_frame, _packets| {});
storage.draw_async(unsafe { psx_io::periph::GpuDma::steal() }, |_frame, _packets| {});
in_flight.wait();Restarting an arena over storage whose packets are still linked:
ⓘ
let mut ot = OrderingTable::<8>::new();
let mut storage: [TriFlat; 4] = core::array::from_fn(|_| TriFlat::new([(0, 0); 3], 0, 0, 0));
let mut frame = ot.frame();
let mut arena = PrimitiveArena::new(&mut storage);
frame.add(1, arena.push(TriFlat::new([(0, 0), (8, 0), (0, 8)], 255, 0, 0)).unwrap());
let mut again = PrimitiveArena::new(&mut storage); // still borrowed by the frame
again.push(TriFlat::new([(0, 0); 3], 0, 0, 0));
frame.submit(dma);Structs§
- Frame
Pair - Two
FrameStorages in the usual ping-pong: build frame N+1 into one while the GPU walks frame N from the other. - Frame
Storage - An ordering table together with the packet storage its frames link.
- InFlight
- A frame the GPU is walking, owning its
'staticstorage and theGpuDmatoken untilwait. - OtFrame
- One frame’s view of an
OrderingTable: insert packets, then submit. - Primitive
Arena - Hands out packets from caller storage as
&'f mut P, one slot each.