psx_gpu/gpu.rs
1//! The GPU driver.
2
3use crate::chain::StaticChain;
4use crate::display::DisplayConfig;
5use crate::material::{TextureMaterial, TextureWindow};
6use crate::prim::GpuPacket;
7use psx_hw::gpu::{gp0, gp1, DmaDirection};
8use psx_io::gpu::{wait_command_ready, write_command, write_display_control};
9use psx_io::periph::GpuDma;
10
11bitflags::bitflags! {
12 /// GP0(E6h) mask-bit state.
13 ///
14 /// With both flags set the GPU gives front-to-back occlusion without a
15 /// Z-buffer: draw nearest first, and farther pixels that would overdraw
16 /// are rejected. The state applies until changed, so return to
17 /// [`MaskMode::empty`] before translucent passes that must not mask.
18 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
19 pub struct MaskMode: u32 {
20 /// Force bit 15 on every pixel written.
21 const SET_ON_DRAW = 1 << 0;
22 /// Skip pixels whose bit 15 is already set.
23 const CHECK_BEFORE_DRAW = 1 << 1;
24 }
25}
26
27/// The PS1 GPU: its command port (GP0), its control port (GP1) and DMA
28/// channel 2, which feeds GP0.
29///
30/// `Gpu` owns the [`GpuDma`] token, so every operation that writes a GPU
31/// port is a method that needs `&mut Gpu`. That puts the borrow checker
32/// between two users of one command stream: drawing immediately while a
33/// linked-list walk feeds GP0 is a compile error, not a corrupted frame.
34///
35/// ```compile_fail,E0500
36/// # use psx_gpu::{ot::OrderingTable, prim::TriFlat, Gpu};
37/// # fn f(gpu: &mut Gpu, ot: &mut OrderingTable<8>) {
38/// let frame = ot.frame();
39/// frame.submit_with(gpu.dma_mut(), || {
40/// gpu.draw(&TriFlat::new([(0, 0), (8, 0), (0, 8)], 255, 0, 0)); // GP0 mid-walk
41/// });
42/// # }
43/// ```
44///
45/// It is zero-sized, so holding and passing it costs nothing. Reading
46/// status (`psx_gpu::is_draw_done`, `psx_io::gpu::status`) needs no
47/// handle: a read can't disturb the command stream.
48///
49/// ```no_run
50/// use psx_gpu::display::{DisplayConfig, Resolution, VideoMode};
51/// use psx_gpu::prim::FillRect;
52/// use psx_gpu::Gpu;
53///
54/// fn start(dma: psx_io::periph::GpuDma) -> Gpu {
55/// let mut gpu = Gpu::new(dma, DisplayConfig::new(VideoMode::Ntsc, Resolution::R320X240));
56/// gpu.draw(&FillRect::new((0, 0), (320, 240), (0, 0, 32)));
57/// gpu
58/// }
59/// ```
60#[derive(Debug)]
61#[repr(transparent)]
62pub struct Gpu(GpuDma);
63
64impl Gpu {
65 /// Take over the GPU: reset it, program `display`, point DMA at GP0 and
66 /// turn the display on.
67 #[doc(alias = "ResetGraph")]
68 pub fn new(dma: GpuDma, display: DisplayConfig) -> Self {
69 reset(display);
70 Self(dma)
71 }
72
73 /// The driver as a view of a token held elsewhere, such as the one a
74 /// `FramePair` owns, for immediate drawing between its frames.
75 #[inline(always)]
76 pub fn from_dma_mut(dma: &mut GpuDma) -> &mut Self {
77 // SAFETY: `Gpu` is a `repr(transparent)` wrapper of `GpuDma`, so the
78 // two types have the same layout and the cast keeps the exclusive
79 // borrow.
80 unsafe { &mut *core::ptr::from_mut(dma).cast::<Self>() }
81 }
82
83 /// The token, for APIs that start channel 2 themselves (frame
84 /// submission, VRAM DMA). The driver stays borrowed meanwhile.
85 #[inline(always)]
86 pub fn dma_mut(&mut self) -> &mut GpuDma {
87 &mut self.0
88 }
89
90 /// Give the token back.
91 #[inline(always)]
92 pub fn release(self) -> GpuDma {
93 self.0
94 }
95
96 /// Reprogram the display mode and the picture position.
97 #[doc(alias = "PutDispEnv")]
98 pub fn set_display(&mut self, display: DisplayConfig) {
99 write_display_control(display.mode_command());
100 write_display_window(display);
101 }
102
103 /// Show VRAM from `origin` (GP1(05h)): a double-buffer flip.
104 #[inline]
105 pub fn set_display_start(&mut self, origin: (u16, u16)) {
106 write_display_control(gp1::display_start(origin.0 as u32, origin.1 as u32));
107 }
108
109 /// Turn the video output on or off (GP1(03h)).
110 #[doc(alias = "SetDispMask")]
111 #[inline]
112 pub fn set_display_enabled(&mut self, enabled: bool) {
113 write_display_control(gp1::display_enable(enabled));
114 }
115
116 /// Clip drawing to the rectangle from `top_left` to `bottom_right`
117 /// (inclusive VRAM coordinates).
118 #[inline]
119 pub fn set_draw_area(&mut self, top_left: (u16, u16), bottom_right: (u16, u16)) {
120 set_draw_area(top_left, bottom_right);
121 }
122
123 /// Add `offset` to every vertex drawn from now on (GP0(E5h)), so a
124 /// back buffer can be drawn in its own coordinates.
125 #[inline]
126 pub fn set_draw_offset(&mut self, offset: (i16, i16)) {
127 set_draw_offset(offset);
128 }
129
130 /// Set the mask-bit state (GP0(E6h)).
131 #[inline]
132 pub fn set_mask_mode(&mut self, mode: MaskMode) {
133 wait_command_ready();
134 write_command(gp0::mask_bit(
135 mode.contains(MaskMode::SET_ON_DRAW),
136 mode.contains(MaskMode::CHECK_BEFORE_DRAW),
137 ));
138 }
139
140 /// Apply `material`'s texture page, blend equation, dither and
141 /// texture window (GP0(E1h), GP0(E2h)).
142 #[inline]
143 pub fn set_draw_mode(&mut self, material: TextureMaterial) {
144 set_draw_mode(material);
145 }
146
147 /// Set the texture window (GP0(E2h)).
148 #[inline]
149 pub fn set_texture_window(&mut self, window: TextureWindow) {
150 wait_command_ready();
151 write_command(window.word());
152 }
153
154 /// Send `packet` to GP0 now, without an ordering table.
155 ///
156 /// The same packet types go into an `OtFrame`, so a primitive has one
157 /// encoder whether it is drawn now or linked into a frame.
158 #[inline(always)]
159 pub fn draw<P: GpuPacket>(&mut self, packet: &P) {
160 draw(packet);
161 }
162
163 /// Wait until the GPU has drawn everything sent to it.
164 ///
165 /// See [`crate::wait_idle`] for what that waits on.
166 #[doc(alias = "DrawSync")]
167 #[inline]
168 pub fn wait_idle(&mut self) {
169 crate::wait_idle_impl();
170 }
171
172 /// Clear GPUSTAT bit 24 (GP1(02h)) before kicking work that ends in
173 /// GP0(1Fh); see `psx_gpu::is_draw_done`.
174 #[inline]
175 pub fn arm_draw_done(&mut self) {
176 write_display_control(gp1::ACK_IRQ);
177 }
178
179 /// Send GP0(1Fh), closing immediate drawing so `is_draw_done` rises
180 /// once it is drawn.
181 #[inline]
182 pub fn signal_draw_done(&mut self) {
183 wait_command_ready();
184 write_command(gp0::REQUEST_IRQ);
185 }
186
187 /// Kick a `'static`, immutable chain, such as
188 /// [`DRAW_DONE_NODE`](crate::chain::DRAW_DONE_NODE), without waiting for it.
189 ///
190 /// The chain outlives any walk, so there is nothing to wait for before
191 /// reusing memory; a later kick waits for this walk on its own.
192 #[inline]
193 pub fn submit_static(&mut self, chain: &'static impl StaticChain) {
194 // SAFETY: `StaticChain` guarantees a well-formed list that stays live
195 // and unmodified for 'static.
196 unsafe { crate::chain::start_walk(chain.head()) }
197 }
198}
199
200/// [`Gpu::new`]'s port writes.
201pub(crate) fn reset(display: DisplayConfig) {
202 write_display_control(gp1::RESET);
203 write_display_control(display.mode_command());
204 write_display_window(display);
205 write_display_control(gp1::dma_direction(DmaDirection::CpuToGp0 as u32));
206 write_display_control(gp1::display_enable(true));
207}
208
209/// GP1(06h) and GP1(07h) for `display`.
210pub(crate) fn write_display_window(display: DisplayConfig) {
211 write_display_control(display.horizontal_range_command());
212 write_display_control(display.vertical_range_command());
213}
214
215pub(crate) fn set_draw_area(top_left: (u16, u16), bottom_right: (u16, u16)) {
216 wait_command_ready();
217 write_command(gp0::draw_area_top_left(
218 top_left.0 as u32,
219 top_left.1 as u32,
220 ));
221 write_command(gp0::draw_area_bottom_right(
222 bottom_right.0 as u32,
223 bottom_right.1 as u32,
224 ));
225}
226
227pub(crate) fn set_draw_offset(offset: (i16, i16)) {
228 wait_command_ready();
229 write_command(gp0::draw_offset(offset.0 as i32, offset.1 as i32));
230}
231
232pub(crate) fn set_draw_mode(material: TextureMaterial) {
233 wait_command_ready();
234 write_command(material.draw_mode_word());
235 write_command(material.texture_window_word());
236}
237
238/// One ready-wait, then the packet's payload words, in order.
239#[inline(always)]
240pub(crate) fn draw<P: GpuPacket>(packet: &P) {
241 let words = core::ptr::from_ref(packet).cast::<u32>();
242 wait_command_ready();
243 let mut index = 1;
244 while index <= P::WORDS as usize {
245 // SAFETY: `GpuPacket` guarantees a `repr(C)` layout of a tag word and
246 // then at least `WORDS` initialised `u32`s, so words 1..=WORDS are
247 // inside `packet`, which the borrow keeps alive and unchanged.
248 write_command(unsafe { words.add(index).read() });
249 index += 1;
250 }
251}
252
253/// Draw area and offset for a buffer at VRAM line `top`, written without a
254/// ready-wait, as `FrameBuffer` always has: state commands between frames.
255pub(crate) fn write_draw_target(top: u16, width: u16, height: u16) {
256 write_command(gp0::draw_area_top_left(0, top as u32));
257 write_command(gp0::draw_area_bottom_right(
258 (width - 1) as u32,
259 (top + height - 1) as u32,
260 ));
261 write_command(gp0::draw_offset(0, top as i32));
262}