280. 从调色板 RAM 生成背景调色板

根据 PPU 调色板 RAM 字节构建 RGB 背景调色板。

280 / 356 · tests/chapter_05_rendering_pipeline/test_280_background_palettes_from_palette_ram.py

参考

https://www.nesdev.org/wiki/PPU_palettes#Palette_RAM

待创建的文件

emulator/rendering/palette_ram.py

为什么需要这一步

支持属性表的名称表渲染器需要四个已经解析好的 RGB 背景调色板:

background_palettes[palette_id][color_index]

但真实的 NES 游戏并不会把 RGB 颜色存储在名称表或属性表中,而是把 NES 颜色索引写入 PPU 调色板 RAM。

这个辅助函数把

PPU palette RAM bytes -> RGB background palettes

什么是 PPU 调色板 RAM?

PPU 调色板 RAM 是一块映射在以下位置的 32 字节小区域

$3F00-$3F1F

本步骤中我们只使用前 16 个字节

$3F00-$3F0F = background palette area

这些字节存储的是 NES 颜色索引 $00-$3F,而不是 RGB 值。

流程

palette RAM byte
    -> NES color index $00-$3F
    -> get_nes_rgb_color(index)
    -> RGB tuple

背景底色/通用背景色

对于背景渲染,颜色索引 0 使用 $3F00 处的共享底色。

因此每个返回的背景调色板都使用相同的第一个 RGB 颜色

background_palettes[0][0] == backdrop
background_palettes[1][0] == backdrop
background_palettes[2][0] == backdrop
background_palettes[3][0] == backdrop

$3F00-$3F0F 对应的背景调色板布局:

$3F00 -> backdrop / universal background color

palette 0:
    entry 0 -> $3F00
    entry 1 -> $3F01
    entry 2 -> $3F02
    entry 3 -> $3F03

palette 1:
    entry 0 -> $3F00
    entry 1 -> $3F05
    entry 2 -> $3F06
    entry 3 -> $3F07

palette 2:
    entry 0 -> $3F00
    entry 1 -> $3F09
    entry 2 -> $3F0A
    entry 3 -> $3F0B

palette 3:
    entry 0 -> $3F00
    entry 1 -> $3F0D
    entry 2 -> $3F0E
    entry 3 -> $3F0F

请注意

$3F04, $3F08, and $3F0C are not used as independent background color-0
entries in this simplified helper. Color index 0 uses the shared backdrop.

建议的实现示例

from emulator.rendering.framebuffer import RGBColor
from emulator.rendering.nes_palette import get_nes_rgb_color

PALETTE_RAM_SIZE = 16
TOTAL_PALETTES = 4
COLORS_PER_PALETTE = 4

BackgroundPalettes = list[list[RGBColor]]


def build_background_palettes_from_palette_ram(
    palette_ram: bytes,
) -> BackgroundPalettes:
    if len(palette_ram) != PALETTE_RAM_SIZE:
        raise ValueError(f"Background palette RAM must be {PALETTE_RAM_SIZE} bytes")

    backdrop_color = get_nes_rgb_color(palette_ram[0])
    background_palettes = []

    for palette_id in range(TOTAL_PALETTES):
        base = palette_id * COLORS_PER_PALETTE

        palette = [
            backdrop_color,
            get_nes_rgb_color(palette_ram[base + 1]),
            get_nes_rgb_color(palette_ram[base + 2]),
            get_nes_rgb_color(palette_ram[base + 3]),
        ]

        background_palettes.append(palette)

    return background_palettes

本步骤不涉及

  • 精灵调色板
  • 精灵透明度行为
  • 调色板 RAM 镜像
  • 直接从 PPU 总线读取
  • PPUMASK 强调颜色
  • pygame 显示

运行本课

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