335. 将 PPU 背景视口转换为不透明度掩码

根据当前 PPU 滚动状态组合水平不透明度掩码视口。

335 / 356 · tests/chapter_13_scrolling/test_335_ppu_background_viewport_to_opaque_mask.py

待更新文件

emulator/rendering/ppu_background_renderer.py

参考

https://www.nesdev.org/wiki/PPU_scrolling
https://www.nesdev.org/wiki/PPU_nametables

为什么这看起来与步骤 334 相似

不透明度掩码适配器有意复制了帧缓冲区适配器的寻址结构:

decode the same viewport X
select the same horizontal logical pair
process the same left and right bases
compose using the same viewport X

重要的区别在于,每个产生数据的操作都必须使用不透明度掩码路径:

ppu_background_to_opaque_mask()
compose_horizontal_opaque_mask_viewport()

它绝不能意外调用

ppu_background_to_framebuffer()
compose_horizontal_framebuffer_viewport()

这种有意的复制使两条路径都易于阅读。它们的对等测试可以防止这段小型重复的寻址机制发生偏差。

生成的掩码之后将被精灵优先级和 sprite-zero-hit 检测共用。此步骤只负责构建它,并不集成任何一个消费方。

范围之外

  • 帧缓冲区组合
  • 与 Console 集成
  • 与 sprite-zero-hit 集成
  • 垂直像素滚动
  • pygame

完整示例实现

# emulator/rendering/ppu_background_renderer.py

# --- NEW BLOCK: COPY FRAMEBUFFER ADDRESSING FOR THE OPACITY-MASK PATH ---
def ppu_background_viewport_to_opaque_mask(
    ppu: PPU,
) -> BackgroundOpaqueMask:
    viewport_x, _ = decode_background_viewport_position(
        temp_vram_addr=ppu.temp_vram_addr,
        fine_x=ppu.fine_x,
    )

    nametable_y = (ppu.temp_vram_addr >> 11) & 1
    left_base = BASE_NAMETABLE_ADDR + nametable_y * 0x0800
    right_base = left_base + 0x0400

    # Same addresses, but call the opacity-mask producer.
    left = ppu_background_to_opaque_mask(
        ppu,
        base_nametable_addr=left_base,
    )
    right = ppu_background_to_opaque_mask(
        ppu,
        base_nametable_addr=right_base,
    )

    # Use the opacity-mask compositor, not the framebuffer compositor.
    return compose_horizontal_opaque_mask_viewport(
        left=left,
        right=right,
        viewport_x=viewport_x,
    )

运行本课

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