⚠️ 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 Por Qué Todos los Tiles Están Vacíos Durante el Renderizado

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

Resumen

Se implementaron verificaciones detalladas para investigar por qué todos los tiles leídos de VRAM están vacíos (0x00) durante el renderizado. Se agregaron logs de diagnóstico en múltiples puntos del pipeline de renderizado: verificación de VRAM durante el renderizado (no solo en LY=0), logs de qué tiles se leen del tilemap y su contenido, verificación detallada de tiles vacíos, y logs de timing de carga de tiles vs renderizado. Los logs confirman que VRAM está completamente vacía cuando se renderiza (0/6144 bytes no-cero), todos los tiles están vacíos (0/20 tiles con datos), y el tilemap apunta a tiles vacíos (todos los tile IDs son 0x00). La causa raíz identificada es que VRAM está vacía cuando se renderiza, lo que indica que los tiles no se han cargado todavía o el juego aún no los está cargando.

Concepto de Hardware

Timing de Carga de Tiles en Game Boy

En la Game Boy real, los tiles se cargan en VRAM durante V-Blank (cuando el LCD está apagado o durante las líneas 144-153). Sin embargo, algunos juegos cargan tiles durante H-Blank o incluso durante el renderizado activo.

Problema de timing en emuladores:

  • Si el emulador renderiza antes de que los tiles se carguen, verá tiles vacíos
  • Si vram_is_empty_ se actualiza solo una vez por frame (en LY=0), puede estar desactualizado durante el renderizado
  • El checkerboard temporal se activa cuando todos los tiles están vacíos, pero si los tiles se cargan después, el checkerboard seguirá activo

Verificación de Tiles Vacíos

La verificación de tiles vacíos debe ser precisa:

  • Un tile está vacío si todas sus 8 líneas (16 bytes) son 0x00
  • Algunos tiles legítimos pueden tener líneas con 0x00, pero no todas
  • Si la verificación es incorrecta, puede marcar tiles con datos como vacíos

Checkerboard Temporal

El checkerboard temporal es una ayuda visual que se activa cuando:

  1. El tile está completamente vacío (tile_is_empty == true)
  2. VRAM está completamente vacía (vram_is_empty_ == true)
  3. El checkerboard está habilitado (enable_checkerboard_temporal == true)

Si cualquiera de estas condiciones no se cumple, el renderizado normal debería funcionar.

Implementación

Se implementaron 4 tipos de verificaciones para investigar el problema de tiles vacíos:

1. Verificación de VRAM Durante el Renderizado

Se agregó verificación de VRAM no solo en LY=0, sino también durante el renderizado (LY=0, LY=72, LY=143) para detectar si VRAM se carga después de que se actualiza vram_is_empty_.

// Verificar VRAM en este momento
int vram_non_zero = 0;
for (uint16_t i = 0; i < 6144; i++) {
    if (mmu_->read(0x8000 + i) != 0x00) {
        vram_non_zero++;
    }
}

printf("[PPU-VRAM-DURING-RENDER] Frame %llu | LY: %d | Mode: %d | "
       "VRAM non-zero: %d/6144 (%.2f%%) | vram_is_empty_: %s\n",
       frame_counter_ + 1, ly_, mode_,
       vram_non_zero, (vram_non_zero * 100.0) / 6144,
       vram_is_empty_ ? "YES" : "NO");

2. Verificación de Qué Tiles se Leen del Tilemap

Se agregaron logs detallados de qué tiles se leen del tilemap y su contenido, verificando si los tiles tienen datos o están vacíos.

// Leer tile ID del tilemap
uint8_t tile_id = mmu_->read(tilemap_addr);

// Calcular dirección del tile
uint16_t tile_addr;
if (unsigned_addressing) {
    tile_addr = data_base + (tile_id * 16);
} else {
    int8_t signed_tile_id = static_cast(tile_id);
    tile_addr = data_base + ((signed_tile_id + 128) * 16);
}

