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

Diffuse Tiles Research and Rendering Optimization

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

Summary

This step investigates why tiles appear fuzzy or half-baked and why it takes a long time. to appear content from the ROM. Diagnostic logs were implemented to verify the tile decoding (2bpp), the application of the BGP palette, the scroll (SCX/SCY), the signed/unsigned addressing, and tile loading timing. The logs revealed that Palette decoding and application work correctly, but tiles load very late (Frame 4720-4943), which explains why it takes a long time for content to appear.

Hardware Concept

2bpp decoding (2 bits per pixel)

Each Game Boy tile has 8x8 pixels = 64 pixels. Each pixel is encoded with 2 bits (2bpp = 2 bits per pixel), so 16 bytes per tile are needed (8 lines × 2 bytes per line). The format is:

  • Byte 1: Contains the low bit (bit_low) of each pixel
  • Byte 2: Contains the high bit (bit_high) of each pixel
  • Color index: color_index = (bit_high<< 1) | bit_low

The resulting color index (0-3) is mapped to a final color using the BGP palette.

BGP Palette (Background Palette)

BGP (Background Palette, register 0xFF47) maps raw color indexes (0-3) to indexes finals (0-3). The format is:[color3][color2][color1][color0], where each color occupies 2 bits. The final color is calculated as:

final_color = (BGP >> (color_index * 2)) & 0x03

For example, with BGP = 0xE4(11100100):

  • Color 0 → (0xE4 >> 0) & 0x03 = 0
  • Color 1 → (0xE4 >> 2) & 0x03 = 1
  • Color 2 → (0xE4 >> 4) & 0x03 = 2
  • Color 3 → (0xE4 >> 6) & 0x03 = 3

This is a standard identity mapping that preserves color indices.

Scroll (SCX/SCY)

SCX (Scroll register 0xFF42) shifts the background vertically. Scroll is applied when calculating the position on the tilemap:

map_x = (screen_x + SCX) & 0xFF
map_y = (screen_y + SCY) & 0xFF

Signed/Unsigned Addressing

The Game Boy can use two addressing modes for tiles:

  • Unsigned (LCDC bit 4 = 1): Tile IDs 0-255, base on 0x8000
  • Signed (LCDC bit 4 = 0): Tile IDs -128 to 127, base on 0x9000

In signed mode, the tile ID is interpreted as int8_t, allowing tiles to be referenced before of the base (0x9000 - 128*16 = 0x8800).

Implementation

5 diagnostic log systems were implemented to investigate the problems of rendering:

1. Tiles Decoding Logs (2bpp)

Logs were added inPPU::render_scanline()that verify the decoding of tiles on the center line (LY=72). The logs show:

  • Tile ID and tile address
  • Bytes 1 and 2 of the tile line
  • Manual decoding of some pixels (first and last pixel)
  • Resulting color index for each pixel

Log tag: [PPU-TILE-DECODE]

2. Palette Application Logs

Added logs that verify the application of the BGP palette. The logs show:

  • Raw color index (0-3)
  • BGP value
  • Final color after applying the palette
  • BGP mapping (color_index → ​​final_color)

Log tag: [PPU-PALETTE-APPLY]

3. Tiles Load Timing Analysis

Analysis added inMMU::write()that detects when the tiles are loaded and analyzes the loading speed. The logs show:

  • Frame in which the first tile is loaded
  • Number of tiles loaded per second
  • Total tiles loaded

Log tag: [TILE-LOAD-TIMING]

4. Scroll Logs (SCX/SCY)

Added logs that check scroll values ​​during rendering. The logs show:

  • Current frame
  • Scan line (LY)
  • SCX and SCY Values
  • Calculated position in the tilemap (MapX, MapY)

Log tag: [PPU-SCROLL]

5. Signed/Unsigned Addressing Logs

Added logs that verify the addressing of tiles. The logs show:

  • Tile ID
  • Addressing type (signed/unsigned)
  • Tiles database
  • Calculated tile address
  • If signed, the signed value of the tile ID

Log tag: [PPU-ADDRESSING]

