278. Выбор палитры по таблице атрибутов
Декодирование выбора фоновой палитры из таблицы атрибутов таблицы имён.
Урок 278 из 356 · tests/chapter_05_rendering_pipeline/test_278_attribute_table_palette_selection.py
Справочная информация
https://www.nesdev.org/wiki/PPU_attribute_tablesФайл для создания
emulator/rendering/attribute_table.pyПочему этот шаг важен
Текущий рендерер таблицы имён использует одну общую 4-цветную палитру для каждого тайла. Настоящие фоны NES выбирают между четырьмя фоновыми субпалитрами с помощью таблицы атрибутов таблицы имён.
Этот шаг пока не выполняет рендеринг с атрибутами. Он отвечает лишь на один небольшой вопрос:
For tile coordinate (tile_x, tile_y), which palette ID does the attribute
table select?Что такое таблица атрибутов?
У каждой таблицы имён есть
960 bytes tile IDs
64 bytes attribute tableТаблица атрибутов — это сетка байтов размером 8x8. Каждый байт атрибута покрывает область 4x4 тайла, то есть 32x32 пикселя.
Один байт атрибута разбит на четыре квадранта размером 2x2 тайла
+-----------------------+
| top-left | top-right |
| 2x2 | 2x2 |
+----------+------------+
| bottom-l | bottom-r |
| 2x2 | 2x2 |
+-----------------------+Каждый квадрант хранит 2-битный идентификатор палитры
0b00 -> palette 0
0b01 -> palette 1
0b10 -> palette 2
0b11 -> palette 3Важное различие
Таблица атрибутов не хранит RGB-цвета и не хранит индексы цвета NES. Она выбирает, какую фоновую субпалитру использовать. Позже палитровая RAM и RGB-палитра NES превратят этот выбор в реальные цвета.
Расположение битов внутри одного байта атрибута
bits 0-1 -> top-left quadrant
bits 2-3 -> top-right quadrant
bits 4-5 -> bottom-left quadrant
bits 6-7 -> bottom-right quadrantЭтот шаг реализует: распаковку/чтение одного квадранта из байта.
Пример читаемой реализации
TABLE_SIZE = 64
BYTES_PER_ROW = 8
def get_attribute_palette_id(
attribute_table: bytes,
tile_x: int,
tile_y: int,
) -> int:
if len(attribute_table) != TABLE_SIZE:
raise ValueError("Attribute table must be 64 bytes")
attribute_x = tile_x // 4
attribute_y = tile_y // 4
attribute_index = attribute_y * BYTES_PER_ROW + attribute_x
attribute_byte = attribute_table[attribute_index]
quadrant_x = (tile_x % 4) // 2
quadrant_y = (tile_y % 4) // 2
is_top_left = quadrant_x == 0 and quadrant_y == 0
is_top_right = quadrant_x == 1 and quadrant_y == 0
is_bottom_left = quadrant_x == 0 and quadrant_y == 1
if is_top_left:
return attribute_byte & 0b11
if is_top_right:
return (attribute_byte >> 2) & 0b11
if is_bottom_left:
return (attribute_byte >> 4) & 0b11
return (attribute_byte >> 6) & 0b11Почему tile_x // 4 и tile_y // 4? Потому что каждый байт атрибута покрывает область 4x4 тайла.
Почему (tile_x % 4) // 2?
Внутри этой области 4x4
tile positions 0,1 belong to quadrant 0
tile positions 2,3 belong to quadrant 1Таким образом
0 % 4 // 2 -> 0
1 % 4 // 2 -> 0
2 % 4 // 2 -> 1
3 % 4 // 2 -> 1Вне рамок этого шага
- рендеринг таблиц имён с атрибутами
- поиск в палитровой RAM PPU
- преобразование в RGB-палитру
- скроллинг
- спрайты
- отображение через pygame
Запустить этот урок
uv run pytest tests/chapter_05_rendering_pipeline/test_278_attribute_table_palette_selection.py -v