This project is educational and Open Source. No code is copied from other emulators. Implementation based solely on technical documentation and permitted tests.
Step 0459: Instrumentation Pipeline Shade→RGB Conversion
Summary
Aim:Instrument the idx→shade→rgb conversion pipeline to identify where the "RGB only 2 unique colors instead of ≥3" bug crashes. Step 0458 confirmed that the indices are written correctly (0, 1, 2, 3), so the remaining bug must be in the palette conversion or the shade→RGB conversion.
Critical finding:✅ The conversion pipeline workscorrectly. The instrumentation shows that when there are 4 different indices (0, 1, 2, 3), the pipeline produces 4 unique colors: (255,255,255), (170,170,170), (85,85,85), (0,0,0). The problem is NOT in the conversion pipeline.
Real problem identified:The index framebuffer only has variety in the first pixels (where the rendered tile is). The rest of the framebuffer has only indices 0 and 2, which produces only 2 unique colors. This is a problem ofrendering, not conversion.
Result:✅ Conversion pipeline validated and working correctly. No fix is required in the pipeline. The problem is in the rendering (outside the scope of this Step).
Hardware Concept
DMG Conversion Pipeline: Index → Shade → RGB
In DMG (Classic Game Boy) mode, the color conversion pipeline works in 3 stages:
- Color index (0-3):The framebuffer contains raw indices (0, 1, 2, 3) that represent which pixel of the tile is being rendered.
- Shade (0-3):The BGP register (0xFF47) maps each index to a shade (tone of gray). Each index occupies 2 bits in BGP:
- Bits 1-0: shade for index 0
- Bits 3-2: shade for index 1
- Bits 5-4: shade for index 2
- Bits 7-6: shade for index 3
- RGB (0-255):The shade is converted to RGB888 using a fixed table:
- Shade 0 = White (255, 255, 255)
- Shade 1 = Light gray (170, 170, 170)
- Shade 2 = Dark gray (85, 85, 85)
- Shade 3 = Black (0, 0, 0)
Fountain:Pan Docs - "Color Palettes" (DMG)
Typical Pipeline Errors
The most common errors that cause 2-color collapse are:
- Incorrect paddle shift:Wear
<<instead of*2, or use the shade instead of the index to calculate the shift. - Collapse on shade_to_rgb:The conversion table maps different shades to the same RGB value.
- Incorrect Stride:RGB writing steps bytes or uses an incorrect stride.
- Bad buffer:It is read/written from the wrong buffer (back instead of front).
Implementation
Phase A: Pipeline Instrumentation
Added debug instrumentation to capture samples from the idx→shade→rgb pipeline:
- Debug members in PPU.hpp:
last_idx_samples_[32]- First 32 indexes processedlast_shade_samples_[32]- First 32 resulting shadeslast_rgb_samples_[32][3]- First 32 RGB values (R, G, B)last_convert_sample_count_- Number of samples capturedlast_bgp_used_debug_- BGP used in conversion
- Modification in convert_framebuffer_to_rgb():Capture samples during conversion (only first N pixels to avoid cluttering context).
- Getters in PPU.hpp:
get_last_idx_samples(),get_last_shade_samples(),get_last_rgb_samples(), etc. - Exhibition on Cython:Method
get_last_dmg_convert_samples()which returns a dict with the samples. - Modification in tests:Checking the pipeline after rendering 1 frame.
Numerical Evidence
The instrumentation shows that the pipeline is working correctly:
[TEST-PIPELINE] BGP used: 0xE4
[TEST-PIPELINE] First 8 pixels:
idx: [0, 1, 2, 3, 0, 1, 2, 3]
shade: [0, 1, 2, 3, 0, 1, 2, 3]
rgb: [(255, 255, 255), (170, 170, 170), (85, 85, 85), (0, 0, 0),
(255, 255, 255), (170, 170, 170), (85, 85, 85), (0, 0, 0)]
✅ Pipeline OK: idx=4 unique, shade=4 unique, rgb=4 unique
Conclusion:The conversion pipeline works correctly. When there are 4 different indices, it produces 4 unique colors.
Real Problem Identified
Analysis of the entire framebuffer shows that:
- First 100 pixels:Distribution 0=25, 1=25, 2=25, 3=25 (4 different indices) ✅
- Complete sampling:Distribution 0=1152, 1=0, 2=1152, 3=0 (only indices 0 and 2) ❌
This means that the tile is only rendering on the first few pixels, and the rest of the framebuffer has only indices 0 and 2. This is a problem.rendering, not conversion.
Affected Files
src/core/cpp/PPU.hpp- Added debug members for pipeline samples (under#ifdef VIBOY_DEBUG_PPU)src/core/cpp/PPU.cpp- Modifiedconvert_framebuffer_to_rgb()to capture samples, member initialization in constructorsrc/core/cython/ppu.pxd- Added getter declarations for samplessrc/core/cython/ppu.pyx- Added methodget_last_dmg_convert_samples()to expose samples to Pythontests/test_palette_dmg_bgp_0454.py- Added pipeline check after rendering 1 frame
Tests and Verification
Command executed: pytest -v tests/test_palette_dmg_bgp_0454.py
Result:Test fails because the entire framebuffer only has 2 unique colors, but instrumentation shows that the pipeline works correctly in the first few pixels.
Test Code:
# --- Step 0459: Check pipeline idx→shade→rgb ---
samples = ppu.get_last_dmg_convert_samples()
if samples:
idx_samples = samples['idx'][:8] # First 8
shade_samples = samples['shade'][:8]
rgb_samples = samples['rgb'][:8]
bgp_used = samples['bgp_used']
# Assert: idx_samples contains 0,1,2,3
unique_idx = set(idx_samples)
assert unique_idx == {0, 1, 2, 3}
# Assert: shade_samples has ≥3 distinct values
unique_shade = set(shade_samples)
assert len(unique_shade) >= 3
# Assert: rgb produces ≥3 distinct values
unique_rgb = set(rgb_samples)
assert len(unique_rgb) >= 3
Native Validation:✅ Successful compilation with-DVIBOY_DEBUG_PPU, without errors. Instrumentation correctly captures samples from the pipeline.
Evidence:The first 8 pixels show 4 unique colors: (255,255,255), (170,170,170), (85,85,85), (0,0,0). The pipeline works correctly.
Sources consulted
- Bread Docs:Power Up Sequence
- Bread Docs:Color Palettes (DMG)
Educational Integrity
What I Understand Now
- DMG conversion pipeline:The idx→shade→rgb pipeline works in 3 stages: raw index (0-3) → shade via BGP (0-3) → RGB888 via fixed table. Each stage is independent and can fail separately.
- Debug instrumentation:Capturing pipeline samples allows you to identify exactly where the conversion breaks down. The samples show that the pipeline works correctly when there are a variety of indexes.
- Separation of responsibilities:The "RGB only 2 single colors" issue may be in rendering (wrong indices) or conversion (palette/RGB). Instrumentation allows you to isolate the problem.
What remains to be confirmed
- Full render:The tile is only rendered on the first pixels. I need to check why the rest of the framebuffer has only indices 0 and 2. This is a rendering issue, not a conversion issue.
- Index distribution:The logs show that there are 17280 non-zero pixels with distribution 0=5760, 1=5760, 2=5760, 3=5760, but full sampling only finds indices 0 and 2. I need to investigate this discrepancy.
Hypotheses and Assumptions
Confirmed hypothesis:The conversion pipeline works correctly. When there are 4 different indices, it produces 4 unique colors. No fix is required in the pipeline.
Pending hypothesis:The problem is in the rendering. The tile is not repeating correctly across the entire screen, or there is a problem with how the indices are being written to the framebuffer.
Next Steps
- [ ] Investigate why the index framebuffer only has variety in the first few pixels
- [ ] Verify that the tile is rendering correctly on the entire screen
- [ ] If the problem is rendering, correct it in a future Step