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.
Debug: Instrumentación del Pipeline de Píxeles en C++
Resumen
¡Hito alcanzado! La arquitectura de bucle nativo ha resuelto todos los deadlocks y el emulador funciona a 60 FPS con LY ciclando correctamente. Sin embargo, la pantalla permanece en blanco porque el método render_scanline() de la PPU en C++ está generando un framebuffer lleno de ceros.
Este Step instrumenta el pipeline de renderizado de píxeles dentro de PPU::render_scanline() con logs de diagnóstico detallados para identificar por qué no se están leyendo los datos de los tiles desde la VRAM. El diagnóstico del "renderizador ciego" sugiere que el método se ejecuta correctamente pero falla en algún punto de la cadena de renderizado (cálculo de direcciones, lectura de memoria, decodificación de bits).
Concepto de Hardware: El Pipeline de Renderizado de un Píxel
Para dibujar un solo píxel en la pantalla, la PPU realiza una compleja cadena de cálculos y lecturas de memoria. Cada paso de esta cadena es crítico y cualquier fallo resultará en un píxel incorrecto o en blanco:
- Cálculo de Coordenadas en el Tilemap: Calcula la coordenada
(map_x, map_y)en el mapa de fondo de 256x256 píxeles, aplicando el scroll (SCX,SCY). - Búsqueda del Tile en el Tilemap: Usa
(map_x, map_y)para encontrar la posición del tile correspondiente en el tilemap (0x9800o0x9C00). - Lectura del ID del Tile: Lee el ID del tile (
tile_id) de esa posición del tilemap. - Cálculo de la Dirección del Tile: Usa el
tile_idpara calcular la dirección base de los datos del tile en la tabla de tiles (0x8000o0x8800). - Lectura de los Datos del Píxel: Lee los 2 bytes que corresponden a la línea de píxeles correcta dentro de ese tile.
- Decodificación del Índice de Color: Decodifica esos 2 bytes para obtener el índice de color (0-3) del píxel final.
Si cualquier paso de esta cadena falla (un cálculo de dirección incorrecto, una lectura de memoria que devuelve 0, una decodificación errónea), el resultado final será un píxel de color 0 (blanco). El framebuffer lleno de ceros que observamos sugiere que uno de estos pasos está fallando sistemáticamente.
Según Pan Docs, el formato de datos de un tile es:
- Cada tile ocupa 16 bytes (8 líneas × 2 bytes por línea).
- Cada línea del tile ocupa 2 bytes consecutivos en VRAM.
- El byte bajo contiene el bit menos significativo de cada píxel (bit 7 = píxel 0, bit 6 = píxel 1, ...).
- El byte alto contiene el bit más significativo de cada píxel (bit 7 = píxel 0, bit 6 = píxel 1, ...).
- El índice de color final se calcula como:
color_index = (byte_high_bit << 1) | byte_low_bit.
Implementación
Se instrumentó el método render_scanline() con logs de depuración detallados que muestran los valores intermedios del pipeline de renderizado para los primeros píxeles de las primeras dos líneas.
Modificación en PPU.cpp
Se agregó #include <cstdio> al principio del archivo y se añadieron logs de depuración dentro del bucle de renderizado:
#include "PPU.hpp"
#include "MMU.hpp"
#include <cstdio>
// ... dentro de render_scanline() ...
// --- LOGS DE DEPURACIÓN (Step 0180) ---
// Variable estática para imprimir logs solo una vez (primeras dos líneas, primeros 8 píxeles)
static bool debug_printed = false;
// ... código de renderizado ...
// --- LOGS DE DEPURACIÓN (Step 0180) ---
// Imprimir logs detallados para los primeros píxeles de las primeras dos líneas
if (!debug_printed && ly_ < 2 && x < 8) {
// Mostrar tile_id como signed si estamos en modo signed, unsigned si estamos en modo unsigned
int display_tile_id = signed_addressing ? static_cast<int8_t>(tile_id) : static_cast<int>(tile_id);
printf("[PPU DEBUG] ly=%d, x=%d | map_x=%d, map_y=%d | tile_map_addr=0x%04X | tile_id=%d | tile_addr=0x%04X | byte1=0x%02X, byte2=0x%02X | color=%d\n",
ly_, x, map_x, map_y, tile_map_addr, display_tile_id, tile_addr, byte1, byte2, color_index);
}
// Marcar que ya imprimimos los logs (solo una vez, después de la línea 1)
if (ly_ == 1) {
debug_printed = true;
}
Decisiones de Diseño
- Logs Limitados: Los logs solo se imprimen para los primeros 8 píxeles de las primeras 2 líneas para evitar saturar la consola. Una vez impresos, se desactivan automáticamente.
- Información Completa: Cada log muestra todos los valores intermedios del pipeline: coordenadas, direcciones, IDs, bytes leídos y color final.
- Modo Signed/Unsigned: El
tile_idse muestra correctamente según el modo de direccionamiento (signed o unsigned) para facilitar el diagnóstico.
Archivos Afectados
src/core/cpp/PPU.cpp- Agregado#include <cstdio>e instrumentación con logs de depuración enrender_scanline()
Tests y Verificación
Este cambio requiere recompilación del módulo C++ y ejecución del emulador para capturar los logs de depuración:
- Recompilación del Módulo C++:
.\rebuild_cpp.ps1 - Ejecución del Emulador:
python main.py roms/tetris.gb - Análisis de la Salida:
La consola mostrará un log detallado para los primeros píxeles. Estaremos buscando:
- Si
byte1ybyte2son siempre0x00: El problema está en el cálculo detile_addrotile_line_addr. Estamos apuntando a una zona vacía de la VRAM. - Si
tile_ides siempre0: El problema está en el cálculo detile_map_addr. No estamos leyendo el mapa de tiles correctamente. - Si
byte1ybyte2tienen valores correctos perocolor_indexes0: El problema está en la decodificación final de los bits.
- Si
Validación de Módulo Compilado C++: Los logs se generan directamente desde el código C++ compilado, confirmando que el pipeline de renderizado se está ejecutando en el código nativo.
Fuentes Consultadas
- Pan Docs: Sección sobre el formato de datos de tiles y el pipeline de renderizado de la PPU
- Diagnóstico del Renderizador Ciego: Análisis del framebuffer lleno de ceros y el comportamiento del método
render_scanline()
Integridad Educativa
Lo que Entiendo Ahora
- Pipeline de Renderizado: El renderizado de un píxel es un proceso complejo que involucra múltiples pasos: cálculo de coordenadas, lectura del tilemap, cálculo de direcciones, lectura de VRAM y decodificación de bits. Cualquier fallo en cualquier paso resultará en un píxel incorrecto.
- Instrumentación Quirúrgica: Los logs de depuración deben ser limitados y específicos para evitar saturar la consola, pero deben mostrar suficiente información para diagnosticar el problema.
- Diagnóstico del Renderizador Ciego: El framebuffer lleno de ceros sugiere que el método se ejecuta pero falla en algún punto del pipeline. Los logs nos permitirán identificar exactamente dónde.
Lo que Falta Confirmar
- Punto de Fallo en el Pipeline: Necesitamos ejecutar el emulador y analizar los logs para identificar exactamente dónde falla el pipeline de renderizado.
- Estado de la VRAM: Necesitamos confirmar si los datos de tiles están realmente en VRAM o si el problema es que no se han copiado aún.
- Cálculo de Direcciones: Necesitamos verificar si las direcciones calculadas (
tile_map_addr,tile_addr,tile_line_addr) son correctas.
Hipótesis y Suposiciones
Hipótesis Principal: El pipeline de renderizado se está ejecutando, pero falla en algún paso específico (cálculo de direcciones, lectura de memoria, decodificación). Los logs nos permitirán identificar exactamente dónde.
Suposición: Asumimos que los datos de tiles están en VRAM (la CPU ha ejecutado las rutinas de inicialización). Si los logs muestran que byte1 y byte2 son siempre 0x00, esto sugeriría que estamos apuntando a una zona vacía de VRAM o que el cálculo de direcciones es incorrecto.
Próximos Pasos
- [ ] Recompilar el módulo C++ con la instrumentación de depuración
- [ ] Ejecutar el emulador y capturar los logs de depuración
- [ ] Analizar los logs para identificar el punto de fallo en el pipeline
- [ ] Si
byte1ybyte2son0x00: Investigar el cálculo de direcciones de tiles - [ ] Si
tile_ides siempre0: Investigar el cálculo de direcciones del tilemap - [ ] Si los bytes son correctos pero
color_indexes0: Investigar la decodificación de bits - [ ] Corregir el problema identificado y verificar que el framebuffer se llena correctamente