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

Análisis de Limpieza de VRAM y Renderizado con Tiles

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

Resumen

Este step implementa análisis detallado para investigar por qué el juego limpia VRAM después de cargar tiles, verificar si el renderizado funciona correctamente cuando hay tiles (antes de limpiar), y analizar por qué TETRIS muestra pantalla blanca. Se mejora la lógica del checkerboard temporal para que solo se active cuando VRAM está completamente vacía, no cuando hay tiles cargados.

Se implementan logs de renderizado cuando hay tiles para verificar si el framebuffer tiene píxeles no-blancos, análisis detallado de limpieza de VRAM que detecta cuándo y por qué se limpia VRAM, análisis de estado del LCD para TETRIS que verifica si el LCD está activo y si hay tiles, y mejora del checkerboard temporal que solo se activa cuando VRAM está completamente vacía.

Concepto de Hardware

Limpieza de VRAM

Los juegos pueden limpiar VRAM durante la inicialización o transiciones de pantalla. Esto puede ocurrir antes o después de cargar tiles. Si se limpia después de cargar tiles, el tilemap puede apuntar a tiles que ya no existen. Algunos juegos cargan tiles, actualizan tilemap, y luego limpian tiles no usados.

Fuente: Pan Docs - "Video RAM (VRAM)": VRAM contiene tanto los datos de tiles (0x8000-0x97FF) como los tilemaps (0x9800-0x9FFF). Los juegos pueden limpiar VRAM para liberar espacio o preparar una nueva pantalla.

Renderizado con Tiles

El renderizado debe funcionar correctamente cuando hay tiles en VRAM. Si el tilemap apunta correctamente a tiles con datos, el framebuffer debería tener píxeles no-blancos. Si el framebuffer está vacío aunque hay tiles, hay un problema con el renderizado.

Fuente: Pan Docs - "Tile Data" y "Tile Map": La PPU lee el tilemap para obtener los tile IDs, luego lee los datos de tiles desde VRAM, y finalmente renderiza los píxeles en el framebuffer.

Checkerboard Temporal

El checkerboard temporal es una ayuda visual para verificar que el pipeline de renderizado funciona. Debe activarse solo cuando VRAM está completamente vacía. No debe activarse cuando hay tiles cargados pero un tile específico está vacío.

Implementación

Tarea 1: Verificación de Renderizado Cuando Hay Tiles

Se agregaron logs en PPU::render_scanline() para verificar si el renderizado funciona correctamente cuando hay tiles en VRAM. Los logs se activan cuando se detectan tiles por primera vez y verifican el framebuffer en la línea 72 (centro de la pantalla).

// Verificar framebuffer cuando hay tiles
if (vram_has_tiles && ly_ == 72) {
    static int render_with_tiles_check_count = 0;
    if (render_with_tiles_check_count < 5) {
        render_with_tiles_check_count++;
        
        size_t line_start = ly_ * SCREEN_WIDTH;
        int non_zero_pixels = 0;
        for (int x = 0; x < SCREEN_WIDTH; x++) {
            uint8_t color_idx = framebuffer_[line_start + x] & 0x03;
            if (color_idx != 0) {
                non_zero_pixels++;
            }
        }
        
        printf("[PPU-RENDER-WITH-TILES] Frame %llu | LY:72 | Píxeles no-blancos: %d/160\n",
               static_cast<unsigned long long>(frame_counter_ + 1), non_zero_pixels);
        
        if (non_zero_pixels == 0) {
            printf("[PPU-RENDER-WITH-TILES] ⚠️ PROBLEMA: Framebuffer vacío aunque hay tiles en VRAM!\n");
        }
    }
}

Tarea 2: Análisis Detallado de Limpieza de VRAM

Se mejoró el análisis de limpieza de VRAM en MMU::write() para detectar cuándo y por qué se limpia VRAM. El análisis detecta cuando se escribe 0x00 en VRAM después de cargar tiles, verifica si se está limpiando desde el inicio de VRAM, y verifica el estado del tilemap después de limpiar.

// Cuando se escribe 0x00 en VRAM después de cargar tiles
if (addr >= 0x8000 && addr <= 0x97FF && value == 0x00 && tiles_were_loaded_recently_global) {
    static int vram_clean_detailed_count = 0;
    if (vram_clean_detailed_count < 20) {
        vram_clean_detailed_count++;
        
        // Verificar si se está limpiando desde el inicio de VRAM
        if (addr == 0x8000) {
            printf("[VRAM-CLEAN-DETAILED] ⚠️ INICIO DE LIMPIEZA: PC:0x%04X | Banco ROM: %d | Tiles cargados antes: %d\n",
                   debug_current_pc, get_current_rom_bank(), tiles_loaded_count);
        }
        
        // Verificar si se está limpiando cerca del final de VRAM
        if (addr >= 0x97F0) {
            printf("[VRAM-CLEAN-DETAILED] ⚠️ FIN DE LIMPIEZA: PC:0x%04X | VRAM completamente limpiada\n",
                   debug_current_pc);
            
            // Verificar estado del tilemap después de limpiar
            int tilemap_non_zero = 0;
            for (int i = 0; i < 32; i++) {
                if (memory_[0x9800 + i] != 0x00) {
                    tilemap_non_zero++;
                }
            }
            printf("[VRAM-CLEAN-DETAILED] Tilemap después de limpiar: %d/32 tile IDs no-cero\n",
                   tilemap_non_zero);
        }
    }
}

Tarea 3: Análisis de Estado del LCD para TETRIS

Se agregó análisis de estado del LCD en PPU::render_scanline() para verificar por qué TETRIS muestra pantalla blanca. El análisis verifica el estado del LCD, BG Display, VRAM y tilemap.

