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
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 renderingsrc/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 of
PixelArrayin 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
- Optimize rendering(Step 0307):
- Search climbed surface
- Optimize render loop
- Measure impact with performance monitor
- Check synchronization(Step 0308):
- Confirm that the snapshot is taken at the correct time
- Verify that there are no race conditions
- 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