282. Ppu background to framebuffer

Render the current PPU background memory into a framebuffer.

Lesson 282 of 356 · tests/chapter_05_rendering_pipeline/test_282_ppu_background_to_framebuffer.py

File to create

emulator/rendering/ppu_background_renderer.py

Why this step exists

The renderer can now produce a background framebuffer from explicit byte arrays:

nametable bytes
attribute table bytes
pattern table bytes
palette RAM bytes

But the emulator's current state stores those bytes behind the PPU/PpuBus memory interface. This step creates a thin extraction helper:

ppu_background_to_framebuffer(ppu)

It reads the relevant PPU memory regions, then delegates to the pure renderer.

Memory regions used in this simplified renderer

$2000-$23BF -> visible nametable tile IDs, 960 bytes
$23C0-$23FF -> attribute table, 64 bytes
$0000-$0FFF -> pattern table 0, if PPUCTRL bit 4 is clear
$1000-$1FFF -> pattern table 1, if PPUCTRL bit 4 is set
$3F00-$3F0F -> background palette RAM, 16 bytes

Suggested implementation example

def ppu_background_to_framebuffer(ppu: PPU) -> Framebuffer:
    nametable_bytes = bytes(
        ppu.ppu_bus.read(BASE_NAMETABLE_ADDR + offset)
        for offset in range(NAMETABLE_SIZE)
    )

    attribute_table = bytes(
        ppu.ppu_bus.read(BASE_ATTR_TABLE_ADDR + offset)
        for offset in range(ATTR_TABLE_SIZE)
    )

    pattern_table_base = (
        PATTERN_TABLE_1_ADDR
        if ppu.ctrl & CTRL_BACKGROUND_PATTERN_TABLE
        else PATTERN_TABLE_0_ADDR
    )

    pattern_table_bytes = bytes(
        ppu.ppu_bus.read(pattern_table_base + offset)
        for offset in range(PATTERN_TABLE_SIZE)
    )

    palette_ram = bytes(
        ppu.ppu_bus.read(PALETTE_RAM_ADDR + offset)
        for offset in range(PALETTE_RAM_SIZE)
    )

    return nametable_with_palette_ram_to_framebuffer(
        nametable_bytes,
        attribute_table,
        pattern_table_bytes,
        palette_ram,
    )

Architecture rule

This helper extracts data from PPU memory. It should not duplicate nametable, attribute, CHR, or palette rendering logic.

Important simplification

This renders only the base nametable at $2000. It does not apply scrolling or PPUCTRL nametable-selection bits yet.

Out of scope

  • scrolling
  • PPUCTRL base nametable selection
  • sprites
  • OAMDMA
  • pygame display
  • full frame loop

Run this lesson

uv run pytest tests/chapter_05_rendering_pipeline/test_282_ppu_background_to_framebuffer.py -v