// Verificar contenido del tile
uint8_t tile_byte1 = mmu_->read(tile_addr);
uint8_t tile_byte2 = mmu_->read(tile_addr + 1);
bool tile_has_data = (tile_byte1 != 0x00 || tile_byte2 != 0x00);

printf("[PPU-TILEMAP-READ] Frame %llu | LY: %d | X: %d | "
       "Tilemap[0x%04X]=0x%02X | TileAddr=0x%04X | "
       "Byte1=0x%02X Byte2=0x%02X | HasData=%s\n",
       frame_counter_ + 1, ly_, x,
       tilemap_addr, tile_id, tile_addr,
       tile_byte1, tile_byte2, tile_has_data ? "YES" : "NO");

3. Verificación Detallada de Tiles Vacíos

Se agregaron logs detallados de la verificación de tiles vacíos, mostrando cuántas líneas tienen datos y cuántas están vacías, y por qué el checkerboard se activa o no.

// Verificar cada línea del tile
int lines_with_data = 0;
int lines_empty = 0;
for (uint8_t line_check = 0; line_check < 8; line_check++) {
    uint16_t check_addr = tile_addr + (line_check * 2);
    uint8_t check_byte1 = mmu_->read(check_addr);
    uint8_t check_byte2 = mmu_->read(check_addr + 1);
    
    if (check_byte1 != 0x00 || check_byte2 != 0x00) {
        lines_with_data++;
    } else {
        lines_empty++;
    }
}

bool tile_is_empty_result = (lines_with_data == 0);

printf("[PPU-TILE-EMPTY-CHECK] Frame %llu | LY: %d | X: %d | "
       "TileAddr=0x%04X | LinesWithData=%d LinesEmpty=%d | "
       "tile_is_empty=%s | vram_is_empty_=%s | enable_checkerboard=%s\n",
       frame_counter_ + 1, ly_, x,
       tile_addr, lines_with_data, lines_empty,
       tile_is_empty_result ? "YES" : "NO",
       vram_is_empty_ ? "YES" : "NO",
       enable_checkerboard_temporal ? "YES" : "NO");

4. Verificación de Tiles Disponibles y Timing

Se agregó verificación de tiles disponibles al inicio de render_scanline() y logs de timing de escritura a VRAM en MMU.cpp.

// Verificar primeros 20 tiles del tilemap
for (int i = 0; i < 20; i++) {
    uint8_t tile_id = mmu_->read(map_base + i);
    uint16_t tile_addr;
    // ... calcular dirección del tile ...
    
    // Verificar si el tile tiene datos
    bool has_data = false;
    for (int j = 0; j < 16; j++) {
        if (mmu_->read(tile_addr + j) != 0x00) {
            has_data = true;
            break;
        }
    }
    
    if (has_data) tiles_with_data++;
    else tiles_empty++;
}

printf("[PPU-TILES-AVAILABLE] Frame %llu | LY: %d | "
       "Tiles with data: %d/20 | Tiles empty: %d/20\n",
       frame_counter_ + 1, ly_,
       tiles_with_data, tiles_empty);

Logs de Timing en MMU.cpp

Se agregaron logs de timing cuando se escriben tiles no-cero a VRAM, mostrando el frame, LY y modo cuando ocurre la escritura.

// En MMU.cpp, cuando se escribe a VRAM
if (addr >= 0x8000 && addr <= 0x97FF && value != 0x00) {
    uint64_t frame_counter_from_ppu = 0;
    uint8_t ly_from_ppu = 0;
    uint8_t mode_from_ppu = 0;
    
    if (ppu_ != nullptr) {
        frame_counter_from_ppu = ppu_->get_frame_counter();
        ly_from_ppu = ppu_->get_ly();
        mode_from_ppu = ppu_->get_mode();
    }
    
    printf("[MMU-VRAM-WRITE-TIMING] PC:0x%04X | Addr:0x%04X | Value:0x%02X | "
           "Frame: %llu | LY: %d | Mode: %d\n",
           debug_current_pc, addr, value,
           frame_counter_from_ppu, ly_from_ppu, mode_from_ppu);
}

