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 字段才能保留完整的打包状态。

精度边界

真实硬件是在经过从 t 到 v 的定时传输之后,根据当前地址 v 加上精细 X 进行渲染的。本教程目前使用 t 加精细 X 作为帧级别的快照。精确的第 257 点以及预渲染第 280-304 点的传输仍是未来的时序工作。

另一个简化

真实的粗略 Y 值 30 和 31 具有特殊的环绕行为。本步骤只执行直接的字段到像素解码,视口环绕将在之后实现。

不在本步骤范围内

  • 读取名称表字节
  • 创建帧缓冲
  • 跨越名称表边界
  • 精确的 t -> v 时序传输
  • 粗略 Y 30/31 环绕
  • 商业 ROM 测试夹具

运行本课

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