343. 垂直 vram 时序
在 PPU 的时序点上应用垂直的 v 递增和垂直的 t 到 v 复制。
第 343 / 356 · tests/chapter_13_scrolling/test_343_vertical_vram_timing.py
要更新的文件
emulator/ppu/ppu.py参考资料
https://www.nesdev.org/wiki/PPU_scrolling#At_dot_256_of_each_scanline为什么需要这一步
第 341 步和第 342 步实现了纯函数式的垂直地址机制。本步骤将它们接入 PPU 时序。
当渲染启用时
visible and pre-render scanlines, dot 256:
increment fine/coarse Y in v
pre-render scanline, every dot 280-304:
copy fine Y, vertical nametable, and coarse Y from t into v简化时序
visible scanline:
... 255 256 257 ...
| |
| +-- horizontal reload
+------ vertical increment
pre-render scanline 261:
... 256 257 ... 280----------------304 ...
| | |
| | +-- vertical reload active at every dot
| +---------- horizontal reload
+-------------- vertical increment为什么在点 280-304 的每个点都重复垂直重载?如果 CPU 写入在该区间内改变了 t,后面的点就可以复制到较新的垂直状态。把该区间当作点 280 处的一次操作,会丢失这种行为。
当 PPUMASK 的背景位或精灵位任一置位时,渲染即处于启用状态。后渲染扫描线和 VBlank 扫描线不会执行这些自动更新。
点 256 处的重要顺序
水平获取递增先运行,垂直递增后运行。它们各自负责不同的字段,因此两处更改都必须体现在最终的 v 中。
超出范围
- 逐扫描线的有效视口记录
- 两图块预取补偿
- 帧缓冲行合成
- 不透明度掩码行合成
完整示例实现
# emulator/ppu/ppu.py
class PPU:
...
# --- NEW BLOCK: APPLY VERTICAL ADDRESS TIMING ---
def _step_vertical_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
if self.cycle == 256:
self.vram_addr = increment_vertical_vram_addr(
self.vram_addr
)
vertical_reload = (
self.scanline == PPU_PRE_RENDER_SCANLINE
and 280 <= self.cycle <= 304
)
if vertical_reload:
self.vram_addr = copy_vertical_scroll_bits(
self.vram_addr,
self.temp_vram_addr,
)
def step(self, cycles: int = 1) -> None:
...
for _ in range(cycles):
self.cycle += 1
self._step_horizontal_rendering_address()
# --- NEW LINE: APPLY VERTICAL TIMING AT THE CURRENT DOT ---
self._step_vertical_rendering_address()
...运行本课
uv run pytest tests/chapter_13_scrolling/test_343_vertical_vram_timing.py -v