Archivos Afectados

  • src/core/cpp/PPU.cpp - Agregadas verificaciones de VRAM durante renderizado, logs de tiles leídos del tilemap, verificación detallada de tiles vacíos, y verificación de tiles disponibles
  • src/core/cpp/MMU.cpp - Agregados logs de timing de escritura a VRAM

Tests y Verificación

Se ejecutaron pruebas con ROMs de test (Tetris, Mario, Oro) para generar logs de diagnóstico. Los logs confirman el problema identificado:

  • VRAM está completamente vacía: VRAM non-zero: 0/6144 (0.00%) tanto en LY=0 como en LY=72
  • Todos los tiles están vacíos: Tiles with data: 0/20 | Tiles empty: 20/20
  • El tilemap apunta a tiles vacíos: Todos los tile IDs son 0x00, que apuntan al tile 0 (que está vacío)
  • El checkerboard se activa correctamente: Checkerboard se activará para este tile

Ejemplo de Logs Generados

[PPU-VRAM-DURING-RENDER] Frame 1 | LY: 0 | Mode: 2 | VRAM non-zero: 0/6144 (0.00%) | vram_is_empty_: YES
[PPU-TILES-AVAILABLE] Frame 1 | LY: 0 | Tiles with data: 0/20 | Tiles empty: 20/20
[PPU-TILES-AVAILABLE] ⚠️ PROBLEMA: Todos los tiles están vacíos, checkerboard se activará
[PPU-TILEMAP-READ] Frame 1 | LY: 0 | X: 0 | Tilemap[0x9800]=0x00 | TileAddr=0x8000 | Byte1=0x00 Byte2=0x00 | HasData=NO
[PPU-TILEMAP-READ] ⚠️ Tile vacío detectado - activará checkerboard si vram_is_empty_=true
[PPU-TILE-EMPTY-CHECK] Frame 1 | LY: 0 | X: 0 | TileAddr=0x8000 | LinesWithData=0 LinesEmpty=8 | tile_is_empty=YES | vram_is_empty_=YES | enable_checkerboard=YES
[PPU-TILE-EMPTY-CHECK] ✅ Checkerboard se activará para este tile

Validación de módulo compilado C++: El código se compiló exitosamente y los logs se generan correctamente durante la ejecución.

Hallazgos Clave

Hallazgos Iniciales (Ejecución Corta)

En la ejecución inicial (30-60 segundos), los logs confirmaron:

  • VRAM está completamente vacía: 0/6144 bytes no-cero durante el renderizado (Frame 1-5)
  • Todos los tiles vacíos inicialmente: 0/20 tiles con datos en los primeros frames
  • El tilemap apunta a tiles vacíos: Todos los tile IDs son 0x00 en los primeros frames
  • El checkerboard se activa correctamente: Se activa cuando detecta tiles vacíos

Hallazgos de Ejecución Extendida (2.5 minutos)

Después de ejecutar todas las ROMs durante 2.5 minutos, se descubrieron hallazgos importantes:

  1. Los tiles SÍ se cargan después de algunos frames:
    • Después del Frame 5-6, los logs muestran: Tiles with data: 20/20 | Tiles empty: 0/20
    • Esto confirma que los tiles se cargan eventualmente, pero no en el primer frame
  2. Hay escrituras a VRAM durante V-Blank:
    • Los logs de [MMU-VRAM-WRITE-TIMING] muestran escrituras durante V-Blank (Mode: 1, LY: 145-149)
    • Ejemplo: Frame: 4720 | LY: 145 | Mode: 1 | Addr:0x8800 | Value:0xFF
    • Esto es correcto según el hardware: los tiles se cargan durante V-Blank
  3. vram_is_empty_ cambia eventualmente:
    • En algunos juegos (Oro.gbc, PKMN Amarillo, PKMN), vram_is_empty_ cambia de YES a NO después de varios miles de frames
    • Ejemplo: Frame 4723 | vram_is_empty_ cambió: YES -> NO (Oro.gbc)
    • Ejemplo: Frame 4719 | vram_is_empty_ cambió: YES -> NO (PKMN Amarillo)
    • Ejemplo: Frame 4946 | vram_is_empty_ cambió: YES -> NO (PKMN)
  4. Discrepancia en verificación de VRAM:
    • Los logs muestran que los tiles tienen datos (Tiles with data: 20/20) pero la verificación de VRAM muestra VRAM non-zero: 0/6144
    • Esto sugiere que puede haber un problema con la verificación de VRAM o que los tiles están en una parte diferente de VRAM
    • O que la verificación de VRAM se ejecuta en un momento diferente al de la verificación de tiles

