Este proyecto es educativo y Open Source. No se copia código de otros emuladores. Implementación basada únicamente en documentación técnica y tests permitidas.
Investigación y Corrección del Renderizado de Framebuffer
Resumen
Investigación detallada del problema de renderizado donde el framebuffer contiene datos correctos (80/160 píxeles no-blancos según logs de C++) pero la pantalla se muestra completamente blanca. Se agregaron logs de diagnóstico en tres puntos críticos del pipeline de renderizado: (1) diagnóstico del framebuffer recibido en el renderizador, (2) verificación de aplicación de paleta a píxeles específicos, y (3) verificación detallada de la copia del framebuffer en Python. Estos logs permitirán identificar exactamente dónde se pierde la información de color en el pipeline de renderizado.
Concepto de Hardware
Pipeline de Renderizado: De Índices a RGB
El framebuffer de la PPU contiene índices de color (0-3), no colores RGB directos. Estos índices deben convertirse a colores RGB usando la paleta BGP (Background Palette) antes de mostrarse en pantalla.
El pipeline completo es:
- C++ (PPU): Escribe índices de color (0-3) en el framebuffer durante el renderizado de cada scanline
- Cython: Expone el framebuffer como
memoryviewpara acceso desde Python - Python (viboy.py): Copia el framebuffer a
bytearraypara protegerlo de cambios en C++ - Python (renderer.py): Convierte índices a RGB usando la paleta de debug:
- Índice 0 → (255, 255, 255) - Blanco
- Índice 1 → (170, 170, 170) - Gris Claro
- Índice 2 → (85, 85, 85) - Gris Oscuro
- Índice 3 → (8, 24, 32) - Negro
- Pygame: Dibuja los píxeles RGB en la pantalla
Problema Identificado: Discrepancia entre Framebuffer y Renderizado
Los logs del Step 0331 muestran que:
- Framebuffer (C++): 80/160 píxeles no-blancos, distribución: 0=80, 1=0, 2=0, 3=80
- Renderizado (Python/Pygame): Pantalla completamente blanca
Esto sugiere que el problema está en algún punto del pipeline entre C++ y Pygame. Las posibles causas son:
- El framebuffer se corrompe durante la copia en Python
- El renderizador no está recibiendo los datos correctos
- Hay un problema con la conversión de índices a RGB
- El renderizador está usando una paleta incorrecta o no la está aplicando
Fuente: Pan Docs - Background Palette (BGP), Pixel Rendering, Pipeline de Renderizado
Implementación
Logs de Diagnóstico Agregados
Se agregaron tres sistemas de logs de diagnóstico para rastrear el framebuffer a través del pipeline:
1. Diagnóstico del Framebuffer Recibido (renderer.py)
En renderer.py, después de recibir framebuffer_data, se agregan logs que:
- Cuentan los índices en los primeros 100 píxeles del framebuffer
- Muestran los primeros 20 índices para verificar el patrón checkerboard
- Verifican que el índice 3 se convierte correctamente a negro (8, 24, 32)
# --- STEP 0332: Diagnóstico de Framebuffer Recibido ---
if framebuffer_data is not None and len(framebuffer_data) > 0:
if not hasattr(self, '_framebuffer_diagnostic_count'):
self._framebuffer_diagnostic_count = 0
if self._framebuffer_diagnostic_count < 5:
self._framebuffer_diagnostic_count += 1
# Contar índices en el framebuffer
index_counts = {0: 0, 1: 0, 2: 0, 3: 0}
for idx in range(min(100, len(framebuffer_data))):
color_idx = framebuffer_data[idx] & 0x03
if color_idx in index_counts:
index_counts[color_idx] += 1
logger.info(f"[Renderer-Framebuffer-Diagnostic] Frame {self._framebuffer_diagnostic_count} | "
f"Index counts (first 100): 0={index_counts[0]} 1={index_counts[1]} "
f"2={index_counts[2]} 3={index_counts[3]}")
# Verificar primeros 20 píxeles
first_20 = [framebuffer_data[i] & 0x03 for i in range(min(20, len(framebuffer_data)))]
logger.info(f"[Renderer-Framebuffer-Diagnostic] First 20 indices: {first_20}")
# Verificar que el índice 3 se convierte a negro
if index_counts[3] > 0:
test_index = 3
test_color = palette[test_index]
logger.info(f"[Renderer-Framebuffer-Diagnostic] Index 3 -> RGB: {test_color} (should be black: (8, 24, 32))")
if test_color != (8, 24, 32):
logger.warning(f"[Renderer-Framebuffer-Diagnostic] ⚠️ PROBLEMA: Index 3 no se convierte a negro!")
# -------------------------------------------
2. Verificación de Aplicación de Paleta (renderer.py)
Se agregaron logs que verifican que la paleta se aplica correctamente a píxeles específicos:
- Esquina superior izquierda (0, 0)
- Centro de pantalla (80, 72)
- Esquina inferior derecha (159, 143)
Estos logs muestran el índice de color y el RGB resultante para cada píxel, permitiendo verificar que la conversión funciona correctamente.
# --- STEP 0332: Verificación de Aplicación de Paleta ---
if not hasattr(self, '_palette_apply_check_count'):
self._palette_apply_check_count = 0
if self._palette_apply_check_count < 5:
self._palette_apply_check_count += 1
test_pixels = [
(0, 0), # Esquina superior izquierda
(80, 72), # Centro de pantalla
(159, 143) # Esquina inferior derecha
]
for x, y in test_pixels:
idx = y * 160 + x
if idx < len(frame_indices):
color_index = frame_indices[idx] & 0x03
rgb_color = palette[color_index]
logger.info(f"[Renderer-Palette-Apply] Pixel ({x}, {y}): index={color_index} -> RGB={rgb_color}")
if color_index == 3 and rgb_color != (8, 24, 32):
logger.warning(f"[Renderer-Palette-Apply] ⚠️ PROBLEMA: Index 3 no se convierte a negro en ({x}, {y})!")
# -------------------------------------------
3. Verificación Detallada de Copia del Framebuffer (viboy.py)
Se mejoraron los logs en viboy.py para verificar que la copia del framebuffer es idéntica al original:
- Muestra los primeros 20 índices antes y después de copiar
- Verifica que la copia es idéntica al original
- Cuenta los índices en la copia para comparar con los logs de C++
# --- Step 0332: Verificación Detallada de Copia del Framebuffer ---
# Verificar primeros 20 píxeles antes de copiar
first_20_before = [raw_view[i] & 0x03 for i in range(min(20, len(raw_view)))]
if not hasattr(self, '_framebuffer_copy_detailed_count'):
self._framebuffer_copy_detailed_count = 0
if self._framebuffer_copy_detailed_count < 5:
self._framebuffer_copy_detailed_count += 1
logger.info(f"[Viboy-Framebuffer-Copy-Detailed] Frame {self._framebuffer_copy_detailed_count} | "
f"First 20 indices before copy: {first_20_before}")
# Hacer copia profunda
fb_data = bytearray(raw_view)
# Verificar primeros 20 píxeles después de copiar
first_20_after = [fb_data[i] & 0x03 for i in range(min(20, len(fb_data)))]
# Verificar que la copia es idéntica
if first_20_before != first_20_after:
logger.warning(f"[Viboy-Framebuffer-Copy-Detailed] ⚠️ DISCREPANCIA: "
f"Before={first_20_before}, After={first_20_after}")
# Contar índices en la copia
index_counts = {0: 0, 1: 0, 2: 0, 3: 0}
for idx in range(len(fb_data)):
color_idx = fb_data[idx] & 0x03
if color_idx in index_counts:
index_counts[color_idx] += 1
logger.info(f"[Viboy-Framebuffer-Copy-Detailed] Index counts in copy: "
f"0={index_counts[0]} 1={index_counts[1]} "
f"2={index_counts[2]} 3={index_counts[3]}")
# -------------------------------------------
Decisiones de Diseño
- Límite de logs: Los logs solo se muestran en los primeros 5 frames para evitar saturación del contexto y mantener el rendimiento
- Verificación de píxeles específicos: Se verifican píxeles en posiciones conocidas (esquinas y centro) para tener puntos de referencia claros
- Comparación antes/después: Se comparan los datos antes y después de la copia para detectar corrupción durante la transferencia
Archivos Afectados
src/gpu/renderer.py- Agregados logs de diagnóstico del framebuffer recibido y verificación de aplicación de paletasrc/viboy.py- Mejorados logs de verificación detallada de copia del framebuffer
Tests y Verificación
Los logs de diagnóstico se ejecutarán automáticamente cuando se ejecute el emulador con cualquier ROM. Para analizar los resultados:
# Ejecutar pruebas con las 5 ROMs (2.5 minutos cada una)
timeout 150 python3 main.py roms/pkmn.gb 2>&1 | tee logs/test_pkmn_step0332.log
timeout 150 python3 main.py roms/tetris.gb 2>&1 | tee logs/test_tetris_step0332.log
timeout 150 python3 main.py roms/mario.gbc 2>&1 | tee logs/test_mario_step0332.log
timeout 150 python3 main.py roms/pkmn-amarillo.gb 2>&1 | tee logs/test_pkmn_amarillo_step0332.log
timeout 150 python3 main.py roms/Oro.gbc 2>&1 | tee logs/test_oro_step0332.log
# Analizar los logs (usar comandos que no saturan el contexto)
grep "\[Renderer-Framebuffer-Diagnostic\]" logs/test_*_step0332.log | head -n 15
grep "\[Renderer-Palette-Apply\]" logs/test_*_step0332.log | head -n 15
grep "\[Viboy-Framebuffer-Copy-Detailed\]" logs/test_*_step0332.log | head -n 20
Validación esperada:
- Los logs muestran qué índices recibe el renderizador del framebuffer
- Los logs muestran cómo se convierten los índices a RGB
- Se identifica la causa del problema (dónde se pierde la información de color)
Fuentes Consultadas
- Pan Docs: Background Palette (BGP) - Conversión de índices a colores
- Pan Docs: Pixel Rendering - Pipeline de renderizado
- Documentación de Python:
bytearrayymemoryviewpara transferencia de datos binarios
Integridad Educativa
Lo que Entiendo Ahora
- Pipeline de renderizado: El framebuffer contiene índices (0-3), no colores RGB. La conversión a RGB debe hacerse en el renderizador usando la paleta.
- Diagnóstico sistemático: Para encontrar problemas en un pipeline complejo, es necesario agregar logs en cada etapa para ver dónde se pierde la información.
- Verificación de copia: Cuando se copian datos entre C++ y Python, es crítico verificar que la copia es idéntica al original para detectar corrupción.
Lo que Falta Confirmar
- Causa del problema: Los logs de diagnóstico revelarán exactamente dónde se pierde la información de color en el pipeline
- Corrección: Una vez identificada la causa, se implementará la corrección en el Step 0333
Hipótesis y Suposiciones
Hipótesis principal: El problema está en la conversión de índices a RGB en el renderizador. Es posible que:
- La paleta no se esté aplicando correctamente a todos los píxeles
- Hay algún código que sobrescribe los colores RGB después de la conversión
- El framebuffer se corrompe durante la copia (aunque los logs del Step 0331 sugieren que esto no es el caso)
Próximos Pasos
- [ ] Ejecutar pruebas con las 5 ROMs y recopilar logs de diagnóstico
- [ ] Analizar los logs para identificar dónde se pierde la información de color
- [ ] Step 0333: Implementar corrección basada en los hallazgos de los logs
- [ ] Step 0334: Verificación final de renderizado después de la corrección