276. Nametable a framebuffer
Renderizar un fondo de nametable simplificado en datos de framebuffer.
Lección 276 de 356 · tests/chapter_05_rendering_pipeline/test_276_nametable_to_framebuffer.py
Referencia
https://www.nesdev.org/wiki/PPU_nametablesArchivo a crear
emulator/rendering/nametable_renderer.pyPor qué existe este paso
Las tablas de patrones contienen gráficos de tiles, pero no describen la disposición del fondo. Los nametables proporcionan esa disposición.
¿Qué es un nametable? Un nametable es memoria de la PPU que almacena qué tile de fondo aparece en cada celda de tile de la pantalla.
Ejemplo mínimo
nametable[0] = 5Esto significa
top-left 8x8 background cell uses pattern table tile #5Distinción importante
pattern table = tile graphics dictionary
nametable = tile layout/map
palette = colors used by tile pixelsModelo de renderizado simplificado para este paso
nametable tile ID
-> decoded_tiles[tile_id]
-> tile pixel color index 0-3
-> same shared 4-color palette[color_index]
-> framebuffer pixelMemoria real de nametable de la NES
960 bytes tile IDs
64 bytes attribute tableEste paso usa intencionadamente solo los 960 bytes de tiles visibles y una única paleta compartida de 4 colores para todos los tiles. La selección de paleta mediante tabla de atributos es un paso posterior.
Simplificación importante
Este primer renderizador de nametable es un punto de partida básico. Cada tile usa la misma paleta de 4 colores. Tests posteriores añaden un renderizador consciente de atributos donde diferentes regiones de la pantalla pueden elegir distintas paletas de fondo.
Tamaño de pantalla a partir de la geometría del nametable
32 tiles across * 8 pixels = 256 pixels
30 tiles down * 8 pixels = 240 pixelsEjemplo de implementación sugerida
from emulator.ppu.chr_decoder import (
CHR_TILE_HEIGHT,
CHR_TILE_WIDTH,
decode_pattern_table,
)
from emulator.rendering.framebuffer import Framebuffer, RGBColor
NAMETABLE_ROWS = 30
NAMETABLE_TILES_PER_ROW = 32
NAMETABLE_SIZE = NAMETABLE_TILES_PER_ROW * NAMETABLE_ROWS
BACKGROUND_WIDTH = NAMETABLE_TILES_PER_ROW * CHR_TILE_WIDTH
BACKGROUND_HEIGHT = NAMETABLE_ROWS * CHR_TILE_HEIGHT
def nametable_to_framebuffer(
nametable_bytes: bytes,
pattern_table_bytes: bytes,
palette: list[RGBColor],
) -> Framebuffer:
if len(nametable_bytes) != NAMETABLE_SIZE:
raise ValueError("Nametable visible tile area must be 960 bytes")
decoded_tiles = decode_pattern_table(pattern_table_bytes)
framebuffer = Framebuffer(width=BACKGROUND_WIDTH, height=BACKGROUND_HEIGHT)
for tile_y in range(NAMETABLE_ROWS):
for tile_x in range(NAMETABLE_TILES_PER_ROW):
nametable_index = tile_y * NAMETABLE_TILES_PER_ROW + tile_x
tile_id = nametable_bytes[nametable_index]
tile = decoded_tiles[tile_id]
for row in range(CHR_TILE_HEIGHT):
for col in range(CHR_TILE_WIDTH):
# Basic starting point: every tile uses the same
# 4-color palette. A later renderer will use the
# attribute table to select different palettes.
color_index = tile[row][col]
rgb = palette[color_index]
pixel_x = tile_x * CHR_TILE_WIDTH + col
pixel_y = tile_y * CHR_TILE_HEIGHT + row
framebuffer.set_pixel(pixel_x, pixel_y, rgb)
return framebufferRegla de arquitectura
No dupliques aquí la lógica de decodificación CHR. Reutiliza decode_pattern_table().
Fuera de alcance
- decodificación de la tabla de atributos
- selección de paleta por tile
- desplazamiento (scrolling)
- espejado de nametable
- lectura directa desde el bus de la PPU
- sprites
- OAMDMA
- pantalla de pygame
Ejecutar esta lección
uv run pytest tests/chapter_05_rendering_pipeline/test_276_nametable_to_framebuffer.py -v