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

Investigación de Generación del Framebuffer en C++ PPU

Fecha: 2025-12-29 Step ID: 0351 Estado: VERIFIED

Resumen

Se implementó código de diagnóstico detallado para investigar la generación del framebuffer en C++ PPU. El objetivo era verificar si el framebuffer contiene los datos correctos cuando hay tiles reales, si los tiles se decodifican correctamente en C++, si la paleta se aplica correctamente, y comparar el contenido del framebuffer cuando hay tiles reales vs cuando solo hay checkerboard. Los resultados muestran que el código de diagnóstico funciona correctamente, pero confirman que VRAM tiene muy pocos bytes no-cero (40/6144 o menos), por debajo del umbral de 200, por lo que nunca se detectan tiles reales y el framebuffer siempre contiene solo checkerboard.

Concepto de Hardware

Decodificación de Tiles 2bpp: Cada tile es 8x8 píxeles = 16 bytes (2 bytes por línea). El formato 2bpp usa 2 bits por píxel, lo que permite 4 colores posibles (0-3). Para cada línea: Byte1 = bits bajos, Byte2 = bits altos. El color del píxel se calcula como (bit_alto << 1) | bit_bajo. Esta decodificación debe realizarse correctamente para que los tiles se rendericen correctamente en el framebuffer.

Aplicación de Paleta (BGP): BGP (0xFF47) define la paleta de 4 colores. Cada 2 bits de BGP mapean un índice de color (0-3) a un color de la paleta. El índice decodificado del tile se mapea usando BGP para obtener el índice final que se escribe en el framebuffer. Si la paleta no se aplica correctamente, los colores en el framebuffer serán incorrectos.

Generación del Framebuffer: El framebuffer se genera línea por línea durante el renderizado. Cada píxel se decodifica del tile correspondiente, el índice de color se aplica usando la paleta BGP, y el índice final se escribe en el framebuffer. Si el framebuffer contiene solo índices 0 y 3 (checkerboard) incluso cuando hay tiles reales en VRAM, el problema está en la generación del framebuffer (decodificación de tiles o aplicación de paleta).

Detección de Tiles Reales: Para detectar si hay tiles reales en VRAM, se cuenta el número de bytes no-cero en VRAM (0x8000-0x97FF = 6144 bytes). Si hay más de 200 bytes no-cero (aprox. 12 tiles completos), se considera que hay tiles reales. Si VRAM tiene muy pocos bytes no-cero, se usa un patrón de checkerboard temporal para mostrar algo en pantalla mientras el juego carga tiles.

Implementación

Se implementaron 4 tareas principales de diagnóstico en src/core/cpp/PPU.cpp:

1. Verificación del Contenido del Framebuffer Cuando Hay Tiles Reales

Se agregó código en PPU::render_scanline() cuando ly_ == VBLANK_START para verificar el contenido completo del framebuffer cuando hay tiles reales. El código cuenta los índices de color en todo el framebuffer, verifica si hay líneas con más de 2 índices diferentes (no solo checkerboard), y genera advertencias si el framebuffer contiene solo checkerboard aunque haya tiles reales en VRAM.

// --- Step 0351: Verificación Detallada del Framebuffer con Tiles Reales ---
if (tiles_were_detected_this_frame && framebuffer_content_detailed_count < 10) {
    // Contar índices en todo el framebuffer
    int index_counts[4] = {0, 0, 0, 0};
    int total_non_zero_pixels = 0;
    int lines_with_varied_indices = 0;
    
    for (int y = 0; y < 144; y++) {
        // ... contar índices por línea ...
    }
    
    // Advertencia si el framebuffer contiene solo checkerboard
    if (index_counts[1] == 0 && index_counts[2] == 0 && 
        index_counts[0] > 0 && index_counts[3] > 0) {
        printf("[PPU-FRAMEBUFFER-CONTENT-DETAILED] ⚠️ ADVERTENCIA: ...\n");
    }
}
// -------------------------------------------

2. Verificación de Decodificación de Tiles en C++

Se agregó código antes del bucle de renderizado para verificar algunos tiles durante el renderizado (solo en LY=0 y LY=72). El código lee los datos del tile, decodifica la primera línea del tile usando el formato 2bpp, y loggea los píxeles decodificados para verificar que la decodificación es correcta.

// --- Step 0351: Verificación Detallada de Decodificación de Tiles ---
if ((ly_ == 0 || ly_ == 72) && tile_decode_detailed_count < 10) {
    for (int x = 0; x < SCREEN_WIDTH && x < 80; x += 8) {
        // Leer datos del tile
        uint8_t byte1 = mmu_->read(tile_addr);
        uint8_t byte2 = mmu_->read(tile_addr + 1);
        
        // Decodificar primera línea del tile
        uint8_t decoded_pixels[8];
        for (int bit = 7; bit >= 0; bit--) {
            uint8_t bit_low = (byte1 >> bit) & 1;
            uint8_t bit_high = (byte2 >> bit) & 1;
            decoded_pixels[7 - bit] = (bit_high << 1) | bit_low;
        }
        
        // Loggear píxeles decodificados
        printf("[PPU-TILE-DECODE-DETAILED] ...\n");
    }
}
// -------------------------------------------

