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

Framebuffer debugging

Date:2025-12-18 StepID:0036 State: Verified

Summary

After optimizing the rendering withPixelArray, the emulator showed a black screen with no visible logs. The problem was diagnosed and corrected:PixelArraywas not closing properly before doingblitto the screen, blocking the surface. Switched to using a context manager (with) for ensure correct closure. Additionally, added aheartbeatwhich prints every 60 frames (≈1 second) the PC and FPS to confirm that the emulator is alive, even when the registry is in DEBUG mode.

Hardware Concept

Surface Lock in Pygame:When you create aPixelArrayon a Pygame surface, the surface remains"locked"for direct writing. This means that while thePixelArrayis active, the surface cannot be used for other operations such asbliteithertransform.scale. If you try to do these operations with the surface locked, Pygame may crash silently or not draw at all.

Context Manager Pattern:In Python, a context manager (usingwith) guarantees that a resource be released correctly, even if an exception occurs. ForPixelArray, wearwith pygame.PixelArray(buffer) as pixels:ensures that the array closes automatically when exiting the block, unlocking the surface and allowing subsequent operations such asblitwork correctly.

Heartbeat (System Heartbeat):A heartbeat is a diagnostic mechanism that periodically prints the system status to confirm that it is alive and working. In this case, every 60 frames (approximately 1 second at 60 FPS), the Program Counter (PC) and the current FPS are printed. This is especially useful when the registry is in DEBUG mode and no normal messages are displayed, allowing you to verify that the emulator is running correctly.

Implementation

Two main changes were made:

1. Context Manager for PixelArray

Insrc/gpu/renderer.py, the use ofPixelArrayof:

pixels = pygame.PixelArray(self.buffer)
#...rendering code...
of the pixels

To use a context manager:

with pygame.PixelArray(self.buffer) as pixels:
    #...rendering code...
    # The array is automatically closed when exiting the block

This guarantees that thePixelArrayclose properly before attempting to scale or blit the buffer.

2. Heartbeat in the Main Loop

Insrc/viboy.py, a frame counter and a heartbeat that prints every 60 frames were added:

frame_count = 0

# In the main loop, when V-Blank is detected:
if in_vblank and not self._prev_vblank:
    frame_count += 1
    
    # Heartbeat: every 60 frames (≈1 second), show status
    if frame_count % 60 == 0:
        pc = self._cpu.registers.get_pc()
        fps = self._clock.get_fps() if self._clock is not None else 0.0
        logger.info(f"Heartbeat: PC=0x{pc:04X} | FPS={fps:.2f}")

The heartbeat useslogger.info()so that it always shows up, even when the registry is in DEBUG mode.

"Bit 0 Hack" Verification

Verified that the LCDC "Bit 0 Hack" is still present and working correctly (lines 239-258 ofrenderer.py). This hack allows CGB games like Tetris DX to writeLCDC=0x80(bit 7=1 LCD ON, bit 0=0 BG OFF) can display graphics, ignoring bit 0 when the LCD is on.

Affected Files

  • src/gpu/renderer.py- ChangedPixelArrayto use context manager (with)
  • src/viboy.py- Added frame and heartbeat counter that prints every 60 frames

Tests and Verification

Manual Verification:The emulator was run with Tetris DX to verify that:

  • Screen is no longer black (game background shown)
  • The heartbeat appears every second on the console:INFO: Heartbeat: PC=0xXXXX | FPS=59.XX
  • Framebuffer renders correctly without crashes

Context Manager Validation:It was verified that the code compiles correctly and that thePixelArraycloses before doingblit, avoiding blocking the surface.

Note on Unit Tests:The existing tests intests/test_gpu_scroll.pythey use@patch('src.gpu.renderer.pygame.draw.rect'), but now the code usesPixelArrayratherdraw.rect. These tests will need to be updated in the future to reflect the new rendering method, but they do not affect the functionality of the emulator.

Sources consulted

  • Pygame Documentation:PixelArray- Context manager and surface blocking
  • Python Documentation:Context Managers- Resource management pattern

Educational Integrity

What I Understand Now

  • Blocking Surfaces: PixelArraylocks the underlying surface while active. This is necessary to allow direct writing to memory, but requires it to be closed before using the surface for other operations.
  • Context Managers:The patternwithin Python guarantees the release of resources safe, even if an exception occurs. It is the recommended way to usePixelArray.
  • Heartbeat for Diagnosis:When the registry is in DEBUG mode, a periodic heartbeat allows verify that the system is alive without needing to change the logging level.

What remains to be confirmed

  • Unit Tests:The scroll tests need to be updated to reflect the use ofPixelArrayratherdraw.rect. This will be done in a future step.
  • Performance:Although the context manager adds a small overhead, the benefit of ensuring closure correct outweighs any performance impact. It can be measured in the future if necessary.

Hypotheses and Assumptions

It is assumed that the surface lock was the main cause of the black screen. If the problem persists after Due to this change, it may be necessary to investigate other aspects such as the color palette or VRAM content.

Next Steps

  • [ ] Update scroll tests to reflect the use ofPixelArray
  • [ ] Verify that Tetris DX displays the background and graphics correctly
  • [ ] If the screen is still black, investigate color palette and VRAM content
  • [ ] Consider adding more diagnostics if necessary (e.g. verify that VRAM has data)