279. Таблица имён с атрибутами
Рендеринг фона таблицы имён с использованием палитр, выбранных по атрибутам.
Урок 279 из 356 · tests/chapter_05_rendering_pipeline/test_279_nametable_with_attributes.py
Справочная информация
https://www.nesdev.org/wiki/PPU_attribute_tablesФайл для обновления
emulator/rendering/nametable_renderer.pyПочему этот шаг важен
Первый рендерер таблицы имён использовал одну общую 4-цветную палитру для всего фона. Это было полезной отправной точкой, но настоящие фоны NES используют таблицу атрибутов для выбора между четырьмя фоновыми палитрами в разных областях.
Этот шаг добавляет новый рендерер вместо изменения старого
old/simple:
nametable_to_framebuffer(nametable, pattern_table, palette)
new/attribute-aware:
nametable_with_attributes_to_framebuffer(
nametable,
attribute_table,
pattern_table,
background_palettes,
)Что такое background_palettes? background_palettes — это список из четырёх 4-цветных RGB-палитр:
background_palettes[0] -> palette ID 0
background_palettes[1] -> palette ID 1
background_palettes[2] -> palette ID 2
background_palettes[3] -> palette ID 3Каждый пиксель тайла по-прежнему даёт только индекс цвета 0-3. Таблица атрибутов выбирает, какой идентификатор палитры использовать для области тайла:
palette_id = get_attribute_palette_id(attribute_table, tile_x, tile_y)
rgb = background_palettes[palette_id][color_index]Важная аппаратная модель
Таблица атрибутов не хранит RGB-цвета. Она выбирает идентификатор фоновой палитры. Для этого шага мы передаём уже разрешённые RGB-фоновые палитры вручную. Позже другой шаг сможет строить эти палитры из палитровой RAM PPU и NES_PALETTE_RGB.
Пример возможной реализации
from emulator.rendering.attribute_table import get_attribute_palette_id
BackgroundPalettes = list[list[RGBColor]]
def nametable_with_attributes_to_framebuffer(
nametable_bytes: bytes,
attribute_table: bytes,
pattern_table_bytes: bytes,
background_palettes: BackgroundPalettes,
) -> 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]
palette_id = get_attribute_palette_id(attribute_table, tile_x, tile_y)
palette = background_palettes[palette_id]
for row in range(CHR_TILE_HEIGHT):
for col in range(CHR_TILE_WIDTH):
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Правило совместимости
Оставьте nametable_to_framebuffer(..., palette) без изменений. Она остаётся простым рендерером с одной палитрой, используемым в более ранних тестах.
Вне рамок этого шага
- поиск в палитровой RAM PPU
- поведение универсального цвета фона
- скроллинг
- спрайты
- OAMDMA
- отображение через pygame
Запустить этот урок
uv run pytest tests/chapter_05_rendering_pipeline/test_279_nametable_with_attributes.py -v