279. Nametable con atributos
Renderizar el fondo del nametable usando paletas seleccionadas por atributos.
Lección 279 de 356 · tests/chapter_05_rendering_pipeline/test_279_nametable_with_attributes.py
Referencia
https://www.nesdev.org/wiki/PPU_attribute_tablesArchivo a actualizar
emulator/rendering/nametable_renderer.pyPor qué existe este paso
El primer renderizador de nametable usaba una única paleta compartida de 4 colores para todo el fondo. Eso fue un punto de partida útil, pero los fondos reales de la NES usan la tabla de atributos para elegir entre cuatro paletas de fondo en diferentes regiones.
Este paso añade un renderizador nuevo en lugar de cambiar el antiguo
old/simple:
nametable_to_framebuffer(nametable, pattern_table, palette)
new/attribute-aware:
nametable_with_attributes_to_framebuffer(
nametable,
attribute_table,
pattern_table,
background_palettes,
)¿Qué son background_palettes? background_palettes es una lista de cuatro paletas RGB de 4 colores:
background_palettes[0] -> palette ID 0
background_palettes[1] -> palette ID 1
background_palettes[2] -> palette ID 2
background_palettes[3] -> palette ID 3Cada píxel de tile sigue produciendo solo un índice de color 0-3. La tabla de atributos selecciona qué ID de paleta usar para la región del tile:
palette_id = get_attribute_palette_id(attribute_table, tile_x, tile_y)
rgb = background_palettes[palette_id][color_index]Modelo de hardware importante
La tabla de atributos no almacena colores RGB. Selecciona un ID de paleta de fondo. Para este paso, pasamos manualmente paletas de fondo RGB ya resueltas. Más adelante, otro paso podrá construir esas paletas a partir de la RAM de paletas de la PPU y NES_PALETTE_RGB.
Ejemplo de implementación sugerida
from emulator.rendering.attribute_table import get_attribute_palette_id
BackgroundPalettes = list[list[RGBColor]]
def nametable_with_attributes_to_framebuffer(
nametable_bytes: bytes,
attribute_table: bytes,
pattern_table_bytes: bytes,
background_palettes: BackgroundPalettes,
) -> 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]
palette_id = get_attribute_palette_id(attribute_table, tile_x, tile_y)
palette = background_palettes[palette_id]
for row in range(CHR_TILE_HEIGHT):
for col in range(CHR_TILE_WIDTH):
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 compatibilidad
Mantén nametable_to_framebuffer(..., palette) sin cambios. Sigue siendo el renderizador simple de una sola paleta usado por tests anteriores.
Fuera de alcance
- búsqueda en la RAM de paletas de la PPU
- comportamiento del color de fondo universal
- desplazamiento (scrolling)
- sprites
- OAMDMA
- pantalla de pygame
Ejecutar esta lección
uv run pytest tests/chapter_05_rendering_pipeline/test_279_nametable_with_attributes.py -v