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 de Pantallas Completamente Blancas
Resumen
Se implementaron verificaciones de diagnóstico exhaustivas en todas las etapas del pipeline de renderizado para investigar por qué las pantallas están completamente blancas. Los logs confirman que el pipeline funciona correctamente: `render_scanline()` se ejecuta, escribe datos al framebuffer (checkerboard), el intercambio de buffers funciona, y Python lee los datos correctamente. El renderizador también recibe los datos. Sin embargo, se identificó un problema crítico: `render_scanline()` se ejecuta en Mode 2 (OAM Search) en lugar de Mode 0 (H-Blank), lo cual puede afectar el timing del renderizado.
Concepto de Hardware
Pipeline de Renderizado C++ → Python
El pipeline de renderizado funciona en varias etapas:
- C++ PPU (`render_scanline()`): Escribe índices de color (0-3) al `framebuffer_back_` durante H-Blank (Mode 0).
- C++ PPU (`swap_framebuffers()`): Intercambia `framebuffer_back_` y `framebuffer_front_` cuando se completa un frame completo (LY=144).
- C++ PPU (`get_frame_ready_and_reset()`): Marca el frame como listo y devuelve puntero a `framebuffer_front_`.
- Python (`viboy.py`): Lee el framebuffer desde C++ y crea un snapshot inmutable.
- Python (`renderer.py`): Convierte índices a RGB y dibuja en la pantalla.
Timing del Renderizado: Según Pan Docs, el renderizado de una línea debe ocurrir durante H-Blank (Mode 0), después de que Mode 3 (Pixel Transfer) completa. Si `render_scanline()` se ejecuta en un modo diferente, puede haber problemas de sincronización.
Checkerboard Temporal: Cuando VRAM está vacía (`vram_is_empty_ == true`), se activa un patrón checkerboard (alternando índices 0 y 3) para visualizar que el renderizado funciona mientras el juego carga tiles.
Implementación
Se agregaron verificaciones de diagnóstico en 6 puntos críticos del pipeline:
Verificaciones Implementadas
- Ejecución de `render_scanline()`: Logs al inicio de la función confirmando ejecución y modo/timing.
- Escritura al framebuffer: Verificación después de escribir todos los píxeles de la línea, contando píxeles no-blancos y distribución de índices.
- Activación del checkerboard: Logs cuando se detecta que el checkerboard debería activarse y cuando se escribe al framebuffer.
- Framebuffer antes de que Python lo lea: Verificación en `get_frame_ready_and_reset()` antes de `swap_framebuffers()`, contando píxeles no-blancos en `framebuffer_back_`.
- Framebuffer después del intercambio: Verificación en `swap_framebuffers()` después del intercambio, contando píxeles no-blancos en `framebuffer_front_`.
- Framebuffer en Python: Verificación cuando Python lee el framebuffer de C++, analizando los primeros 100 píxeles.
Hallazgos Clave
- ✅ `render_scanline()` se ejecuta: Se ejecuta en cada línea visible (LY 0-143), pero en Mode 2 (OAM Search) en lugar de Mode 0 (H-Blank).
- ✅ Se escriben datos al framebuffer: 80/160 píxeles no-blancos por línea (checkerboard), distribución 0=80, 3=80.
- ✅ Checkerboard se activa: Se activa correctamente cuando VRAM está vacía y tiles están vacíos.
- ✅ Framebuffer tiene datos antes del intercambio: 11520/23040 píxeles no-blancos (50% - checkerboard completo).
- ✅ Framebuffer mantiene datos después del intercambio: 11520/23040 píxeles no-blancos en `framebuffer_front_`.
- ✅ Python lee los datos: 52/100 píxeles no-blancos en los primeros 100 píxeles.
- ✅ Renderizador recibe los datos: 11520/23040 píxeles no-blancos, patrón checkerboard correcto [3,3,3,3,3,3,3,3,0,0,0,0,0,0,0,0,3,3,3,3].
Problema Crítico Identificado
`render_scanline()` se ejecuta en Mode 2 (OAM Search) en lugar de Mode 0 (H-Blank). Según Pan Docs, el renderizado debe ocurrir durante H-Blank (Mode 0), después de que Mode 3 (Pixel Transfer) completa. Aunque los datos se escriben correctamente, esto puede causar problemas de timing y sincronización.
Archivos Afectados
src/core/cpp/PPU.cpp- Agregadas verificaciones en `render_scanline()`, `get_frame_ready_and_reset()`, y `swap_framebuffers()`src/viboy.py- Agregada verificación cuando Python lee el framebuffer de C++logs/test_*_step0372.log- Logs de diagnóstico de las 6 ROMs de prueba
Tests y Verificación
Se ejecutaron pruebas cortas (30 segundos) con las 6 ROMs principales para capturar logs de diagnóstico:
- ROMs probadas: tetris.gb, mario.gbc, zelda-dx.gbc, Oro.gbc, pkmn.gb, pkmn-amarillo.gb
- Comando ejecutado:
timeout 30 python3 main.py roms/[ROM].gb[c] > logs/test_[ROM]_step0372.log 2>&1 - Análisis de logs: Se analizaron los logs usando comandos `grep` con límites para identificar dónde se pierde la información
Evidencia de Logs
[PPU-RENDER-EXECUTION] Frame 1 | LY: 0 | render_scanline() ejecutado | Count: 1
[PPU-RENDER-EXECUTION] Mode: 2 (0=H-Blank, 1=V-Blank, 2=OAM, 3=Pixel Transfer) | LY: 0 | Expected: H-Blank (0)
[PPU-FRAMEBUFFER-WRITE] Frame 1 | LY: 0 | Non-zero pixels written: 80/160 | Distribution: 0=80 1=0 2=0 3=80
[PPU-CHECKERBOARD-ACTIVATE] Frame 1 | LY: 0 | X: 0 | Checkerboard activado | Tile empty: YES | VRAM empty: YES | Count: 1
[PPU-FRAMEBUFFER-BEFORE-READ] Frame 1 | Total non-zero pixels: 11520/23040 | Distribution: 0=11520 1=0 2=0 3=11520
[PPU-FRAMEBUFFER-AFTER-SWAP] Frame 1 | Total non-zero pixels in front: 11520/23040 | Distribution: 0=11520 1=0 2=0 3=11520
[Viboy-Framebuffer-Read] Frame 1 | Non-zero pixels (first 100): 52/100 | Distribution: 0=48 1=0 2=0 3=52
[Renderer-Received] Frame 1 | First 20 indices: [3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3] | Non-zero pixels: 11520/23040 (50.00%)
Validación de módulo compilado C++: El módulo se recompiló exitosamente con las nuevas verificaciones. Los logs confirman que todas las etapas del pipeline funcionan correctamente.
Fuentes Consultadas
- Pan Docs: LCD Timing, Background, Window
- Pan Docs: PPU Modes (Mode 0, 1, 2, 3)
Integridad Educativa
Lo que Entiendo Ahora
- Pipeline de renderizado: El pipeline C++ → Python funciona correctamente. Los datos se escriben, se intercambian, y se leen sin pérdida de información.
- Timing del renderizado: `render_scanline()` debe ejecutarse durante H-Blank (Mode 0), no durante OAM Search (Mode 2). Esto es crítico para la sincronización correcta.
- Checkerboard temporal: El checkerboard se activa y funciona correctamente cuando VRAM está vacía, generando un patrón visible (50% píxeles no-blancos).
Lo que Falta Confirmar
- Problema de timing: Por qué `render_scanline()` se ejecuta en Mode 2 en lugar de Mode 0. Esto requiere investigar dónde se llama la función y en qué momento del ciclo de la PPU.
- Conversión RGB: Si el renderizador Python está convirtiendo correctamente los índices (0, 3) a RGB. Los índices 0 y 3 deberían convertirse a blanco y negro respectivamente según la paleta BGP.
- Dibujado en pantalla: Si Pygame está dibujando correctamente los píxeles RGB en la superficie de la ventana.
Hipótesis y Suposiciones
Hipótesis principal: El problema de las pantallas completamente blancas NO está en el pipeline de datos (que funciona correctamente), sino posiblemente en:
- El timing incorrecto de `render_scanline()` (Mode 2 vs Mode 0) que puede causar problemas de sincronización.
- La conversión de índices a RGB en el renderizador Python.
- El dibujado de píxeles en la superficie de Pygame.
Próximos Pasos
- [ ] Investigar por qué `render_scanline()` se ejecuta en Mode 2 en lugar de Mode 0 y corregir el timing.
- [ ] Verificar la conversión de índices a RGB en el renderizador Python (especialmente índices 0 y 3).
- [ ] Verificar que Pygame está dibujando correctamente los píxeles RGB en la superficie de la ventana.
- [ ] Si el problema persiste, considerar desactivar completamente el checkerboard temporal y forzar renderizado normal para aislar el problema.