Skip to main content

Module ppu

Module ppu 

Source
Expand description

The pixel-processing unit, which draws the screen.

The picture comes from six modules: tile holds the pixels, map says which tile goes in which cell, bg and window place the two layers that read a map, obj carries the sprites drawn over them, and palette decides what a pixel’s colour index becomes.

§Reaching video memory

VRAM and OAM are not always the CPU’s to write. The PPU takes them while it draws, and a write made then is dropped without a word. Every function that writes takes an Access, which is the answer to “how do you know this will land”.

Direct says the caller is already somewhere it will: inside a VBlank, or with the LCD switched off. It is the fast answer, and Vblank::with and with_lcd_off are how one is come by. Nothing enforces the window’s length, so a closure that runs past the end of a VBlank loses the rest of its writes.

Polled says nothing about when it is called, and waits for the PPU itself instead. It reaches far more of the frame than VBlank alone, since HBlank recurs on every line, and it blocks until the whole write is through. For anyone coming from GBDK, this is the shape all of its video memory writes take.

let vblank = unsafe { Vblank::listen() };

// A frame's worth of updates, taken inside the window
vblank.with(|d| {
    map::write(d, bg::map(), col % 32, 0, LEVEL.sub(col, 0, 1, 18));
    obj::set(d, 0, player);
});

// A bulk load: the screen goes blank, and the window has no deadline
ppu::with_lcd_off(|d| tile::write_all(d, 0, &TILESET));

// Nowhere in particular: let the write wait for the PPU itself
tile::write(Access::Polled, 5, &spark);

Work that has to happen between frames belongs before Vblank::with, not inside it: decide what to draw first, then take the window and spend it on writes.

§Writing from a handler

Much of what follows reads a register, changes a bit and writes it back, LCDC most of all: bg, window, obj and tile each own a bit of it and leave the rest alone. A handler doing the same to the same register races whatever it interrupted, and one of the two changes is lost.

palette fares worse. Its colours travel through an index register, set once and stepped by the hardware, so a handler writing a palette of its own sends the rest of the interrupted write into whichever palette it left selected.

So a program driving a raster effect from a STAT handler should keep its other writes to those registers in VBlank, where the two cannot overlap.

§Frame pacing

A program that uses this module links a weak _on_vblank advancing a frame counter. Writing #[gb::rt::interrupt(VBlank)] takes that vector instead, which nothing reports: such a handler must call frame_tick, or Vblank::wait never returns.

Modules§

bg
The background: a tilemap seen through a window the size of the screen.
hdmacgb
The Game Boy Color’s copier into video memory.
map
Tilemaps: two 32 by 32 grids of tile indices.
obj
Objects: the 40 sprites the PPU draws over the background.
palette
Palettes: what the two bits of a pixel turn into.
tile
Tile data: the 384 slots at 0x8000, sixteen bytes each.
window
The window: a second tilemap laid over the background.

Structs§

Vblank
The frame clock: proof that the VBlank interrupt is reaching the counter.

Enums§

Access
How a write reaches video memory.
VramBankcgb
Which half of the Game Boy Color’s video memory the CPU sees at 0x8000.

Functions§

frame_tick
Advance the frame counter.
with_lcd_off
Turn the LCD off for the length of f.
with_vram_bankcgb
Run f with bank mapped at 0x8000, then put back the one that was there.