345. 记录可见扫描线滚动状态
在每条可见扫描线的点 1 记录有效视口状态。
第 345 / 356 · tests/chapter_13_scrolling/test_345_record_visible_scanline_scroll_state.py
要更新的文件
emulator/ppu/ppu.py参考资料
https://www.nesdev.org/wiki/PPU_rendering#Cycles_1-256
https://www.nesdev.org/wiki/PPU_scrolling#Details为什么需要这一步
现在 PPU 会按照水平和垂直渲染时序更新 v,但现有的高层渲染器是在整帧结束之后运行的。它需要一份简短的记录,记下代表每个可见屏幕行的有效地址。
一个不可变状态存储
vram_addr:
the copied address represented by the prefetched pixels
fine_x:
the separate 0-7 pixel offset inside the first tile在点 1,真实的 v 超前两个图块列,因为点 321-336 已为当前扫描线预取了前两个图块。因此记录操作会执行:
copied visible v = real v rewound once, then rewound again示例
current scanline = 20
real v coarse X = 7
copied visible coarse X:
7 -> 6 -> 5
stored destination:
current_scanline_scroll_states[20]回退改变的是水平图块位置,而不是扫描线索引。该状态属于当前扫描线 20,而不是扫描线 18。
记录条件
- 背景或精灵渲染已启用
- 扫描线可见:0-239
- 当前点为 1
重要不变量
- 缓冲区始终恰好有 240 个条目
- 每个 PPU 拥有独立的缓冲区
- 记录不会修改真实的 PPU.vram_addr
- 细X单独存储
- 后渲染、VBlank 和预渲染不会被记录
超出范围
- 归档已完成的帧
- 替换缺失的条目
- 重置当前帧缓冲区
- 帧缓冲或不透明度掩码合成
完整示例实现
# emulator/ppu/ppu.py
# --- NEW BLOCK: EFFECTIVE STATE FOR ONE VISIBLE SCANLINE ---
@dataclass(frozen=True)
class BackgroundScanlineState:
vram_addr: int
fine_x: int
@dataclass
class PPU:
...
# --- NEW LINE: CURRENT FRAME'S VISIBLE SCANLINE STATES ---
current_scanline_scroll_states: list[
BackgroundScanlineState | None
] = field(default_factory=lambda: [None] * 240)
...
# --- NEW BLOCK: RECORD THE CURRENT VISIBLE SCANLINE ---
def _record_visible_scanline_scroll_state(self) -> None:
rendering_enabled = self.mask & (
MASK_SHOW_BACKGROUND | MASK_SHOW_SPRITES
)
if not rendering_enabled:
return
if not 0 <= self.scanline < 240:
return
if self.cycle != 1:
return
visible_vram_addr = decrement_horizontal_vram_addr(
decrement_horizontal_vram_addr(self.vram_addr)
)
self.current_scanline_scroll_states[self.scanline] = (
BackgroundScanlineState(
vram_addr=visible_vram_addr,
fine_x=self.fine_x,
)
)
def step(self, cycles: int = 1) -> None:
...
for _ in range(cycles):
self.cycle += 1
self._step_horizontal_rendering_address()
self._step_vertical_rendering_address()
# --- NEW LINE: RECORD THE CURRENT SCANLINE AT DOT 1 ---
self._record_visible_scanline_scroll_state()
...运行本课
uv run pytest tests/chapter_13_scrolling/test_345_record_visible_scanline_scroll_state.py -v