gb/ppu.rs
1//! The pixel-processing unit, which draws the screen.
2//!
3//! The picture comes from six modules: [`tile`] holds the pixels, [`map`] says
4//! which tile goes in which cell, [`bg`] and [`window`] place the two layers
5//! that read a map, [`obj`] carries the sprites drawn over them, and
6//! [`palette`] decides what a pixel's colour index becomes.
7//!
8//! # Reaching video memory
9//!
10//! VRAM and OAM are not always the CPU's to write. The PPU takes them while it
11//! draws, and a write made then is dropped without a word. Every function that
12//! writes takes an [`Access`], which is the answer to "how do you know this will
13//! land".
14//!
15//! [`Direct`](Access::Direct) says the caller is already somewhere it will:
16//! inside a VBlank, or with the LCD switched off. It is the fast answer, and
17//! [`Vblank::with`] and [`with_lcd_off`] are how one is come by. Nothing enforces
18//! the window's length, so a closure that runs past the end of a VBlank loses
19//! the rest of its writes.
20//!
21//! [`Polled`](Access::Polled) says nothing about when it is called, and waits
22//! for the PPU itself instead. It reaches far more of the frame than VBlank
23//! alone, since HBlank recurs on every line, and it blocks until the whole write
24//! is through. For anyone coming from GBDK, this is the shape all of its video
25//! memory writes take.
26//!
27//! ```ignore
28//! let vblank = unsafe { Vblank::listen() };
29//!
30//! // A frame's worth of updates, taken inside the window
31//! vblank.with(|d| {
32//! map::write(d, bg::map(), col % 32, 0, LEVEL.sub(col, 0, 1, 18));
33//! obj::set(d, 0, player);
34//! });
35//!
36//! // A bulk load: the screen goes blank, and the window has no deadline
37//! ppu::with_lcd_off(|d| tile::write_all(d, 0, &TILESET));
38//!
39//! // Nowhere in particular: let the write wait for the PPU itself
40//! tile::write(Access::Polled, 5, &spark);
41//! ```
42//!
43//! Work that has to happen between frames belongs before [`Vblank::with`], not
44//! inside it: decide what to draw first, then take the window and spend it on
45//! writes.
46//!
47//! # Writing from a handler
48//!
49//! Much of what follows reads a register, changes a bit and writes it back,
50//! `LCDC` most of all: [`bg`], [`window`], [`obj`] and [`tile`] each own a bit
51//! of it and leave the rest alone. A handler doing the same to the same register
52//! races whatever it interrupted, and one of the two changes is lost.
53//!
54//! [`palette`] fares worse. Its colours travel through an index register, set
55//! once and stepped by the hardware, so a handler writing a palette of its own
56//! sends the rest of the interrupted write into whichever palette it left
57//! selected.
58//!
59//! So a program driving a raster effect from a `STAT` handler should keep its
60//! other writes to those registers in VBlank, where the two cannot overlap.
61//!
62//! # Frame pacing
63//!
64//! A program that uses this module links a weak `_on_vblank` advancing a frame
65//! counter. Writing `#[gb::rt::interrupt(VBlank)]` takes that vector instead,
66//! which nothing reports: such a handler must call [`frame_tick`], or
67//! [`Vblank::wait`] never returns.
68
69pub mod bg;
70#[cfg(feature = "cgb")]
71#[cfg_attr(docsrs, doc(cfg(feature = "cgb")))]
72pub mod hdma;
73pub mod map;
74pub mod obj;
75pub mod palette;
76pub mod tile;
77pub mod window;
78
79use core::marker::PhantomData;
80
81use gb_hram::HramAtomicAccess;
82
83// One byte, so each access is a single `ldh` that cannot tear against the
84// handler, and the wrap every 256 frames is harmless to the inequality
85// `Vblank::wait` compares with.
86crate::hram! {
87 static FRAME: HramAtomicCell<u8>;
88}
89
90// Weak, so a handler in the program replaces it. Not `pub`: the symbol is what
91// the vector needs, and calling this as a function would return through `reti`.
92#[linkage = "weak"]
93#[unsafe(no_mangle)]
94extern "z80-interrupt" fn on_vblank() {
95 frame_tick();
96}
97
98/// How a write reaches video memory.
99///
100/// VRAM at `0x8000` and OAM at `0xFE00` are locked on different schedules: VRAM
101/// is reachable except in mode 3, OAM except in modes 2 and 3, and turning the
102/// LCD off opens both. See
103/// <https://gbdev.io/pandocs/Accessing_VRAM_and_OAM.html>.
104///
105/// Where the hardware has them locked it ignores writes and reads back `0xFF`,
106/// so a value written there is lost rather than wrong, and nothing reports it.
107#[derive(Clone, Copy)]
108pub enum Access<'a> {
109 /// The caller is already inside a window where both are open, so writes go
110 /// straight through.
111 ///
112 /// Minted by [`Vblank::with`] and [`with_lcd_off`], and bounded to the closure
113 /// they run. It is zero-sized and `Copy`; the lifetime is the only thing
114 /// stopping it being carried out of the window:
115 ///
116 /// ```compile_fail
117 /// # use gb::ppu::{Access, Vblank};
118 /// # let vblank = unsafe { Vblank::listen() };
119 /// let mut saved: Option<Access> = None;
120 /// vblank.with(|d| { saved = Some(d); }); // ERROR: `d` escapes the closure
121 /// ```
122 #[non_exhaustive]
123 Direct(PhantomData<&'a ()>),
124
125 /// Wait for the PPU to release video memory before writing, which makes the
126 /// call safe at any point in the frame.
127 ///
128 /// This reaches far more of the frame than one VBlank does, since HBlank
129 /// recurs on every line. What it costs is the wait, which a bulk write pays
130 /// for every byte: one this way can hold the CPU for more than a frame while
131 /// the picture stays up.
132 Polled,
133}
134
135// A proof about the PPU's current state belongs to the context that read it.
136impl<'a> !Send for Access<'a> {}
137impl<'a> !Sync for Access<'a> {}
138
139impl<'a> Access<'a> {
140 /// Mint [`Direct`](Access::Direct), asserting that video memory is reachable.
141 ///
142 /// The escape hatch for a context [`Vblank::with`] cannot serve, such as a
143 /// VBlank handler, which is already inside the window it would wait for.
144 ///
145 /// # Safety
146 ///
147 /// The PPU must be in mode 0 or 1, or the LCD off. The returned lifetime is
148 /// unconstrained, so the caller must bound it to the period that holds.
149 pub const unsafe fn assume() -> Self {
150 Access::Direct(PhantomData)
151 }
152
153 /// Copy `src` into video memory at `dst`.
154 ///
155 /// # Safety
156 ///
157 /// `dst` must be a VRAM or OAM address with room for all of `src`.
158 pub(crate) unsafe fn write(self, dst: *mut u8, src: &[u8]) {
159 // One routine for VRAM and OAM: `wait_blank` covers the OAM lock as well
160 // as the VRAM one, so there is nothing left to tell the two apart.
161 //
162 // Resolve the discipline once. Monomorphising on it keeps the branch out
163 // of the loop, which matters where a run is only a byte or two long.
164 match self {
165 Access::Polled => unsafe { run::<true>(dst, src) },
166 _ => unsafe { run::<false>(dst, src) },
167 }
168 }
169}
170
171unsafe fn run<const WAIT: bool>(dst: *mut u8, src: &[u8]) {
172 // Walk the destination rather than indexing from `dst`: one fewer value live
173 // across the wait, in a loop the register allocator is already tight on.
174 let mut d = dst;
175 for b in src {
176 if WAIT {
177 wait_blank();
178 }
179 unsafe { core::ptr::write_volatile(d, *b) };
180 d = unsafe { d.add(1) };
181 }
182}
183
184/// Block until the PPU is between lines or between frames.
185///
186/// Modes 0 and 1 are the two, and seeing either leaves at least 80 dots before
187/// video memory is taken again. Waiting only for mode 3 to pass would not: mode
188/// 2 can be a dot from mode 3, and a write made on the strength of that check
189/// would be dropped after it.
190#[inline(always)]
191pub(crate) fn wait_blank() {
192 use crate::mmio::PpuMode;
193 while matches!(
194 crate::mmio::STAT.read().mode(),
195 PpuMode::OamScan | PpuMode::Drawing
196 ) {}
197}
198
199/// Advance the frame counter.
200///
201/// Needed only by a VBlank handler that replaced the one this module installs.
202#[inline(always)]
203pub fn frame_tick() {
204 // A read-modify-write, and single-writer: the VBlank handler is the only
205 // caller that matters, and the CPU clears IME on dispatch, so it cannot
206 // interrupt itself.
207 FRAME.set(FRAME.get().wrapping_add(1));
208}
209
210/// The frame clock: proof that the VBlank interrupt is reaching the counter.
211///
212/// Everything that waits for a frame hangs without it.
213pub struct Vblank(());
214
215// A proof about the hardware's current state belongs to the context that made it.
216impl !Send for Vblank {}
217impl !Sync for Vblank {}
218
219impl Vblank {
220 /// Listen for the VBlank interrupt.
221 ///
222 /// # Safety
223 ///
224 /// Turns interrupts on. That is preemption, which the surrounding code may
225 /// have been written to rule out.
226 #[inline]
227 pub unsafe fn listen() -> Self {
228 // `IE` is read and written back, and another module may have turned
229 // interrupts on already, so the pair is kept off the air.
230 crate::interrupt::disable();
231 unsafe {
232 crate::interrupt::set_enabled(
233 crate::interrupt::enabled() | crate::mmio::Interrupts::VBLANK,
234 );
235 crate::interrupt::enable();
236 }
237 Vblank(())
238 }
239
240 /// Frames counted since boot, wrapping at 256.
241 ///
242 /// [`wrapping_sub`](u8::wrapping_sub) of two readings is the frames between
243 /// them; a plain subtraction is what overflows across the wrap.
244 #[inline(always)]
245 pub fn frame_count(&self) -> u8 {
246 FRAME.get()
247 }
248
249 /// Block until the next frame.
250 ///
251 /// The CPU sleeps while waiting. Does not return with the LCD off, since no
252 /// VBlank arrives then, nor where a replacement handler skips
253 /// [`frame_tick`].
254 pub fn wait(&self) {
255 let seen = FRAME.get();
256 loop {
257 // VBlank is requested at dot 0 of line 144, so LY reading 143 can be
258 // a single dot away from it, and halting there would sleep through
259 // the frame being waited for. Every other line leaves at least 457
260 // dots, against the ~60 this takes to reach the halt.
261 //
262 // LY is read before the counter so a VBlank landing between the two
263 // is caught by the counter read rather than missed. Volatile and
264 // `asm!` accesses keep that order.
265 let near_vblank = crate::mmio::LY.read() == 143;
266 if FRAME.get() != seen {
267 return;
268 }
269 if !near_vblank {
270 crate::interrupt::halt();
271 }
272 }
273 }
274
275 /// Wait for the next frame, then run `f` inside its VBlank.
276 ///
277 /// Nothing enforces the window. `f` keeps running once the PPU has moved on,
278 /// and writes past that point are dropped in silence.
279 ///
280 /// ```ignore
281 /// vblank.with(|d| {
282 /// bg::set_scroll(d, camera_x, camera_y);
283 /// obj::set(d, 0, player);
284 /// });
285 ///
286 /// // Overruns: the tail of the tileset lands after the window and is lost.
287 /// // `with_lcd_off` has no deadline.
288 /// vblank.with(|d| load_tileset(&TILES, d));
289 /// ```
290 ///
291 /// Waiting goes through [`wait`](Self::wait), so this does not return under
292 /// the same conditions.
293 pub fn with<R>(&self, f: impl FnOnce(Access<'_>) -> R) -> R {
294 self.wait();
295 f(unsafe { Access::assume() })
296 }
297}
298
299/// Turn the LCD off for the length of `f`.
300///
301/// The screen goes blank, and in exchange video memory stays reachable for as
302/// long as `f` runs rather than the roughly 1140 M-cycles [`Vblank::with`]
303/// allows, or 2280 in CGB double speed mode. A program with more tiles than one
304/// VBlank fits loads them this way.
305///
306/// Unlike [`Vblank::with`], the wait here is a poll and needs no interrupt, so
307/// this runs before a program has turned them on.
308///
309/// [`Vblank::wait`] does not return inside `f`, since no VBlank arrives with the LCD
310/// off. An LCD that was already off is left that way and `f` runs at once.
311///
312/// Turning it back on restarts the PPU at line 0, and the screen stays blank
313/// through that first frame. See <https://gbdev.io/pandocs/LCDC.html>.
314pub fn with_lcd_off<R>(f: impl FnOnce(Access<'_>) -> R) -> R {
315 let lcdc = crate::mmio::LCDC.read();
316
317 // Waiting with the LCD already off would never return, and a caller that
318 // switched it off means to keep it off.
319 if !lcdc.lcd_enable() {
320 return f(unsafe { Access::assume() });
321 }
322
323 // Clearing the enable bit outside VBlank can damage the panel, so wait for
324 // one. The line is polled rather than waited on through `Vblank`: this runs
325 // before interrupts are on, which is where a program loads its tiles,
326 // and it leaves `IME` alone rather than ending a critical section it was
327 // called inside.
328 //
329 // The first loop skips a VBlank already under way, so the second catches the
330 // next one near its start. A handler landing between that read and the write
331 // would have to outlast the rest of the VBlank to push the write out of it.
332 while crate::mmio::LY.read() >= 146 {}
333 while crate::mmio::LY.read() < 145 {}
334
335 // Re-read: a handler may have reconfigured the PPU during the wait.
336 unsafe { crate::mmio::LCDC.write(crate::mmio::LCDC.read().with_lcd_enable(false)) };
337
338 let r = f(unsafe { Access::assume() });
339
340 // Re-read rather than writing `lcdc` back: `f` may have reconfigured the
341 // PPU, and only the enable bit is this function's to restore.
342 unsafe { crate::mmio::LCDC.write(crate::mmio::LCDC.read().with_lcd_enable(true)) };
343 r
344}
345
346/// Which half of the Game Boy Color's video memory the CPU sees at `0x8000`.
347///
348/// The PPU reads both halves through its own paths, so this is only about CPU
349/// access.
350#[cfg(feature = "cgb")]
351#[cfg_attr(docsrs, doc(cfg(feature = "cgb")))]
352#[derive(Clone, Copy, PartialEq, Eq, Debug)]
353pub enum VramBank {
354 /// Tile slots and the two tilemaps, as on the original Game Boy.
355 Zero = 0,
356 /// A second set of tile slots, and per-cell attributes where bank zero holds
357 /// the tilemaps.
358 One = 1,
359}
360
361/// Run `f` with `bank` mapped at `0x8000`, then put back the one that was there.
362///
363/// The Game Boy Color banks all of `0x8000..0xA000`, so the same
364/// [`tile`] or map write lands in a different place depending on this. Bracketing
365/// it keeps that visible at the call site and out of the surrounding code.
366///
367/// `f` takes nothing: the [`Access`] an enclosing [`Vblank::with`] or
368/// [`with_lcd_off`] handed out is still good here, since which bank is mapped and
369/// whether video memory is reachable are separate questions. The two scopes nest
370/// in either order.
371///
372/// A handler that changes the bank must put it back, the way one that switches
373/// ROM banks must, and one that writes video memory has to set the bank it means
374/// rather than assume zero: it may well have interrupted this.
375///
376/// An original Game Boy has no such register: the write goes nowhere and `f` runs against
377/// the one bank there is, overwriting what was already in it. A cartridge that
378/// runs on both machines has to ask [`is_cgb`](crate::is_cgb) first, there being
379/// no second bank to fall back to.
380///
381/// ```ignore
382/// ppu::with_lcd_off(|d| {
383/// tile::write_all(d, 0, &TILES);
384/// ppu::with_vram_bank(VramBank::One, || tile::write_all(d, 0, &MORE));
385/// });
386/// ```
387#[cfg(feature = "cgb")]
388#[cfg_attr(docsrs, doc(cfg(feature = "cgb")))]
389#[inline]
390pub fn with_vram_bank<R>(bank: VramBank, f: impl FnOnce() -> R) -> R {
391 // Bit 0 is the bank; the rest read as ones.
392 let saved = crate::mmio::cgb::VBK.read() & 1;
393 crate::mmio::cgb::VBK.write(bank as u8);
394 let r = f();
395 crate::mmio::cgb::VBK.write(saved);
396 r
397}