⚠️ Clean-Room / Educational

This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.

Analysis of VRAM Cleaning and Rendering with Tiles

Date:2025-12-29 StepID:0328 State: VERIFIED

Summary

This step implements detailed analysis to investigate why the game clears VRAM after loading tiles, check if rendering works correctly when there are tiles (before cleaning), and analyze why TETRIS displays a white screen. Improved the logic of the temporary checkerboard so that it only activates when VRAM is completely empty, not when there are loaded tiles.

Implemented rendering logs when there are tiles to check if the framebuffer has non-white pixels, detailed VRAM cleaning analysis that detects when and why VRAM is cleaned, LCD state analysis for TETRIS that checks if the LCD is active and if there are tiles, and improved temporary checkerboard that only activates when VRAM is completely empty.

Hardware Concept

VRAM Cleaning

Games may clear VRAM during initialization or screen transitions. This can occur before or after loading tiles. If cleared after loading tiles, the tilemap may point to tiles that no longer exist. Some games load tiles, update tilemap, and then clear unused tiles.

Fountain:Pan Docs - "Video RAM (VRAM)": VRAM contains both tile data (0x8000-0x97FF) and tilemaps (0x9800-0x9FFF). Games can clean VRAM to free up space or prepare a new display.

Rendering with Tiles

Rendering should work correctly when there are tiles in VRAM. If the tilemap correctly points to tiles with data, the framebuffer should have non-white pixels. If the framebuffer is empty even though there are tiles, there is a problem with the rendering.

Fountain:Pan Docs - "Tile Data" and "Tile Map": The PPU reads the tilemap to obtain the tile IDs, then reads the tile data from VRAM, and finally renders the pixels in the framebuffer.

Temporary Checkerboard

The temporary checkerboard is a visual aid to verify that the rendering pipeline is working. It should be activated only when VRAM is completely empty. It should not be activated when there are tiles loaded but a specific tile is empty.

Implementation

Task 1: Render Verification When Tiles Are Present

Logs were added inPPU::render_scanline()to check if rendering works correctly when there are tiles in VRAM. The logs are activated when tiles are detected for the first time and check the framebuffer on line 72 (center of the screen).

// Check framebuffer when there are 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");
        }
    }
}

Task 2: Detailed Analysis of VRAM Cleaning

Improved VRAM cleaning analysis inMMU::write()to detect when and why VRAM is cleaned. The analysis detects when 0x00 is written to VRAM after loading tiles, checks if it is being cleaned from the start of VRAM, and checks the state of the tilemap after cleaning.

// When 0x00 is written to VRAM after loading 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] ⚠️ CLEAN END: PC:0x%04X | VRAM completely cleaned\n",
                   debug_current_pc);
            
            // Check tilemap status after cleaning
            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);
        }
    }
}

Task 3: LCD Status Analysis for TETRIS

Added LCD status analysis inPPU::render_scanline()to check why TETRIS shows white screen. The analysis checks the status of the LCD, BG Display, VRAM and tilemap.

// Check LCD status during execution
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;
    
    // Check VRAM status
    int vram_non_zero = 0;
    for (uint16_t i = 0; i< 6144; i++) {
        if (mmu_->read(0x8000 + i) != 0x00) {
            vram_non_zero++;
        }
    }
    
    // Check 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);
}

Task 4: Improving the Temporary Checkerboard

Improved the temporary checkerboard logic so that it only activates when VRAM is completely empty (less than 200 non-zero bytes). This avoids activating the checkerboard when the game is loading tiles.

if (tile_is_empty && enable_checkerboard_temporary) {
    // Check if VRAM is completely empty
    int vram_non_zero = 0;
    for (uint16_t i = 0; i< 6144; i++) {
        if (mmu_->read(0x8000 + i) != 0x00) {
            vram_non_zero++;
        }
    }
    
    // Only activate checkerboard if VRAM is completely empty
    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;
    }
}

Affected Files

  • src/core/cpp/PPU.cpp- Added rendering logs when there are tiles, LCD status analysis, and improved temporary checkerboard
  • src/core/cpp/MMU.cpp- Improved detailed VRAM cleaning analysis

Tests and Verification

Tests were run with the 5 ROMs (Pokémon Red, TETRIS, Mario, Pokémon Yellow, Gold) for 2.5 minutes each. The logs show:

  • LCD status analysis:The logs[PPU-LCD-STATE]show LCD status, BG Display, VRAM and tilemap for each ROM
  • VRAM Cleaning Analysis:The logs[VRAM-CLEAN-DETAILED]show when and why VRAM is cleaned
  • Rendering with tiles:The logs[PPU-RENDER-WITH-TILES]show if the framebuffer has non-white pixels when there are tiles

Compiled C++ module validation:The module was successfully recompiled without errors (only minor formatting warnings).

Sources consulted

Educational Integrity

What I Understand Now

  • VRAM Cleaning:Games can clear VRAM after loading tiles, which can cause the tilemap to point to tiles that no longer exist. This explains why you see the temporary checkerboard (stripes) after tiles are loaded.
  • Rendering with tiles:Rendering should work correctly when there are tiles in VRAM. If the framebuffer is empty even though there are tiles, there is a problem with the rendering.
  • Temporary Checkerboard:The temporary checkerboard should be activated only when VRAM is completely empty, not when there are tiles loaded but a specific tile is empty.

What remains to be confirmed

  • Why VRAM is cleaned:We need to analyze the logs to understand why the game cleans VRAM after loading tiles. Is it part of the initialization? Is it to free up space?
  • Rendering when there are tiles:We need to check if the rendering works correctly when there are tiles (before cleaning). The logs[PPU-RENDER-WITH-TILES]They should show if the framebuffer has non-white pixels.
  • TETRIS white screen:We need to analyze the logs to understand why TETRIS shows a white screen. Is the LCD active? Are there tiles? Is the BG Display disabled?

Hypotheses and Assumptions

Hypothesis 1:The game clears VRAM after loading tiles as part of initialization. This could be to free up space or prepare a new screen.

Hypothesis 2:Rendering works correctly when tiles are present, but the temporary checkerboard activates when VRAM is cleared, causing the streaks.

Hypothesis 3:TETRIS shows a white screen because the BG Display is disabled or because there are no tiles loaded.

Next Steps

  • [ ] Analyze the logs of the 5 ROMs to identify VRAM cleaning patterns
  • [ ] Check if rendering works correctly when there are tiles (before cleaning)
  • [ ] Investigate why TETRIS shows a white screen (analysis of LCD status logs)
  • [ ] Decide whether to disable the temporary checkerboard or change the rendering logic
  • [ ] If the cause of the problem is identified, implement solution in Step 0329