348. 扫描线视口 x
从一条被记录的扫描线状态解码水平视口X。
第 348 / 356 · tests/chapter_13_scrolling/test_348_scanline_viewport_x.py
要更新的文件
emulator/rendering/ppu_background_renderer.py参考资料
https://www.nesdev.org/wiki/PPU_scrolling为什么需要这一步
第 347 步选择了构成水平源对的两个逻辑命名表。后续的行合成器还需要知道这条扫描线在那个 512 像素宽的对内部开始的确切像素位置。
被记录的地址使用与 t 相同的打包滚动布局
yyy NN YYYYY XXXXX水平视口X使用
XXXXX coarse X tile column
low N bit horizontal nametable selection, vram_addr bit 10
state.fine_x pixel offset inside the first tile像素换算
viewport_x = nametable_x * 256 + coarse_x * 8 + fine_x示例
nametable X = 1
coarse X = 5
fine X = 3
viewport X = 1 * 256 + 5 * 8 + 3 = 299为什么要复用 decode_background_viewport_position?现有的纯函数解码器已经拥有打包滚动字段的换算逻辑。t 和被记录的 v 具有相同的位布局,因此在这个渲染器里重复这些掩码和尺寸,会产生两个可能各自漂移的实现。
重要不变量
- 水平命名表的第 10 位贡献 256 像素
- 粗X每个图块贡献八个像素
- 细X贡献最后的 0-7 像素偏移
- 垂直命名表、粗Y和细Y不影响视口X
- 结果保持在逻辑水平范围 0-511 之内
- 被记录的地址不会再次被回退
- 该辅助函数不执行任何渲染或内存访问
常见误解
被记录的地址不需要再一次两图块校正。第 345 步在构造 BackgroundScanlineState 之前,已经把 v 的一个副本回退了两次。在这里重复回退会错误地让每个渲染行向左移动十六个像素。
超出范围
- 选择逻辑命名表地址
- 合成帧缓冲行
- 对目标像素应用水平回绕
- 不透明度掩码合成
- 在定时数据与旧的帧级备用路径之间做选择
完整示例实现
# emulator/rendering/ppu_background_renderer.py
# --- NEW BLOCK: DECODE ONE SCANLINE'S HORIZONTAL VIEWPORT ---
def _scanline_viewport_x(state: BackgroundScanlineState) -> int:
viewport_x, _ = decode_background_viewport_position(
temp_vram_addr=state.vram_addr,
fine_x=state.fine_x,
)
return viewport_x运行本课
uv run pytest tests/chapter_13_scrolling/test_348_scanline_viewport_x.py -v