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.
Fix de Inicialización PPU C++ y Debug Visual
Resumen
El emulador C++ corría a 60 FPS pero mostraba pantalla negra. El diagnóstico reveló que los registros de la PPU (LCDC, BGP) no se inicializaban correctamente en el constructor C++, quedando en 0. Se implementó inicialización explícita de registros con valores seguros (LCDC=0x91, BGP=0xE4) y se agregó un píxel de diagnóstico rojo en el framebuffer para verificar el enlace C++ → Python. Esto permite confirmar que el puente de memoria funciona correctamente antes de depurar el renderizado completo.
Concepto de Hardware
En la Game Boy, la PPU (Pixel Processing Unit) controla el renderizado de la pantalla mediante varios registros I/O mapeados en memoria:
- LCDC (0xFF40): Control del LCD. El bit 7 (LCD Enable) debe estar activo (1) para que la PPU funcione. Si está en 0, la pantalla permanece blanca/negra. El bit 0 (BG Display Enable) controla si el fondo se dibuja.
- BGP (0xFF47): Background Palette. Define los 4 colores del fondo (cada par de bits representa un color 0-3). Si está en 0x00, todos los colores son transparentes/blancos, resultando en pantalla blanca.
- SCX/SCY (0xFF43/0xFF42): Scroll X/Y. Controlan el desplazamiento del fondo.
- OBP0/OBP1 (0xFF48/0xFF49): Object Palettes. Paletas para sprites.
Problema identificado: En C++, si no se inicializan explícitamente las variables, pueden quedar en 0 (comportamiento no definido). Si LCDC=0, la PPU no renderiza. Si BGP=0, todos los píxeles son transparentes. Esto causaba la pantalla negra a pesar de que el bucle de emulación funcionaba correctamente.
Solución: Inicializar explícitamente los registros en el constructor de PPU con valores seguros que permitan ver algo en pantalla, incluso si el juego aún no ha configurado estos registros.
Fuente: Pan Docs - LCD Control Register (LCDC), Background Palette (BGP)
Implementación
Se modificó el constructor de PPU en C++ para inicializar los registros críticos con valores seguros y agregar un píxel de diagnóstico en el framebuffer.
Componentes modificados
- src/core/cpp/PPU.cpp: Constructor actualizado para inicializar registros y agregar píxel de diagnóstico
Cambios realizados
1. Inicialización de registros en el constructor:
// CRÍTICO: Inicializar registros de la PPU con valores seguros
if (mmu_ != nullptr) {
mmu_->write(IO_LCDC, 0x91); // LCD ON, BG ON
mmu_->write(IO_BGP, 0xE4); // Paleta estándar
mmu_->write(IO_SCX, 0x00); // Scroll inicial
mmu_->write(IO_SCY, 0x00);
mmu_->write(IO_OBP0, 0xE4); // Paletas de sprites
mmu_->write(IO_OBP1, 0xE4);
}
2. Píxel de diagnóstico:
// DIAGNÓSTICO: Pintar un píxel ROJO en la esquina superior izquierda
// Esto nos permitirá verificar que el enlace C++ -> Python funciona
framebuffer_[0] = 0xFFFF0000; // ARGB: Rojo sólido
3. Framebuffer inicializado a gris:
framebuffer_(FRAMEBUFFER_SIZE, 0xFF808080) // Gris en lugar de blanco
El framebuffer se inicializa a gris (0x808080) en lugar de blanco para facilitar la visualización del píxel rojo de diagnóstico. Si el enlace C++ → Python funciona, deberíamos ver una pantalla gris con un punto rojo en la esquina superior izquierda.
Decisiones de diseño
- Valores por defecto seguros: Se eligieron valores que permiten ver algo en pantalla incluso si el juego no ha inicializado los registros. LCDC=0x91 activa el LCD y el fondo. BGP=0xE4 es el valor estándar usado por muchos juegos.
- Píxel de diagnóstico: Se agregó un píxel rojo en la posición 0 del framebuffer para verificar que el enlace de memoria funciona. Si vemos el rojo, sabemos que Python está leyendo correctamente la memoria de C++.
- Inicialización en constructor: Se decidió inicializar los registros en el constructor de PPU en lugar de en viboy.py para asegurar que siempre tengan valores válidos, independientemente del orden de inicialización.
Archivos Afectados
src/core/cpp/PPU.cpp- Constructor modificado para inicializar registros y agregar píxel de diagnóstico
Tests y Verificación
Validación visual:
- Al ejecutar
python main.py roms/tetris.gb, se espera ver:- Si el enlace funciona: Pantalla gris con un punto rojo en la esquina superior izquierda
- Si el renderizado funciona: El juego debería aparecer encima del gris
- Si sigue negra: El puntero del framebuffer no se está pasando correctamente
Compilación:
$ python setup.py build_ext --inplace
running build_ext
building 'viboy_core' extension
...
copying build\lib.win-amd64-cpython-313\viboy_core.cp313-win_amd64.pyd ->
Compilación exitosa sin errores (solo warnings menores no críticos).
Próximos pasos de verificación:
- Ejecutar el emulador y verificar el color de la pantalla
- Si aparece gris con punto rojo: El enlace funciona, depurar renderizado
- Si sigue negra: Verificar el wrapper Cython y el memoryview del framebuffer
Fuentes Consultadas
- Pan Docs: LCD Control Register (LCDC)
- Pan Docs: Palettes (BGP, OBP0, OBP1)
- Pan Docs: Scrolling (SCX, SCY)
Integridad Educativa
Lo que Entiendo Ahora
- Inicialización en C++: A diferencia de Python, en C++ las variables no inicializadas pueden tener valores aleatorios o 0. Es crítico inicializar explícitamente todos los registros que controlan el hardware.
- Registros de la PPU: LCDC y BGP son esenciales para que la PPU renderice. Si están en 0, la pantalla permanece negra/blanca incluso si el bucle de emulación funciona correctamente.
- Debug visual: Agregar píxeles de diagnóstico en el framebuffer es una técnica efectiva para verificar que el enlace de memoria C++ → Python funciona correctamente.
Lo que Falta Confirmar
- Enlace de memoria: Verificar que el memoryview de Cython apunta correctamente a la memoria de C++ y que los cambios se reflejan en Python.
- Renderizado completo: Una vez confirmado el enlace, verificar que el renderizado de tiles funciona correctamente y que el juego aparece en pantalla.
Hipótesis y Suposiciones
Hipótesis principal: El problema era la falta de inicialización de registros, no un bug en el renderizado. Si el píxel rojo aparece, confirmamos que el enlace funciona y podemos depurar el renderizado. Si no aparece, el problema está en el puente Cython.
Próximos Pasos
- [ ] Ejecutar el emulador y verificar el color de la pantalla
- [ ] Si aparece gris con punto rojo: Depurar el renderizado de tiles (render_bg)
- [ ] Si sigue negra: Verificar el wrapper Cython (ppu.pyx) y el memoryview del framebuffer
- [ ] Una vez confirmado el enlace, remover el píxel de diagnóstico y el color gris del framebuffer