285. Manual framebuffer display helpers

Add manual framebuffer display helpers using pygame Surface drawing.

Lesson 285 of 356 · tests/chapter_05_rendering_pipeline/test_285_manual_framebuffer_display_helpers.py

File to create

tools/show_framebuffer.py

Why this step exists

The emulator core now produces pure Framebuffer data. Before building a full frontend, we want a small manual tool that can draw a Framebuffer with pygame for visual smoke checks.

Important boundary

This file lives under tools/ because pygame is a frontend/manual-display concern.

Core emulator modules must not import pygame

emulator/rendering/framebuffer.py
emulator/rendering/nametable_renderer.py
emulator/rendering/ppu_background_renderer.py
emulator/console.py

What is a pygame Surface? A Surface is a drawable pixel buffer managed by pygame. A window created by pygame.display.set_mode(...) is also a Surface.

Minimal example

surface.fill((255, 0, 0), pygame.Rect(0, 0, 10, 10))

This fills a 10x10 rectangle with red.

How draw_framebuffer works:

Framebuffer pixel (x, y)
    -> RGB color
    -> pygame Rect(x * scale, y * scale, scale, scale)
    -> surface.fill(color, rect)

Suggested implementation example

import pygame

from emulator.rendering.framebuffer import Framebuffer


def make_checkerboard_framebuffer(width: int = 64, height: int = 64) -> Framebuffer:
    framebuffer = Framebuffer(width=width, height=height)

    for y in range(height):
        for x in range(width):
            block_x = x // 8
            block_y = y // 8

            if (block_x + block_y) % 2 == 0:
                framebuffer.set_pixel(x, y, (255, 255, 255))
            else:
                framebuffer.set_pixel(x, y, (40, 40, 40))

    return framebuffer


def draw_framebuffer(
    surface: pygame.Surface,
    framebuffer: Framebuffer,
    scale: int,
) -> None:
    for y in range(framebuffer.height):
        for x in range(framebuffer.width):
            color = framebuffer.get_pixel(x, y)

            rect = pygame.Rect(
                x * scale,
                y * scale,
                scale,
                scale,
            )

            surface.fill(color, rect)

Testing policy

This test does not open a real pygame window. It uses an off-screen pygame Surface to verify the drawing helper. The window/main-loop step is separate.

Out of scope

  • pygame main loop
  • pygame.display.set_mode
  • event handling
  • displaying ROM output
  • controller input
  • sprites

Run this lesson

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