Flow C: PPU frame rendering

The Picture Processing Unit (implemented entirely in display.c) is advanced by GB_display_run on every GB_advance_cycles call. A complete frame ends at GB_display_vblank.

Trigger

Implicit — any CPU bus cycle advances the PPU by the same T-cycle count via GB_advance_cyclesGB_display_run(gb, cycles, false).

LCD timing overview

One scanline (456 T-cycles @ 4.19 MHz): Mode 2 (OAM Search) 80 T Mode 3 (Pixel Transfer) 172–291 T (variable) Mode 0 (H-Blank) remainder 144 visible lines + 10 V-Blank lines = 154 lines/frame GB_display_run uses GB_BATCHABLE_STATE_MACHINE: display states 1–22+ encode fetcher, FIFO push, HBlank, VBlank End of frame → GB_display_vblank(GB_VBLANK_TYPE_NORMAL_FRAME) → gb->vblank_just_occured = true → vblank_callback(gb, type) → GB_rewind_push (in GB_run)
GB_display_run — state machine entry

GB_display_run (display.c:1533) is the PPU heartbeat:

  • Handles window Y trigger scheduling (wy_check_scheduled)
  • Splits cycles at line boundaries when batch would cross LINE_LENGTH
  • DMG STOP mode: PPU frozen except artificial vblank for timing
  • Runs GB_BATCHABLE_STATE_MACHINE(gb, display, cycles, 2, !force) — states 1–17+ for pixel pipeline

The state machine implements: tile fetch (2 bytes per tile), BG FIFO, OAM sprite sorting, priority mixing, CGB palette indexing, and STAT interrupt line updates.

GB_display_run

Pixel output path

Rendered pixels write to gb->screen (uint32_t ARGB buffer allocated by frontend or core):

  1. PPU computes palette index per pixel
  2. rgb_encode_callback converts (or internal GB_convert_rgb15 for CGB)
  3. Border tiles composited when border_mode != NEVER (DMG/CGB/SGB borders)
  4. SGB HLE may render border and palettes via GB_sgb_render in vblank

Screen dimensions: 160×144 game area; bordered modes use larger buffer (BORDERED_WIDTH × BORDERED_HEIGHT).

Frontends call GB_set_pixels_output(gb, buffer) to set write pointer; updated each vblank after buffer swap.

GB_display_vblank — frame completion

At frame end (display.c:173):

  1. Sets vblank_just_occured = true (consumed by GB_run)
  2. Updates debugger CPU usage counters
  3. SGB: GB_sgb_render if HLE SGB active
  4. Turbo: GB_timing_sync_turbo may skip render and fire GB_VBLANK_TYPE_SKIPPED_FRAME
  5. LCD off: fill screen white/black per model
  6. Normal path: invoke vblank_callback(gb, type)
  7. GB_handle_rumble, GB_timing_sync

Vblank types: NORMAL_FRAME, SKIPPED_FRAME, REPEAT (CGB frame repeat), LCD_OFF, ARTIFICIAL (STOP mode DMG).

GB_display_vblank

Key video section fields
FieldRole
bg_fifo, oam_fifo8-entry pixel FIFOs
visible_objects[10]Sprites on current line
current_line, cycles_for_lineLY and intra-line position
display_stateState machine phase (1–22+)
background_palettes[32]CGB BG palette RAM
oam[160]Sprite attribute table
frame_skip_stateLCD enable/disable edge handling

Failure modes and accuracy risks

  • STAT mode delay TODO — simplifying PPU states may break STAT IRQ tests
  • Mode 3 duration varies with sprite count — wrong object limit → graphical glitches
  • HDMA during mode 3 conflicts — dma_ppu_vram_conflict tracks bus fights
  • Disabling rendering (disable_rendering) used by tester — must not skip vblank flag incorrectly

External reference: Pan Docs — LCD Timing