329. Декодирование позиции области просмотра фона

Декодировать упрощённую позицию области просмотра фона из состояния прокрутки PPU.

Урок 329 из 356 · tests/chapter_13_scrolling/test_329_decode_background_viewport_position.py

Файл для создания

emulator/rendering/background_viewport.py

Справочная документация

https://www.nesdev.org/wiki/PPU_scrolling
https://www.nesdev.org/wiki/PPU_scrolling#PPU_internal_registers
https://www.nesdev.org/wiki/PPU_scrolling#During_rendering

Зачем нужен этот шаг

PPU хранит прокрутку в виде упакованных аппаратных полей, а не готовых к использованию пиксельных координат. Для рендеринга нужна простая позиция верхнего левого угла области просмотра, поэтому этот шаг декодирует:

temp_vram_addr (t): yyy NN YYYYY XXXXX
fine_x (x):         xxx

Где

XXXXX -> coarse X tile position, 5 bits
YYYYY -> coarse Y tile position, 5 bits
NN    -> logical nametable X/Y selection
yyy   -> fine Y pixel position, 3 bits
xxx   -> fine X pixel position, 3 bits stored separately

Преобразование в пиксели

viewport X = nametable X * 256 + coarse X * 8 + fine X
viewport Y = nametable Y * 240 + coarse Y * 8 + fine Y

Предлагаемая реализация

# emulator/rendering/background_viewport.py

NAMETABLE_PIXEL_WIDTH = 256
NAMETABLE_PIXEL_HEIGHT = 240
TILE_PIXEL_SIZE = 8

BackgroundViewportPosition = tuple[int, int]


def decode_background_viewport_position(
    temp_vram_addr: int,
    fine_x: int,
) -> BackgroundViewportPosition:
    coarse_x = temp_vram_addr & 0b1_1111
    coarse_y = (temp_vram_addr >> 5) & 0b1_1111

    nametable_x = (temp_vram_addr >> 10) & 1
    nametable_y = (temp_vram_addr >> 11) & 1

    fine_y = (temp_vram_addr >> 12) & 0b111

    viewport_x = (
        nametable_x * NAMETABLE_PIXEL_WIDTH
        + coarse_x * TILE_PIXEL_SIZE
        + fine_x
    )

    viewport_y = (
        nametable_y * NAMETABLE_PIXEL_HEIGHT
        + coarse_y * TILE_PIXEL_SIZE
        + fine_y
    )

    return viewport_x, viewport_y

Почему использовать t и x вместо старого поля scroll? $2005 получает горизонтальную и вертикальную записи, тогда как поле scroll, используемое для совместимости, хранит только последний байт. Аппаратные поля t и x сохраняют полное упакованное состояние.

Граница точности

Реальное железо рендерит из текущего адреса v плюс тонкий X после синхронизированных по времени переносов из t в v. Этот туториал сейчас использует t плюс тонкий X как снимок на уровне кадра. Точные переносы на такте 257 и на пре-рендере на тактах 280-304 остаются будущей работой над таймингом.

Ещё одно упрощение

Реальные значения грубого Y 30 и 31 имеют особое поведение обёртывания. Этот шаг выполняет только прямое декодирование поля в пиксели; обёртывание области просмотра появится позже.

Вне рамок

  • чтение байтов nametable
  • создание фреймбуфера
  • пересечение границы nametable
  • точные переносы тайминга t -> v
  • обёртывание грубого Y 30/31
  • фикстуры коммерческих ROM

Запустить этот урок

uv run pytest tests/chapter_13_scrolling/test_329_decode_background_viewport_position.py -v