// Verificar estado del LCD durante la ejecución
static int lcd_state_analysis_count = 0;
if (ly_ == 0 && lcd_state_analysis_count < 10) {
    lcd_state_analysis_count++;
    
    uint8_t lcdc_state = mmu_->read(IO_LCDC);
    bool lcd_on = (lcdc_state & 0x80) != 0;
    bool bg_display = (lcdc_state & 0x01) != 0;
    
    // Verificar estado de VRAM
    int vram_non_zero = 0;
    for (uint16_t i = 0; i < 6144; i++) {
        if (mmu_->read(0x8000 + i) != 0x00) {
            vram_non_zero++;
        }
    }
    
    // Verificar tilemap
    int tilemap_non_zero = 0;
    for (int i = 0; i < 32; i++) {
        if (mmu_->read(0x9800 + i) != 0x00) {
            tilemap_non_zero++;
        }
    }
    
    printf("[PPU-LCD-STATE] Frame %llu | LCD: %s | BG Display: %s | VRAM: %d/6144 | Tilemap: %d/32\n",
           static_cast<unsigned long long>(frame_counter_ + 1),
           lcd_on ? "ON" : "OFF",
           bg_display ? "ON" : "OFF",
           vram_non_zero, tilemap_non_zero);
}

Tarea 4: Mejora del Checkerboard Temporal

Se mejoró la lógica del checkerboard temporal para que solo se active cuando VRAM está completamente vacía (menos de 200 bytes no-cero). Esto evita activar el checkerboard cuando el juego está cargando tiles.

if (tile_is_empty && enable_checkerboard_temporal) {
    // Verificar si VRAM está completamente vacía
    int vram_non_zero = 0;
    for (uint16_t i = 0; i < 6144; i++) {
        if (mmu_->read(0x8000 + i) != 0x00) {
            vram_non_zero++;
        }
    }
    
    // Solo activar checkerboard si VRAM está completamente vacía
    if (vram_non_zero < 200) {
        // Generar patrón de checkerboard temporal
        // ... (código existente del checkerboard)
    } else {
        // VRAM tiene datos, pero este tile específico está vacío
        // Renderizar como tile vacío (blanco) sin checkerboard
        byte1 = 0x00;
        byte2 = 0x00;
    }
}

Archivos Afectados

  • src/core/cpp/PPU.cpp - Agregados logs de renderizado cuando hay tiles, análisis de estado del LCD, y mejora del checkerboard temporal
  • src/core/cpp/MMU.cpp - Mejorado análisis detallado de limpieza de VRAM

Tests y Verificación

Se ejecutaron pruebas con las 5 ROMs (Pokémon Red, TETRIS, Mario, Pokémon Amarillo, Oro) durante 2.5 minutos cada una. Los logs muestran:

  • Análisis de estado del LCD: Los logs [PPU-LCD-STATE] muestran el estado del LCD, BG Display, VRAM y tilemap para cada ROM
  • Análisis de limpieza de VRAM: Los logs [VRAM-CLEAN-DETAILED] muestran cuándo y por qué se limpia VRAM
  • Renderizado con tiles: Los logs [PPU-RENDER-WITH-TILES] muestran si el framebuffer tiene píxeles no-blancos cuando hay tiles

Validación de módulo compilado C++: El módulo se recompiló exitosamente sin errores (solo warnings menores de formato).

Fuentes Consultadas

Integridad Educativa

Lo que Entiendo Ahora

  • Limpieza de VRAM: Los juegos pueden limpiar VRAM después de cargar tiles, lo que puede causar que el tilemap apunte a tiles que ya no existen. Esto explica por qué se ve el checkerboard temporal (rayas) después de que se cargan tiles.
  • Renderizado con tiles: El renderizado debe funcionar correctamente cuando hay tiles en VRAM. Si el framebuffer está vacío aunque hay tiles, hay un problema con el renderizado.
  • Checkerboard temporal: El checkerboard temporal debe activarse solo cuando VRAM está completamente vacía, no cuando hay tiles cargados pero un tile específico está vacío.

Lo que Falta Confirmar

  • Por qué se limpia VRAM: Necesitamos analizar los logs para entender por qué el juego limpia VRAM después de cargar tiles. ¿Es parte de la inicialización? ¿Es para liberar espacio?
  • Renderizado cuando hay tiles: Necesitamos verificar si el renderizado funciona correctamente cuando hay tiles (antes de limpiar). Los logs [PPU-RENDER-WITH-TILES] deberían mostrar si el framebuffer tiene píxeles no-blancos.
  • TETRIS pantalla blanca: Necesitamos analizar los logs para entender por qué TETRIS muestra pantalla blanca. ¿El LCD está activo? ¿Hay tiles? ¿El BG Display está desactivado?

Hipótesis y Suposiciones

Hipótesis 1: El juego limpia VRAM después de cargar tiles como parte de la inicialización. Esto podría ser para liberar espacio o preparar una nueva pantalla.

Hipótesis 2: El renderizado funciona correctamente cuando hay tiles, pero el checkerboard temporal se activa cuando VRAM se limpia, causando las rayas.

Hipótesis 3: TETRIS muestra pantalla blanca porque el BG Display está desactivado o porque no hay tiles cargados.

Próximos Pasos

  • [ ] Analizar los logs de las 5 ROMs para identificar patrones de limpieza de VRAM
  • [ ] Verificar si el renderizado funciona correctamente cuando hay tiles (antes de limpiar)
  • [ ] Investigar por qué TETRIS muestra pantalla blanca (análisis de logs de estado del LCD)
  • [ ] Decidir si desactivar el checkerboard temporal o cambiar la lógica de renderizado
  • [ ] Si se identifica la causa del problema, implementar solución en Step 0329