282. PPU 背景到帧缓冲

将当前 PPU 背景内存渲染到帧缓冲中。

282 / 356 · tests/chapter_05_rendering_pipeline/test_282_ppu_background_to_framebuffer.py

待创建文件

emulator/rendering/ppu_background_renderer.py

为什么需要这一步

渲染器现在可以从显式的字节数组生成背景帧缓冲:

nametable bytes
attribute table bytes
pattern table bytes
palette RAM bytes

但模拟器当前的状态是把这些字节存放在 PPU/PpuBus 内存接口之后的。这一步创建一个薄薄的提取辅助函数:

ppu_background_to_framebuffer(ppu)

它读取相关的 PPU 内存区域,然后委托给纯渲染器处理。

这个简化渲染器使用的内存区域

$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

建议的实现示例

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,
    )

架构规则

这个辅助函数从 PPU 内存中提取数据。它不应重复实现名称表、属性、CHR 或调色板的渲染逻辑。

重要的简化

这里只渲染 $2000 处的基础名称表,尚未应用滚动或 PPUCTRL 名称表选择位。

范围之外

  • 滚动
  • PPUCTRL 基础名称表选择
  • 精灵
  • OAMDMA
  • pygame 显示
  • 完整的帧循环

运行本课

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