3. Verificación de Aplicación de Paleta en C++

Se agregó código antes del bucle de renderizado para verificar la aplicación de paleta. El código lee BGP y loggea su valor para verificar que la paleta se está aplicando correctamente.

// --- Step 0351: Verificación Detallada de Aplicación de Paleta ---
if ((ly_ == 0 || ly_ == 72) && palette_apply_detailed_count < 10) {
    uint8_t bgp_check = mmu_->read(IO_BGP);
    printf("[PPU-PALETTE-APPLY-DETAILED] Frame %llu | LY: %d | BGP=0x%02X\n", ...);
}
// -------------------------------------------

4. Comparación Framebuffer con Tiles Reales vs Checkerboard

Se agregó código en PPU::render_scanline() cuando ly_ == VBLANK_START para comparar el contenido del framebuffer cuando hay tiles reales vs cuando solo hay checkerboard. El código verifica si hay tiles reales en VRAM, cuenta los índices en el framebuffer, determina si el framebuffer contiene solo checkerboard, y genera advertencias si hay tiles reales pero el framebuffer contiene solo checkerboard.

// --- Step 0351: Comparación Framebuffer con Tiles Reales vs Checkerboard ---
if (framebuffer_comparison_count < 10) {
    // Verificar si hay tiles reales
    int non_zero_bytes = 0;
    for (uint16_t addr = 0x8000; addr < 0x9800; addr++) {
        if (mmu_->read(addr) != 0x00) non_zero_bytes++;
    }
    bool has_real_tiles = (non_zero_bytes >= 200);
    
    // Contar índices en el framebuffer
    int index_counts[4] = {0, 0, 0, 0};
    // ... contar índices ...
    
    // Determinar si el framebuffer contiene solo checkerboard
    bool is_checkerboard_only = (index_counts[1] == 0 && index_counts[2] == 0 && 
                                  index_counts[0] > 0 && index_counts[3] > 0);
    
    // Advertencia si hay tiles reales pero el framebuffer contiene solo checkerboard
    if (has_real_tiles && is_checkerboard_only) {
        printf("[PPU-FRAMEBUFFER-COMPARISON] ⚠️ PROBLEMA: ...\n");
    }
}
// -------------------------------------------

Archivos Afectados

  • src/core/cpp/PPU.cpp - Agregado código de diagnóstico para verificar contenido del framebuffer, decodificación de tiles, aplicación de paleta, y comparación framebuffer con tiles reales vs checkerboard

Tests y Verificación

Se ejecutaron pruebas con las 5 ROMs en paralelo durante ~2.5 minutos cada una:

  • ROMs probadas: pkmn.gb, tetris.gb, mario.gbc, pkmn-amarillo.gb, Oro.gbc
  • Comando ejecutado: timeout 150 python3 main.py roms/[ROM].gb 2>&1 | tee logs/test_[ROM]_step0351.log
  • Análisis de logs: Se analizaron los logs usando grep para extraer información específica sin saturar el contexto

Resultados de las Pruebas

Logs de Aplicación de Paleta:

logs/test_mario_step0351.log:[PPU-PALETTE-APPLY-DETAILED] Frame 1 | LY: 0 | BGP=0xE4
logs/test_oro_step0351.log:[PPU-PALETTE-APPLY-DETAILED] Frame 1 | LY: 0 | BGP=0x00
logs/test_pkmn_step0351.log:[PPU-PALETTE-APPLY-DETAILED] Frame 1 | LY: 0 | BGP=0x00

✅ La paleta se está leyendo correctamente (BGP se lee desde MMU)

Logs de Comparación Framebuffer:

logs/test_mario_step0351.log:[PPU-FRAMEBUFFER-COMPARISON] Frame 1 | Has real tiles: NO | Is checkerboard only: YES | Distribution: 0=11520 1=0 2=0 3=11520
logs/test_pkmn_step0351.log:[PPU-FRAMEBUFFER-COMPARISON] Frame 1 | Has real tiles: NO | Is checkerboard only: YES | Distribution: 0=11520 1=0 2=0 3=11520

✅ El código de diagnóstico funciona correctamente ❌ No se detectan tiles reales (Has real tiles: NO) ❌ El framebuffer siempre contiene solo checkerboard (Is checkerboard only: YES) ❌ La distribución es exactamente 50% índice 0 y 50% índice 3 (11520 de cada uno)

Logs de Diagnóstico VRAM:

