⚠️ 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.

Fix de Inicialización PPU C++ y Debug Visual

Fecha: 2025-12-19 Step ID: 0115 Estado: VERIFIED

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

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