276. 名称表转帧缓冲
将简化的名称表背景渲染为帧缓冲数据。
第 276 / 356 · tests/chapter_05_rendering_pipeline/test_276_nametable_to_framebuffer.py
参考
https://www.nesdev.org/wiki/PPU_nametables待创建的文件
emulator/rendering/nametable_renderer.py为什么需要这一步
图案表包含图块图形,但并不描述背景布局,而名称表提供了这种布局。
什么是名称表?名称表是 PPU 内存的一部分,用来存储屏幕上每个图块单元应该显示哪个背景图块。
最小示例
nametable[0] = 5这意味着
top-left 8x8 background cell uses pattern table tile #5重要的区别
pattern table = tile graphics dictionary
nametable = tile layout/map
palette = colors used by tile pixels本步骤所使用的简化渲染模型
nametable tile ID
-> decoded_tiles[tile_id]
-> tile pixel color index 0-3
-> same shared 4-color palette[color_index]
-> framebuffer pixel真实的 NES 名称表内存
960 bytes tile IDs
64 bytes attribute table本步骤有意只使用 960 个可见图块字节,并对所有图块使用同一个共享的 4 色调色板。属性表调色板选择是之后的步骤。
重要的简化
这个初版名称表渲染器只是一个基础起点,每个图块都使用相同的 4 色调色板。后面的测试会添加一个支持属性表的渲染器,让屏幕的不同区域可以选择不同的背景调色板。
根据名称表几何结构得到屏幕尺寸
32 tiles across * 8 pixels = 256 pixels
30 tiles down * 8 pixels = 240 pixels建议的实现示例
from emulator.ppu.chr_decoder import (
CHR_TILE_HEIGHT,
CHR_TILE_WIDTH,
decode_pattern_table,
)
from emulator.rendering.framebuffer import Framebuffer, RGBColor
NAMETABLE_ROWS = 30
NAMETABLE_TILES_PER_ROW = 32
NAMETABLE_SIZE = NAMETABLE_TILES_PER_ROW * NAMETABLE_ROWS
BACKGROUND_WIDTH = NAMETABLE_TILES_PER_ROW * CHR_TILE_WIDTH
BACKGROUND_HEIGHT = NAMETABLE_ROWS * CHR_TILE_HEIGHT
def nametable_to_framebuffer(
nametable_bytes: bytes,
pattern_table_bytes: bytes,
palette: list[RGBColor],
) -> Framebuffer:
if len(nametable_bytes) != NAMETABLE_SIZE:
raise ValueError("Nametable visible tile area must be 960 bytes")
decoded_tiles = decode_pattern_table(pattern_table_bytes)
framebuffer = Framebuffer(width=BACKGROUND_WIDTH, height=BACKGROUND_HEIGHT)
for tile_y in range(NAMETABLE_ROWS):
for tile_x in range(NAMETABLE_TILES_PER_ROW):
nametable_index = tile_y * NAMETABLE_TILES_PER_ROW + tile_x
tile_id = nametable_bytes[nametable_index]
tile = decoded_tiles[tile_id]
for row in range(CHR_TILE_HEIGHT):
for col in range(CHR_TILE_WIDTH):
# Basic starting point: every tile uses the same
# 4-color palette. A later renderer will use the
# attribute table to select different palettes.
color_index = tile[row][col]
rgb = palette[color_index]
pixel_x = tile_x * CHR_TILE_WIDTH + col
pixel_y = tile_y * CHR_TILE_HEIGHT + row
framebuffer.set_pixel(pixel_x, pixel_y, rgb)
return framebuffer架构规则
不要在这里重复实现 CHR 解码逻辑,请复用 decode_pattern_table()。
本步骤不涉及
- 属性表解码
- 逐图块调色板选择
- 滚动
- 名称表镜像
- 直接从 PPU 总线读取
- 精灵
- OAMDMA
- pygame 显示
运行本课
uv run pytest tests/chapter_05_rendering_pipeline/test_276_nametable_to_framebuffer.py -v