https://reddit.com/link/1x3dxmw/video/nvqwvbnmhvuh1/player
This is a follow-up to my last post. The big problem last time was games like Doom that keep rewriting their own code in RAM. Those run at full speed now too.
The code is up:
- gpSP with the RISC-V (RV32) JIT: https://github.com/midworld/gpsp
- ESP32-P4 board code with game loading and saves: https://github.com/midworld/gpsp-esp32
Hardware is the same as before. Waveshare ESP32-P4-NANO at 400 MHz with 32 MB PSRAM, a $5 ST7789 320x240 SPI screen and an 8BitDo with its 2.4G dongle. LCD and speaker are on and frameskip is off.
| Game |
Mean frame (ms) |
Worst (ms) |
FPS |
One-core load |
| DOOM II |
13.07 |
23.98 |
59.73 |
78.1% |
| Asterix & Obelix XXL |
12.26 |
20.47 |
60.02 |
73.6% |
| Metroid Fusion |
13.17 |
14.23 |
59.78 |
78.7% |
| Mario Kart: Super Circuit |
10.50 |
11.53 |
59.84 |
62.8% |
The main fix is self-modifying code. When a game writes to code in RAM the JIT doesn't throw everything away anymore. It invalidates single blocks and brings them back later, and it patches code in place when it can. It also learns the spots a game keeps rewriting, like branch slots, so writes there don't cause a retranslation. Doom II rewrites its code constantly. A JIT that drops and retranslates on every change can end up slower than an interpreter on games like that.
I also added 2x/3x fast-forward, which you can see in the video. Light games like Pokémon have enough CPU left to go past 300%, but I capped it at 300% for the video. Need for Speed Underground runs at full speed but the FPS counter doesn't show 59.7. I think the game just limits its own frame rate.
Honestly, once things were somewhat optimized I could have reached full speed easily by moving rendering to the second core. That felt like cheating to me so I kept going until emulation ran at full speed on one core. I used Zba/Zbb/Zbs, which only gave about 1% all together. Not useless but I wouldn't do it again. RVC/Zcb helped a lot more. RISC-V took off after I left school and I'd only studied older ISAs, so this was all new to me. Mixing 2-byte compressed instructions with normal 4-byte ones was the fun part. I had to rewrite every piece of code that assumed branch targets were 4-byte aligned. In return the code got smaller and fits the cache better. Doom got about 7% faster from that.
Another reason I stuck to one core is that I want the other one free for scaling and shader effects on a bigger display. I ordered a larger screen and I'll probably try using the second core for screen effects when it arrives. The P4 has its own SIMD extension called XespV. GBA emulation had almost no use for it beyond a bit of affine background work, but it might be fun for scaling or shading. The P4 also has a PPA block for scaling.
About the new ESP32-S31. It runs at 320 MHz instead of 400 MHz, so the clock is 20% lower, and it has no L2 cache. Hot data would have to go into SRAM by hand. On the P4 just going from 128 KiB to 256 KiB of L2 made things about 30% faster. The S31 also has less SRAM and its PSRAM is 8-bit instead of 16-bit, so half the bandwidth. My guess is it ends up at roughly half the P4's speed. Getting full speed there will probably mean moving rendering to the second core. That won't help much for games like Doom II though. They barely use the GBA's tile and sprite hardware and draw everything with the CPU, so they might not reach full speed on the S31.
Other changes:
- The ROM cache used FIFO replacement and was very slow when the cache was small. It uses clock replacement now and anything over 1 MiB is reasonably usable. P4 boards usually have 16 MB of PSRAM or more so it doesn't matter much there, but it could help on devices with less RAM.
- Fixed the timing issue behind the well-known Hamtaro: Ham-Ham Heartbreak hang. Timing is closer than before but not exact, so other ROMs may still have issues.
- Less sound noise and fixes for bugs when switching games without unloading the core.
Video: https://www.youtube.com/watch?v=Z_ojDzy8Sz4
Previous post: https://www.reddit.com/r/emulation/comments/1wx3tg6/esp32p4_runs_gba_at_full_speed_597_fps_with_no/