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