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.
- hdma
cgb - 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.
- Vram
Bank cgb - 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_ bank cgb - Run
fwithbankmapped at0x8000, then put back the one that was there.