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.
Blank Screen Triage - Framebuffer vs Paletas vs Blit
Resumen
Step 0488 implementa instrumentación completa para diagnosticar el problema de pantalla en blanco del emulador. Se añaden estadísticas de framebuffer (FrameBufferStats) y paletas (PaletteStats) a los snapshots de rom_smoke, se implementa dump de framebuffer a PPM para evidencia visual fuera de SDL, y se crea un test unitario que valida que el PPU puede producir diversidad de colores cuando VRAM tiene datos. Los resultados muestran que el problema es diferente según el modo: en DMG (tetris.gb) el framebuffer está completamente blanco (bug en fetch/decode), mientras que en CGB (tetris_dx.gbc) el framebuffer SÍ tiene diversidad pero la ventana sigue blanca (bug en blit/presentación).
Concepto de Hardware
El PPU (Picture Processing Unit) del Game Boy renderiza frames de 160x144 píxeles. El proceso de renderizado incluye:
- Fetch de tiles: Lectura de datos de tiles desde VRAM (0x8000-0x97FF)
- Decode 2bpp: Conversión de datos 2bpp a índices de color (0-3)
- Aplicación de paleta: Mapeo de índices a shades (DMG: BGP) o colores RGB (CGB: paletas CGB)
- Write al framebuffer: Escritura de píxeles al buffer de presentación
- Blit/Presentación: Transferencia del framebuffer a la textura SDL para visualización
El problema de pantalla en blanco puede ocurrir en cualquiera de estos pasos. Para diagnosticarlo, necesitamos:
- FrameBufferStats: Verificar si el PPU está generando un framebuffer con más de 1 color/patrón
- PaletteStats: Verificar si las paletas están correctamente configuradas
- PPM Dump: Evidencia visual fuera de SDL para descartar problemas de blit/presentación
Referencia: Pan Docs - LCD Control Register (FF40 - LCDC), Background Palette (FF47 - BGP), CGB Palettes (FF68-FF6B).
Implementación
Se implementan 5 fases de instrumentación para diagnosticar el problema de pantalla en blanco:
Fase A: FrameBufferStats
Se añade la estructura FrameBufferStats a PPU.hpp con:
fb_crc32: Hash del framebuffer para detectar cambiosfb_unique_colors: Número de índices únicos (0-3) presentesfb_nonwhite_count: Píxeles con índice != 0fb_nonblack_count: Píxeles con índice != 3fb_top4_colors: Los 4 índices más frecuentesfb_top4_colors_count: Conteo de cada índicefb_changed_since_last: Indica si el framebuffer cambió desde el último frame
La función compute_framebuffer_stats() se ejecuta después de swap_framebuffers() y está gateada por VIBOY_DEBUG_FB_STATS=1. Se expone a Python vía Cython wrapper.
Fase B: PaletteStats
Se añade recolección de estadísticas de paletas en rom_smoke_0442.py:
- Detección de modo CGB vs DMG
- Registros DMG: BGP, OBP0, OBP1 y mapeo índice→shade derivado
- Registros CGB: BGPI, BGPD, OBPI, OBPD
- Contadores de entradas no-blancas en paletas CGB
Se añaden getters en MMU para acceder a datos de paletas CGB (get_cgb_bg_palette_data(), get_cgb_obj_palette_data()).
Fase C: Dump PPM
Se implementa _dump_framebuffer_to_ppm() en Python que:
- Lee el framebuffer de índices desde el PPU
- Convierte índices a RGB usando BGP (DMG) o paletas CGB
- Escribe un archivo PPM (formato Netpbm P6) en la ruta especificada
- Gateado por
VIBOY_DUMP_FB_FRAMEyVIBOY_DUMP_FB_PATH
Fase D: Test Unitario
Se crea test_ppu_framebuffer_diversity_0488.py que:
- Crea un tile checkerboard en VRAM (alterna índices 0 y 3)
- Configura tilemap y activa LCD/BG
- Ejecuta frames completos esperando explícitamente a
is_frame_ready() - Verifica que
fb_unique_colors >= 2yfb_nonwhite_count > 0
Resultado: ✅ El test pasa, confirmando que el PPU puede producir diversidad cuando VRAM tiene datos.
Fase E: Ejecución rom_smoke y Reporte
Se ejecuta rom_smoke_0442.py con:
VIBOY_SIM_BOOT_LOGO=0(baseline limpio)VIBOY_DEBUG_FB_STATS=1(activar estadísticas)VIBOY_DUMP_FB_FRAME=180(dump en frame 180)- ROMs:
tetris.gb(DMG) ytetris_dx.gbc(CGB)
Se genera reporte en docs/reports/reporte_step0488.md con tablas de snapshots, análisis de PPMs, y árbol de decisión.
Archivos Afectados
src/core/cpp/PPU.hpp- EstructuraFrameBufferStatsy métodoget_framebuffer_stats()src/core/cpp/PPU.cpp- Implementación decompute_framebuffer_stats()y llamada después deswap_framebuffers()src/core/cython/ppu.pxd- Declaración Cython deFrameBufferStatssrc/core/cython/ppu.pyx- Wrapper Python deget_framebuffer_stats()src/memory/mmu.py- Gettersget_cgb_bg_palette_data()yget_cgb_obj_palette_data()tools/rom_smoke_0442.py- Integración de FrameBufferStats y PaletteStats en snapshots, función_dump_framebuffer_to_ppm()tests/test_ppu_framebuffer_diversity_0488.py- Test unitario que valida diversidad del framebufferdocs/reports/reporte_step0488.md- Reporte completo con análisis y árbol de decisión
Tests y Verificación
Test unitario: test_ppu_framebuffer_diversity_0488.py::test_ppu_produces_multiple_colors_when_vram_has_pattern
def test_ppu_produces_multiple_colors_when_vram_has_pattern(self):
"""Test que verifica que PPU produce >1 color cuando VRAM tiene patrón."""
# Crea tile checkerboard en VRAM
# Configura tilemap y activa LCD/BG
# Ejecuta frames completos esperando is_frame_ready()
# Verifica fb_unique_colors >= 2 y fb_nonwhite_count > 0
assert fb_stats['fb_unique_colors'] >= 2
assert fb_stats['fb_nonwhite_count'] > 0
Resultado: ✅ PASA (1 passed in 0.05s)
Validación de módulo compilado C++: El test valida que el PPU compilado en C++ puede producir diversidad de colores cuando VRAM contiene un patrón conocido.
Ejecución rom_smoke:
- tetris.gb (DMG): 240 frames ejecutados, PPM generado en frame 180
- tetris_dx.gbc (CGB): 240 frames ejecutados, PPM generado en frame 180
Fuentes Consultadas
- Pan Docs: LCD Control Register (FF40 - LCDC)
- Pan Docs: Background Palette (FF47 - BGP)
- Pan Docs: CGB Palettes (FF68-FF6B)
- Pan Docs: PPU Rendering Pipeline
Integridad Educativa
Lo que Entiendo Ahora
- Pipeline de renderizado PPU: El proceso completo desde fetch de tiles hasta presentación, y dónde puede fallar cada paso.
- FrameBufferStats: Cómo medir la diversidad del framebuffer sin depender de la visualización SDL.
- PaletteStats: Cómo verificar que las paletas están correctamente configuradas en ambos modos (DMG y CGB).
- Dump PPM: Cómo obtener evidencia visual fuera del sistema de presentación para aislar problemas de blit/presentación.
Lo que Falta Confirmar
- Bug en fetch/decode DMG: Por qué el framebuffer está completamente blanco en modo DMG a pesar de que el tilemap tiene datos.
- Bug en blit/presentación CGB: Por qué la ventana SDL muestra pantalla blanca cuando el framebuffer tiene diversidad (4 colores únicos).
- Timing de carga de tiles: Si hay restricciones de acceso a VRAM que impiden la carga correcta de tiles en ROMs reales.
Hipótesis y Suposiciones
Hipótesis principal: El problema es diferente según el modo:
- DMG: El PPU no está renderizando correctamente (framebuffer vacío). Posibles causas: bug en fetch/decode, restricciones de acceso VRAM, o inicialización incorrecta.
- CGB: El PPU SÍ está renderizando (framebuffer tiene diversidad), pero el blit/presentación falla. Posibles causas: formato de textura SDL incorrecto, pitch/stride mal configurado, o blanqueo después del render.
Próximos Pasos
Step 0489 - Diagnóstico Específico por Modo:
- Para tetris.gb (DMG):
- Instrumentar
render_scanline()para verificar fetch/decode en modo DMG - Comparar con tetris_dx.gbc que SÍ funciona (modo CGB)
- Verificar restricciones de acceso a VRAM durante modos PPU en DMG
- Instrumentar
- Para tetris_dx.gbc (CGB):
- Instrumentar antes y después del blit (hash del buffer fuente vs hash del buffer subido a textura)
- Verificar formato de textura SDL (RGBA vs BGRA)
- Verificar pitch/stride del framebuffer
- Verificar que no se esté blanqueando el framebuffer después del render