334. Ppu background viewport to framebuffer

Compose the horizontal background framebuffer from current PPU scroll state.

Lesson 334 of 356 · tests/chapter_13_scrolling/test_334_ppu_background_viewport_to_framebuffer.py

File to update

emulator/rendering/ppu_background_renderer.py

References

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

Why this step exists

Previous steps can render one selected logical nametable and can compose two existing framebuffers. This adapter connects those mechanisms to current PPU scroll state.

Horizontal flow

temp_vram_addr + fine_x
          |
          v
    decode viewport X
          |
          v
select horizontal logical pair
          |
          v
render left and right nametables
          |
          v
compose one 256x240 framebuffer

Logical pair selection

nametable Y = 0 -> left $2000, right $2400
nametable Y = 1 -> left $2800, right $2C00

Nametable X remains part of decoded viewport X. For example, nametable X=1, coarse X=5, and fine X=3 produce viewport X 299.

Compatibility

The existing ppu_background_to_framebuffer() function continues rendering one selected nametable. This new adapter calls it twice and then composes the results.

Out of scope

  • opacity-mask composition
  • Console integration
  • sprite-zero-hit integration
  • vertical pixel scrolling
  • pygame

Complete example implementation

# emulator/rendering/ppu_background_renderer.py

# --- NEW LINES: HORIZONTAL VIEWPORT OPERATIONS ---
from emulator.rendering.background_viewport import (
    compose_horizontal_framebuffer_viewport,
    compose_horizontal_opaque_mask_viewport,
    decode_background_viewport_position,
)

...

# --- NEW BLOCK: COMPOSE THE HORIZONTAL FRAMEBUFFER VIEWPORT ---
def ppu_background_viewport_to_framebuffer(ppu: PPU) -> Framebuffer:
    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

    left = ppu_background_to_framebuffer(
        ppu,
        base_nametable_addr=left_base,
    )
    right = ppu_background_to_framebuffer(
        ppu,
        base_nametable_addr=right_base,
    )

    return compose_horizontal_framebuffer_viewport(
        left=left,
        right=right,
        viewport_x=viewport_x,
    )

Run this lesson

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