286. 手动 pygame 主循环

为帧缓冲冒烟运行器添加手动 pygame 主循环。

286 / 356 · tests/chapter_05_rendering_pipeline/test_286_manual_pygame_main_loop.py

待更新/创建的文件

tools/show_framebuffer.py

为什么需要这一步

上一步添加了可以把 Framebuffer 绘制到 pygame Surface 上的辅助函数。这一步添加了手动窗口循环,使开发者可以用肉眼确认这些纯 Framebuffer 数据可以被显示出来。

重要提示

这是一个手动的冒烟运行器,不是自动化渲染测试。自动化测试不得打开真正的 pygame 窗口。

以 Python 模块的方式运行这个工具

uv run python -m tools.show_framebuffer

Python 的 -m 参数期望的是模块名,而不是文件路径。模块名使用点号:

tools.show_framebuffer

而不是用文件路径的方式:

tools/show_framebuffer.py

现代 Python 将这种项目布局支持为命名空间包,因此本课不需要 __init__.py 文件。

手动命令

Correct:
    uv run python -m tools.show_framebuffer

Incorrect:
    uv run python -m tools/show_framebuffer.py
    uv run python -m tools.show_framebuffer.py

什么是 pygame 主循环?

pygame 主循环通过反复执行以下操作使窗口保持存活

1. reading events
2. drawing to the window surface
3. presenting the drawn image

最小形态

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    draw_framebuffer(window, framebuffer, SCALE)
    pygame.display.flip()

什么是 pygame.display.flip()?它把窗口 surface 的当前内容呈现到显示器上。简单来说:

draw pixels -> flip -> user sees pixels

预期的手动视觉结果

棋盘格帧缓冲应该显示出类似这样的黑/白或深色/白色图案:

+----------------+
| ██  ██  ██  ██ |
| ██  ██  ██  ██ |
|   ██  ██  ██   |
|   ██  ██  ██   |
| ██  ██  ██  ██ |
| ██  ██  ██  ██ |
+----------------+

确切的方块大小取决于帧缓冲尺寸和 SCALE,但重要的视觉检查点是:

alternating light and dark squares are visible
closing the window exits cleanly

建议的实现示例

SCALE = 3


def main() -> None:
    framebuffer = make_checkerboard_framebuffer()

    pygame.init()
    try:
        window = pygame.display.set_mode(
            (framebuffer.width * SCALE, framebuffer.height * SCALE)
        )
        pygame.display.set_caption("Framebuffer Smoke Test")

        running = True
        while running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False

            draw_framebuffer(window, framebuffer, SCALE)
            pygame.display.flip()
    finally:
        pygame.quit()


if __name__ == "__main__":
    main()

架构规则

pygame 仍然位于模拟器核心之外。这个文件之所以可以导入 pygame,是因为它位于 tools/ 目录下。不要在 emulator/rendering、emulator/ppu 或 emulator/console 中导入 pygame。

范围之外

  • 在 pytest 中测试真实的 pygame 窗口
  • 加载 ROM
  • 渲染实时的 Console 输出
  • 手柄输入
  • 精灵

运行本课

uv run pytest tests/chapter_05_rendering_pipeline/test_286_manual_pygame_main_loop.py -v