logs/test_mario_step0351.log:[PPU-VRAM-DIAG] Frame 1 | Non-zero bytes: 40/6144 | Umbral: 200 | Detectado: 0
logs/test_oro_step0351.log:[PPU-VRAM-DIAG] Frame 1 | Non-zero bytes: 0/6144 | Umbral: 200 | Detectado: 0

❌ VRAM tiene muy pocos bytes no-cero (40/6144 o menos) ❌ Nunca se alcanza el umbral de 200 bytes no-cero ❌ Por lo tanto, nunca se detectan tiles reales

Logs de Decodificación de Tiles y Contenido Detallado del Framebuffer:

❌ No se generaron logs de [PPU-TILE-DECODE-DETAILED] - Los tiles no se están decodificando porque no hay tiles reales detectados ❌ No se generaron logs de [PPU-FRAMEBUFFER-CONTENT-DETAILED] - tiles_were_detected_this_frame nunca es true cuando se llega a VBLANK_START

Hallazgos y Conclusiones

Hallazgos Principales

  1. El código de diagnóstico funciona correctamente: Los logs muestran que el código de diagnóstico se ejecuta y genera información útil sobre el estado del framebuffer y VRAM.
  2. VRAM no se está llenando con tiles: Los logs muestran que VRAM tiene muy pocos bytes no-cero (40/6144 o menos), muy por debajo del umbral de 200 bytes no-cero necesario para detectar tiles reales.
  3. El framebuffer siempre contiene solo checkerboard: Como no se detectan tiles reales, el framebuffer siempre contiene solo el patrón de checkerboard (50% índice 0, 50% índice 3).
  4. La paleta se lee correctamente: Los logs muestran que BGP se lee correctamente desde MMU (0xE4 en mario/tetris, 0x00 en otras ROMs).

Conclusión

El problema NO está en la generación del framebuffer en C++. El código de diagnóstico confirma que:

  • ✅ El código de diagnóstico funciona correctamente
  • ✅ La paleta se lee correctamente
  • ❌ VRAM no se está llenando con tiles (solo 40/6144 bytes no-cero o menos)
  • ❌ Por lo tanto, nunca se detectan tiles reales y el framebuffer siempre contiene solo checkerboard

El problema real es que VRAM no se está llenando con tiles. Esto podría deberse a:

  1. Los tiles no se están cargando desde la ROM a VRAM (problema en la CPU/MMU)
  2. Los tiles se están cargando pero se están limpiando inmediatamente después
  3. El tilemap no apunta a los tiles correctos
  4. El timing de carga de tiles es incorrecto

El siguiente paso debe investigar por qué VRAM no se está llenando con tiles, no la generación del framebuffer.

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Decodificación 2bpp: Los tiles se almacenan en formato 2bpp (2 bits por píxel), donde cada línea del tile (8 píxeles) se almacena en 2 bytes consecutivos. El byte bajo contiene los bits bajos de cada píxel, y el byte alto contiene los bits altos.
  • Aplicación de Paleta: BGP mapea los índices de color decodificados (0-3) a los colores finales de la paleta. Cada 2 bits de BGP corresponden a un índice de color.
  • Generación del Framebuffer: El framebuffer se genera línea por línea durante el renderizado. Cada píxel se decodifica del tile correspondiente, se aplica la paleta, y se escribe el índice final en el framebuffer.
  • Detección de Tiles Reales: Para detectar si hay tiles reales en VRAM, se cuenta el número de bytes no-cero. Si hay más de 200 bytes no-cero, se considera que hay tiles reales.

Lo que Falta Confirmar

  • Por qué VRAM no se está llenando con tiles: Necesito investigar si los tiles se están cargando desde la ROM a VRAM, si se están limpiando inmediatamente después, o si hay un problema con el timing de carga.
  • Si el tilemap apunta a los tiles correctos: Necesito verificar si el tilemap contiene tile IDs válidos que apunten a tiles con datos en VRAM.

Hipótesis y Suposiciones

Hipótesis principal: VRAM no se está llenando con tiles porque los tiles no se están cargando desde la ROM a VRAM, o se están cargando pero se están limpiando inmediatamente después. Esto podría deberse a un problema en la CPU (no ejecuta las instrucciones de carga), en la MMU (no escribe correctamente en VRAM), o en el timing (los tiles se cargan pero se limpian antes de que se rendericen).

Próximos Pasos

  • [ ] Investigar por qué VRAM no se está llenando con tiles
  • [ ] Verificar si los tiles se están cargando desde la ROM a VRAM (logs de escritura en VRAM)
  • [ ] Verificar si los tiles se están limpiando inmediatamente después de cargarse
  • [ ] Verificar si el tilemap apunta a los tiles correctos
  • [ ] Verificar el timing de carga de tiles (cuándo se cargan vs cuándo se renderizan)