psx_gpu/lib.rs
1// SPDX-License-Identifier: GPL-2.0-or-later
2//! The PS1 GPU: a driver that owns it, the packets it draws, and safe
3//! frame building over its linked-list DMA.
4//!
5//! # Layers
6//!
7//! - [`Gpu`] owns GP0, GP1 and DMA channel 2 through the
8//! [`GpuDma`](psx_io::periph::GpuDma) token. Every port write is a method,
9//! so the borrow checker keeps immediate drawing out of a running walk.
10//! - [`prim`] holds the packets: `repr(C)` structs whose words are the GP0
11//! wire format. [`Gpu::draw`] sends one now; an [`ot::OrderingTable`]
12//! frame links many for one DMA walk.
13//! - [`frame`] builds frames with lifetimes instead of `unsafe`: a packet
14//! added to an [`frame::OtFrame`] stays borrowed until the walk that reads
15//! it has finished.
16//! - [`ordered`] streams packets in painter order over static storage.
17//! - [`display`] describes what the GPU shows; [`material`] describes how
18//! textured packets sample and blend.
19//! - The raw layer, [`chain::submit_async_raw`] and the `*_unchecked`
20//! adds on [`frame::OtFrame`], is `unsafe`: it hands the DMA controller
21//! addresses the type system cannot follow.
22//!
23//! Register addresses and command-word encoders live in `psx-hw`, shared
24//! with the emulator's GPU, so the two cannot disagree on a layout.
25
26#![no_std]
27#![cfg_attr(target_arch = "mips", feature(asm_experimental_arch))]
28
29pub mod chain;
30mod compat;
31pub mod display;
32pub mod frame;
33pub mod framebuf;
34mod gpu;
35pub mod material;
36pub mod ordered;
37pub mod ot;
38pub mod prim;
39
40#[allow(deprecated, reason = "the forwarders kept for one stage")]
41pub use compat::{
42 arm_draw_done, draw_line_mono, draw_quad_flat, draw_quad_textured,
43 draw_quad_textured_gouraud_material, draw_quad_textured_material, draw_rect_flat,
44 draw_sprite_material, draw_sync, draw_tri_flat, draw_tri_flat_blended, draw_tri_gouraud,
45 fill_rect, init, set_draw_area, set_draw_offset, signal_draw_done, submit_linked_list_async,
46 submit_linked_list_async_raw, submit_linked_list_raw, submit_linked_list_raw_async,
47 submit_linked_list_wait, vsync, wait_idle,
48};
49// The chain items' old root paths, kept for one stage: they are the same
50// items, so they cannot carry a deprecation of their own.
51pub use chain::{DrawDoneNode, StaticChain, StaticPacket, DRAW_DONE_NODE, MAX_NODE_WORDS};
52pub use gpu::{Gpu, MaskMode};
53
54use psx_io::gpu::wait_command_ready;
55
56/// Moved to [`display::VideoMode`].
57#[deprecated(note = "moved to `psx_gpu::display::VideoMode`")]
58pub type VideoMode = display::VideoMode;
59
60/// Moved to [`display::Resolution`].
61#[deprecated(note = "moved to `psx_gpu::display::Resolution`")]
62pub type Resolution = display::Resolution;
63
64/// [`Gpu::wait_idle`]'s waits, shared with the deprecated free function.
65///
66/// Waits for DMA channel 2 to finish its walk, then for GPUSTAT bit 28
67/// (ready for a DMA block), then for bit 26 (ready for a command word).
68/// Bit 28 alone is not a drawing-complete test: on silicon it rises when
69/// the walk has pushed its last packet, about one large primitive before
70/// the drawing ends. Hardware-tests v1.24 cases 219-226 put bit 28's final
71/// rise at the channel's completion (586,354 and 275,124 clocks on the two
72/// large-triangle lists) and bit 26's at the list's closing GP0(1Fh)
73/// (625,348 and 314,075). PSn00bSDK's `DrawSync` waits the same way.
74///
75/// Every wait is bounded, with the recovery of [`chain::wait`]
76/// and `psx_io::gpu::wait_command_ready`, so a wedged GPU costs a reset
77/// instead of a hang.
78#[inline]
79pub(crate) fn wait_idle_impl() {
80 // A frame handed to psx-rt's present queue is still "sent to the GPU":
81 // the armed guard waits until it has been kicked and drawn. Without the
82 // `present-queue` feature this is nothing.
83 psx_io::gpu::run_direct_access_guard();
84 chain::wait_walk();
85 psx_io::gpu::wait_dma_ready();
86 wait_command_ready();
87}
88
89/// True once the GPU has executed the GP0(1Fh) that closes the work kicked
90/// after the last [`Gpu::arm_draw_done`], so everything before it is drawn.
91///
92/// This is the completion test psx-rt's queued display flip
93/// (`psx_rt::interrupts::queue_display_control_at_vblank`) applies at each VBlank edge.
94/// GP0(1Fh) raises GPUSTAT bit 24 only when the GPU reaches it in its
95/// command stream, after the drawing before it; the flag stays set until
96/// GP1(02h). The v1.24 present-queue probe flipped on this flag with 120 of
97/// 120 frames complete on a console. End a DMA chain with it through
98/// [`ot::OrderingTable::end_with_draw_done`] or [`chain::DRAW_DONE_NODE`], an
99/// ordered stream with `push_packet([gp0::REQUEST_IRQ])`, and immediate
100/// drawing with [`Gpu::signal_draw_done`].
101///
102/// Arm the flag right before kicking a frame whose last command is GP0(1Fh),
103/// and only once the previous frame's queued flip has been applied:
104/// acknowledging earlier hides the previous frame's completion from psx-rt's
105/// VBlank handler.
106///
107/// GP0(1Fh) also raises interrupt source 1 (GPU) in `I_STAT`; keep it masked
108/// in `I_MASK`, since psx-rt's handler does not acknowledge it.
109///
110/// It only reads GPUSTAT, so it needs no [`Gpu`].
111#[inline]
112pub fn is_draw_done() -> bool {
113 psx_io::gpu::status().contains(psx_hw::gpu::GpuStat::IRQ1)
114}
115
116/// How often the SDK recovered a hung GPU since boot, for a debug overlay
117/// or a test log. Every GPU wait is bounded and recovers by resetting the
118/// GPU; these counts make the recoveries visible.
119#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
120pub struct RecoveryStats {
121 /// Linked-list walks aborted because they outlived their spin budget.
122 pub dma_aborts: u32,
123 /// Command-buffer resets after a ready bit stayed low.
124 pub command_resets: u32,
125}
126
127/// The recovery counts since boot. Reads only, so it needs no [`Gpu`].
128pub fn recovery_stats() -> RecoveryStats {
129 RecoveryStats {
130 dma_aborts: chain::dma_abort_count(),
131 command_resets: psx_io::gpu::command_reset_count(),
132 }
133}