330. Componer el viewport horizontal del framebuffer

Componer un viewport horizontal de 256x240 a partir de dos framebuffers de nametables adyacentes.

Lección 330 de 356 · tests/chapter_13_scrolling/test_330_compose_horizontal_framebuffer_viewport.py

Archivo a actualizar

emulator/rendering/background_viewport.py

Documentación de referencia

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

Por qué existe este paso

Cada nametable renderizado tiene 256 píxeles de ancho, pero el scrolling horizontal puede hacer que una pantalla contenga píxeles de dos nametables adyacentes. Este paso los trata como un fondo lógico de 512x240 y selecciona de él un viewport de 256x240.

Modelo de coordenadas

left framebuffer:  logical X   0-255
right framebuffer: logical X 256-511

Para cada X de pantalla de destino

logical X = (viewport X + screen X) modulo 512

La operación módulo hace que el borde derecho del par lógico vuelva a su borde izquierdo.

Ejemplo de costura

viewport X = 200

screen X   0-55  <- left X 200-255
screen X  56-255 <- right X   0-199

Invariantes

  • ambos framebuffers de origen son exactamente de 256x240
  • el framebuffer devuelto es exactamente de 256x240
  • ninguno de los framebuffers de origen se muta
  • cada píxel de salida proviene de la misma fila Y en un framebuffer de origen

Concepto erróneo común

Esta función no implementa el mirroring de nametables del cartucho. PpuBus es el propietario del mirroring de direcciones; esta función solo compone vecinos lógicos ya renderizados.

Fuera del alcance

  • scrolling vertical
  • composición de la máscara de opacidad del fondo
  • lecturas del PPU o de PpuBus
  • renderizado de tiles del nametable
  • integración en Console
  • pygame
  • transferencias exactas de v/t temporizadas por dots

Implementación de ejemplo completa

# emulator/rendering/background_viewport.py

from emulator.rendering.framebuffer import Framebuffer


def compose_horizontal_framebuffer_viewport(
    left: Framebuffer,
    right: Framebuffer,
    viewport_x: int,
) -> Framebuffer:
    expected_size = (
        NAMETABLE_PIXEL_WIDTH,
        NAMETABLE_PIXEL_HEIGHT,
    )

    if (left.width, left.height) != expected_size:
        raise ValueError("Left nametable framebuffer must be 256x240")

    if (right.width, right.height) != expected_size:
        raise ValueError("Right nametable framebuffer must be 256x240")

    logical_width = NAMETABLE_PIXEL_WIDTH * 2
    result = Framebuffer(
        width=NAMETABLE_PIXEL_WIDTH,
        height=NAMETABLE_PIXEL_HEIGHT,
    )

    for screen_y in range(NAMETABLE_PIXEL_HEIGHT):
        for screen_x in range(NAMETABLE_PIXEL_WIDTH):
            logical_x = (viewport_x + screen_x) % logical_width

            if logical_x < NAMETABLE_PIXEL_WIDTH:
                source = left
                source_x = logical_x
            else:
                source = right
                source_x = logical_x - NAMETABLE_PIXEL_WIDTH

            color = source.get_pixel(source_x, screen_y)
            result.set_pixel(screen_x, screen_y, color)

    return result

Ejecutar esta lección

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