Causa Raíz Identificada

Los tiles se cargan después del primer frame, pero hay un problema de timing o de verificación.

  • Los tiles se cargan durante V-Blank (correcto según el hardware)
  • Los tiles tienen datos después del Frame 5-6 (confirmado por logs)
  • Pero la verificación de VRAM muestra 0/6144 incluso cuando los tiles tienen datos (discrepancia)
  • El problema puede ser que vram_is_empty_ se actualiza solo en LY=0, y si los tiles se cargan después, seguirá siendo true durante todo el frame

Fuentes Consultadas

  • Pan Docs: "Tile Data", "Tile Map", "VRAM (Video RAM)"
  • Pan Docs: "LCD Timing", "Background", "Window"

Integridad Educativa

Lo que Entiendo Ahora

  • Timing de carga de tiles: Los tiles se cargan en VRAM durante V-Blank o H-Blank, pero el renderizado puede ocurrir antes de que se carguen
  • Verificación de tiles vacíos: Un tile está vacío si todas sus 8 líneas (16 bytes) son 0x00
  • Checkerboard temporal: Se activa cuando el tile está vacío, VRAM está vacía, y el checkerboard está habilitado
  • Problema de timing: Si VRAM está vacía cuando se renderiza, todos los tiles aparecerán vacíos y el checkerboard se activará

Lo que Falta Confirmar

  • Carga de tiles después del primer frame: Si el juego carga tiles después de algunos frames, necesitamos ejecutar el emulador por más tiempo
  • Inicialización de VRAM: Si hay un problema con la inicialización de VRAM o con la carga de tiles desde la ROM
  • Timing de actualización de vram_is_empty_: Si necesita actualizarse más frecuentemente o en diferentes momentos

Hipótesis y Suposiciones

Asumimos que el problema es de timing (tiles se cargan después del renderizado), pero necesitamos más evidencia ejecutando el emulador por más tiempo para ver si los tiles se cargan eventualmente.

Próximos Pasos

Ejecución Extendida Completada

✅ Se ejecutaron todas las ROMs durante 2.5 minutos. Los hallazgos confirman que:

  • ✅ Los tiles SÍ se cargan después de algunos frames (Frame 5-6)
  • ✅ Hay escrituras a VRAM durante V-Blank (correcto según el hardware)
  • vram_is_empty_ cambia eventualmente en algunos juegos

Próximos Pasos Sugeridos

  • [ ] Investigar la discrepancia entre la verificación de VRAM (0/6144) y la verificación de tiles (20/20 con datos)
  • [ ] Verificar si el problema es que vram_is_empty_ se actualiza solo en LY=0, y si los tiles se cargan después, seguirá siendo true durante todo el frame
  • [ ] Considerar actualizar vram_is_empty_ más frecuentemente o en diferentes momentos (no solo en LY=0)
  • [ ] Verificar si la verificación de VRAM está verificando el rango correcto (0x8000-0x97FF) o si hay un problema con cómo se lee VRAM
  • [ ] Si los tiles tienen datos pero VRAM muestra 0/6144, investigar si los tiles están en una parte diferente de VRAM o si hay un problema con la verificación