⚠️ Clean-Room / Educativo

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

Fecha: 2026-01-05 Step ID: 0488 Estado: VERIFIED

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:

  1. Fetch de tiles: Lectura de datos de tiles desde VRAM (0x8000-0x97FF)
  2. Decode 2bpp: Conversión de datos 2bpp a índices de color (0-3)
  3. Aplicación de paleta: Mapeo de índices a shades (DMG: BGP) o colores RGB (CGB: paletas CGB)
  4. Write al framebuffer: Escritura de píxeles al buffer de presentación
  5. 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 cambios
  • fb_unique_colors: Número de índices únicos (0-3) presentes
  • fb_nonwhite_count: Píxeles con índice != 0
  • fb_nonblack_count: Píxeles con índice != 3
  • fb_top4_colors: Los 4 índices más frecuentes
  • fb_top4_colors_count: Conteo de cada índice
  • fb_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_FRAME y VIBOY_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 >= 2 y fb_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) y tetris_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 - Estructura FrameBufferStats y método get_framebuffer_stats()
  • src/core/cpp/PPU.cpp - Implementación de compute_framebuffer_stats() y llamada después de swap_framebuffers()
  • src/core/cython/ppu.pxd - Declaración Cython de FrameBufferStats
  • src/core/cython/ppu.pyx - Wrapper Python de get_framebuffer_stats()
  • src/memory/mmu.py - Getters get_cgb_bg_palette_data() y get_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 framebuffer
  • docs/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

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
  • 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