340. Таймирование vram по горизонтали
Применить горизонтальные инкременты v и горизонтальное копирование из t в v в точках таймирования PPU.
Урок 340 из 356 · tests/chapter_13_scrolling/test_340_horizontal_vram_timing.py
Файл для обновления
emulator/ppu/ppu.pyСправочные материалы
https://www.nesdev.org/wiki/PPU_scrolling#During_rendering
https://www.nesdev.org/wiki/PPU_rendering#Line-by-line_timingЗачем нужен этот шаг
Шаги 338 и 339 реализовали чистые механизмы горизонтальных адресов. Этот шаг привязывает их ко времени PPU.
На видимых и предварительных строках развёртки при включённой отрисовке
dots 8, 16, 24, ... 256:
increment horizontal v after each fetched tile
dot 257:
copy coarse X and horizontal nametable from t into v
dots 328 and 336:
increment horizontal v while prefetching the first tiles for the next scanlineОтрисовка считается включённой, когда в PPUMASK включена отрисовка либо фона, либо спрайтов. Адресное таймирование неактивно в строках развёртки пост-рендера и VBlank.
Упрощённая временная шкала
1---------256 257 --------320 321------336 ----340
tile fetches reload X prefetch tiles
^ every 8 ^ 328 and 336Важный порядок операций
PPU.step() сначала продвигает self.cycle. Полученное значение рассматривается как текущая точка, и лишь затем применяется горизонтальное адресное таймирование. Поэтому PPU, начинающий с cycle 256, выполняет горизонтальную перезагрузку после одного шага в точке 257.
Распространённое заблуждение
Горизонтальные инкременты не ограничены видимыми пикселями. Точки 328 и 336 подготавливают первые два тайла фона для следующей строки развёртки и также инкрементируют v.
Вне объёма работ
- вертикальный инкремент в точке 256
- вертикальное копирование из t в v во время предварительной строки развёртки
- запись области просмотра по строкам развёртки
- изменения фреймбуфера и маски непрозрачности
Полный пример реализации
# emulator/ppu/ppu.py
class PPU:
...
# --- NEW BLOCK: APPLY HORIZONTAL ADDRESS TIMING ---
def _step_horizontal_rendering_address(self) -> None:
rendering_enabled = self.mask & (
MASK_SHOW_BACKGROUND | MASK_SHOW_SPRITES
)
if not rendering_enabled:
return
rendering_scanline = (
0 <= self.scanline < 240
or self.scanline == PPU_PRE_RENDER_SCANLINE
)
if not rendering_scanline:
return
visible_fetch_increment = (
1 <= self.cycle <= 256
and self.cycle % 8 == 0
)
prefetch_increment = (
321 <= self.cycle <= 336
and self.cycle % 8 == 0
)
if visible_fetch_increment or prefetch_increment:
self.vram_addr = increment_horizontal_vram_addr(
self.vram_addr
)
if self.cycle == 257:
self.vram_addr = copy_horizontal_scroll_bits(
self.vram_addr,
self.temp_vram_addr,
)
def step(self, cycles: int = 1) -> None:
...
for _ in range(cycles):
self.cycle += 1
# --- NEW LINE: APPLY TIMING AT THE CURRENT DOT ---
self._step_horizontal_rendering_address()
...Запустить этот урок
uv run pytest tests/chapter_13_scrolling/test_340_horizontal_vram_timing.py -v