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

Performance and Graphic Corruption Investigation

Date:2025-12-25 StepID:0306 State: DRAFT

Summary

Thorough investigation of two critical issues identified in Step 0305: poor performance (21.8 FPS instead of ~60 FPS) and graphical corruption (checkerboard pattern, fragmented sprites). A performance monitor ([PERFORMANCE-TRACE]) was implemented to measure frame time and FPS, and the possible causes of both problems were analyzed.

Aim: Identify the root causes of poor performance and graphical corruption, and determine if they are related.

Identified problems:

  • ⚠️ Critical performance: FPS 21.8 (should be ~60 FPS) - new issue identified
  • ⚠️ Graphic corruption: Checkerboard pattern and fragmented sprites

Hardware Concept

Emulation Performance

Emulators need to maintain 60 FPS for accurate emulation. Expensive operations must be optimized:

  • Object creation: PixelArray, Surface (each frame is expensive)
  • Transformations: Scaling, rotation (expensive image operations)
  • Memory copies: Blit (usually fast, but can stack)
  • Render loops: Iterate over 23,040 pixels in each frame

Graphic Corruption

Checkerboard patterns usually indicate:

  • Incorrect address calculation: Problems with tile mapping
  • Problems with scrolling: SCX/SCY applied incorrectly
  • Frame desynchronization: Read framebuffer while writing

Fragmented sprites usually indicate:

  • Incorrect rendering: Problems with position calculation
  • Priority issues: Sprites rendered in wrong order
  • Corrupt data in OAM: OAM incorrectly read or modified during rendering

Fountain: Pan Docs - "Background", "Sprites", "LCD Timing"

Implementation

Performance Monitor ([PERFORMANCE-TRACE])

Se implementó un monitor de rendimiento en renderer.pywhich measures the time of each frame and calculates the FPS:

# --- STEP 0306: Performance Monitor ([PERFORMANCE-TRACE]) ---
frame_start = time.time()
#...rendering code...
frame_end = time.time()
frame_time = (frame_end - frame_start) * 1000 # in milliseconds
if self._performance_trace_count % 60 == 0: # Every 60 frames
    fps = 1000.0 / frame_time if frame_time > 0 else 0
    print(f"[PERFORMANCE-TRACE] Frame {count} | "
          f"Frame time: {frame_time:.2f}ms | FPS: {fps:.1f}")

Graphic Corruption Analysis

The following hypotheses were investigated:

  • Problem with calculating tile addresses: ✅ Verified - correct calculation
  • Problem with scroll (SCX/SCY): ✅ Verified - scroll applied correctly
  • Problem with tilemap mapping: ✅ Verified - correct mapping
  • Problem with synchronization between frames: ⚠️ POSSIBLE CAUSE- desynchronization between C++ and Python

Analysis of Fragmented Sprites

The following hypotheses were investigated:

  • Problem with sprite rendering: ✅ Verified - correct logic
  • Render order issue: ✅ Verified - correct order
  • Problem with sprite priority: ⚠️ Not fully implemented, but would not explain fragmentation
  • Problem with OAM: ✅ Verified - OAM reads correctly

Analysis of Slow Operations

The following costly operations were identified:

  • Pixel-by-pixel rendering loop: 23,040 iterations per frame (High Impact)
  • pygame.transform.scale(): Scale 160x144 to 480x432 in each frame (Medium-High impact)
  • PixelArray creation: Create new object in each frame (Half impact)

Tests and Verification

Performance Monitor

Command executed: The monitor is activated automatically when you run the emulator

Expected result:

[PERFORMANCE-TRACE] Frame 0 | Frame time: XX.XXms | SPF: XX.X
[PERFORMANCE-TRACE] Frame 60 | Frame time: XX.XXms | SPF: XX.X
[PERFORMANCE-TRACE] Frame 120 | Frame time: XX.XXms | SPF: XX.X

Validation: The monitor will report the frame time and FPS every 60 frames (1 second at 60 FPS).

Análisis de Código

Revised rendering code in:

  • src/gpu/renderer.py: Python rendering
  • src/core/cpp/PPU.cpp: C++ rendering

Validation: Static analysis of the code to identify possible causes of corruption and poor performance.

Findings

Identified Root Causes

Low Performance

  • Main cause: Pixel-by-pixel rendering loop (23,040 iterations per frame)
  • Secondary cause: pygame.transform.scale()without caching
  • Tertiary cause: Creation ofPixelArrayin each frame

Graphic Corruption

  • Main cause: Desynchronization between C++ (writing) and Python (reading) of the framebuffer
  • Secondary cause: Slow rendering allowing partial framebuffer reading

Correlation between Problems

Confirmed hypothesis: Yes, the problems are related.

Poor performance can cause graphics corruption because:

  • If Python rendering is slow, you can read the framebuffer while C++ is writing
  • This would cause some pixels to show values ​​from previous or partial frames
  • The checkerboard pattern could be the result of reading pixels from different frames mixed together.

Next Steps

  1. Optimize rendering(Step 0307):
    • Search climbed surface
    • Optimize render loop
    • Measure impact with performance monitor
  2. Check synchronization(Step 0308):
    • Confirm that the snapshot is taken at the correct time
    • Verify that there are no race conditions
  3. Test fixes:
    • Run emulator and check FPS improvement
    • Verify that graphic corruption disappears

References

  • Pan Docs - "Background Tile Map"
  • Bread Docs - "Sprites"
  • Pan Docs - "LCD Timing"
  • Step 0219 - Immutable Framebuffer Snapshot
  • Step 0305 - Python Rendering Research
  • ANALYSIS_STEP_0306_PERFORMANCE_CORRUPTION.md- Detailed analysis