Affected Files

  • src/core/cpp/PPU.cpp- Added decoding, palette, scroll and addressing logs
  • src/core/cpp/MMU.cpp- Added tile loading timing analysis
  • build_log_step0336.txt- Compilation log
  • logs/test_*_step0336.log- Test logs with the 5 ROMs

Tests and Verification

Tests were run with the 5 ROMs for 2.5 minutes each:

  • pkmn.gb: 20 tiles loaded, first tile in Frame 4943
  • tetris.gb: 3 tiles loaded, first tile in Frame 1
  • mario.gbc: 0 tiles loaded (checkerboard only)
  • pkmn-amarillo.gb: 20 tiles loaded, first tile on Frame 4716
  • Gold.gbc: 20 tiles loaded, first tile on Frame 4720

Key Findings

  1. Tiles decoding works correctly: The logs show that 2bpp decoding is correct. Tiles with data (0xFF 0xFF) are decoded correctly to color_idx=3, and empty tiles (0x00 0x00) to color_idx=0.
  2. Palette app works correctly: The logs show that the BGP palette is applied correctly, mapping raw color indices to indices finals (0→0, 3→3 with BGP=0xE4).
  3. Tiles load very late: Tiles are loaded in Frame 4720-4943 (approximately 78-82 seconds after startup), which explains why it takes much in appearing content from the ROM. This is normal for some games that Many systems initialize before loading graphics.
  4. Scroll works correctly: The logs show that SCX and SCY are They apply correctly, although initially they are at 0.
  5. Addressing works correctly: The logs show that both signed and unsigned addressing work correctly. For example, in Oro.gbc is detected addressing signed with TileID 0x7F mapping to 0x97F0.

Log Example

[PPU-TILE-DECODE] Frame 1 | TileID: 0x00 | Addr: 0x8000 | Byte1: 0xFF Byte2: 0xFF
[PPU-TILE-DECODE] Pixel 7: bit_low=1 bit_high=1 color_idx=3
[PPU-PALETTE-APPLY] Frame 1 | ColorIndex: 3 | BGP: 0xE4 | FinalColor: 3 | BGP mapping: 3->3
[TILE-LOAD-TIMING] First tile loaded on Frame 4720 (PC:0x5EB2)
[PPU-SCROLL] Frame 1 | LY: 72 | SCX: 0 | SCY: 0 | MapX: 0 | MapY: 72
[PPU-ADDRESSING] Frame 1 | TileID: 0x7F | Signed: 1 | Database: 0x9000 | TileAddr: 0x97F0

Sources consulted

Educational Integrity

What I Understand Now

  • 2bpp decoding: Tiles decoding works correctly. The bits are read in the correct order (bit_high and bit_low) and combined to form the color index (0-3).
  • Palette application: The BGP palette is applied correctly using the bit shift. Identity mapping (BGP=0xE4) preserves color indices.
  • Load timing: Tiles load very late in some games (Frame 4720-4943), which is normal for games that initialize many systems before loading graphics. This explains why it takes a long time for content to appear.
  • Addressing: Both signed and unsigned addressing they work correctly. The signed mode allows you to reference tiles before the base (0x8800-0x8FFF) using negative values.

What remains to be confirmed

  • Fuzzy tiles: The logs show that the decoding and palette They work correctly, but the tiles still appear blurry. This could be due to:
    • Issues in Python renderer converting color indices to RGB
    • Problems applying final colors (Game Boy Color palette)
    • Problems with pixel interpolation or scaling
  • Load optimization: Although the tiles load late, this is normal for some games. However, it could be investigated whether there are ways of optimize charging or if there are synchronization problems.

Hypotheses and Assumptions

Logs confirm that palette decoding and application work correctly on the C++ side. The fuzzy tiles problem is probably in the Python renderer which converts color indices to RGB or in the application of final colors. The next step you should investigate the Python renderer.

Next Steps

  • [ ] Investigate the Python renderer that converts color indices to RGB
  • [ ] Check Final Color Application (Game Boy Color Palette)
  • [ ] Investigate interpolation or pixel scaling issues
  • [ ] If the cause of the fuzzy tiles is identified, implement correction