TLDR: I made a Eden v0.0.4 based fork specifically intended for the Retroid Pocket Classic with the devices low specs in mind to maximize compatibility. Fork tested with Qualcomm Adreno Driver 842.6.
I’ve been testing Eden pretty heavily on the Retroid Pocket Classic, and I noticed that compatibility seems to regress pretty sharply after Eden v0.0.4. On my device, most v0.1.0+ builds either crash immediately in titles that used to boot, or show rendering issues that were not present in v0.0.4.
Since v0.0.4 was the last build that gave me consistently good results on the Pocket Classic, I started comparing it against the newer public builds, especially v0.2.0-rc2, to figure out what changed.
The short version is that the newer Vulkan backend appears to be much more aggressive and modernized, but some of those changes do not seem to behave well on the Retroid Pocket Classic’s Snapdragon 865 / Adreno 650 setup. The Pocket Classic also does not support Turnip drivers, so it is stuck with Qualcomm’s stock Vulkan driver. That makes conservative Adreno behavior much more important than it would be on other Android handhelds.
Some of the biggest differences I noticed between v0.0.4 and newer Eden builds were:
- Newer builds use
ankerl::unordered_densein several hot paths, while v0.0.4 still usesstd::unordered_map. - Pipeline cache behavior changed significantly, including newer hash/key handling.
- The Vulkan scheduler changed how it handles the submit mutex around
vkQueueSubmit/vkQueuePresent. - Barriers are batched more aggressively in newer builds.
- Runtime Adreno workarounds were changed compared to the more conservative behavior in v0.0.4.
- Newer builds are more aggressive with async shader handling and placeholder pipelines.
- Texture cache behavior, decode worker behavior, and eviction thresholds were tuned more for higher-memory Android devices.
- Some newer paths appear to rely more heavily on GPU-side texture/ASTC behavior that does not always behave correctly on Adreno 650.
I originally tried working from the newer v0.2.0-rc2 codebase, but the Vulkan renderer had changed enough that fixing the Pocket Classic-specific regressions there was outside the scope of what I could reasonably do. So instead, I went the other direction: I started from Eden v0.0.4, since it already has good compatibility on the Pocket Classic, and selectively backported performance-oriented changes that seemed safe for this specific device.
This is not meant to be a universal Android build or a replacement for upstream Eden. The goal is specifically to improve v0.0.4 for the Retroid Pocket Classic and other lower-spec Snapdragon/Adreno devices using stock drivers.
Here are the main changes I made.
1. Vulkan scheduler lock handling
Files touched:
vk_scheduler.cppvk_master_semaphore.cpp
I changed the Vulkan scheduler behavior so the submission mutex is released before calling vkQueueSubmit / vkQueuePresent.
On the Snapdragon 865, the stock Adreno Vulkan driver can spend a noticeable amount of time inside queue submission. Holding the emulator’s submit mutex during that call can block the CPU thread preparing the next frame. Releasing the lock earlier helps reduce unnecessary stalls between the render thread and driver submission path.
This does not magically fix GPU-bound games, but it helps reduce frame pacing issues in titles where the render thread is already tight.
2. Faster GPU-side lookup maps
Files touched:
channel_state_cache.hscheduler.cppscheduler.hgpu.cppgpu_thread.cppmaxwell_3d.cpp
I replaced some hot std::unordered_map usage with ankerl::unordered_dense::map, similar to what newer Eden builds use.
This mainly targets command processor and GPU page-table lookups. std::unordered_map is node-based and can be fairly cache-unfriendly. On a mobile SoC like the Snapdragon 865, avoiding pointer chasing in hot paths helps more than it would on a desktop CPU with much larger caches and memory bandwidth.
This should mostly benefit command-heavy games where the emulator spends a lot of time walking GPU state.
3. Pipeline cache map and key lookup improvements
Files touched:
vk_pipeline_cache.hvk_pipeline_cache.cppvk_render_pass_cache.hshader_cache.h
I also moved parts of the pipeline/render-pass/shader cache lookup path over to ankerl::unordered_dense, with faster key hashing and comparison behavior.
Pipeline lookups happen constantly during rendering, so even small CPU-side wins here can help. The goal was to reduce per-draw overhead without changing actual rendering behavior too much from the v0.0.4 base.
4. More explicit Adreno-aware Vulkan device handling
Files touched:
vulkan_device.cppvulkan_device.h
I added more explicit Qualcomm/Adreno detection and used it to gate certain Vulkan paths and workarounds.
The stock Adreno 650 driver has some fragile behavior around specific descriptor/update paths and image-format combinations. Since the Pocket Classic cannot use Turnip, avoiding risky stock-driver paths is more important than chasing maximum theoretical performance.
This is one of the main compatibility-focused changes.
5. Vulkan barrier batching
Files touched:
vk_command_pool.cppvk_present_manager.cppvk_swapchain.cppvk_graphics_pipeline.cpp
I backported some of the newer barrier batching behavior so multiple vkCmdPipelineBarrier calls can be coalesced where appropriate.
On tile-based mobile GPUs, excessive barriers can be expensive because they can force synchronization and tile flushes. Reducing redundant barriers helps especially in games with lots of small render passes, UI passes, or post-processing chains.
6. Texture cache and worker improvements
Files touched:
texture_cache.htexture_cache.cpptexture_cache_base.hworkers.cpp
I backported selected texture cache lookup, eviction, and decode worker improvements.
The Pocket Classic only has 6 GB of RAM, so texture cache behavior matters a lot. More stable eviction behavior means fewer reuploads and fewer stalls on the shared memory bus. I tried to keep this conservative because overly aggressive eviction can introduce flicker or streamed texture issues.
7. Buffer cache page alignment tweaks
File touched:
buffer_cache_base.h
I aligned some buffer cache page constants with newer Eden behavior.
The goal here was to better match Adreno’s native page behavior and reduce split-buffer transfer overhead where possible. This is a smaller change, but it fits the overall goal of reducing avoidable memory churn.
8. Settings and HLE scheduler alignment
Files touched:
settings.hk_scheduler.cpp
Some newer settings fields and HLE scheduler behavior were brought over so the backported systems compile cleanly and behave consistently.
This was mostly glue work, but it also picks up some scheduling-related improvements that reduce unnecessary preemption in certain cases.
9. Vulkan turbo mode
File touched:
vk_turbo_mode.cpp
I backported the lightweight “turbo mode” behavior that keeps the GPU from downclocking too aggressively during idle gaps.
Adreno DVFS can be pretty aggressive, especially in 30 FPS titles. Keeping the GPU clocks from constantly dropping can reduce the stutter you sometimes get on the first frame after a short idle period or menu transition.
10. Vulkan rasterizer compatibility adjustments
Files touched:
vk_rasterizer.cppvk_descriptor_pool.h
These were mostly compile-compatibility changes so the older v0.0.4 rasterizer could work with the updated pipeline cache and device-info paths.
I did not try to rewrite the rasterizer itself.
11. Worker-thread big.LITTLE pinning
Files touched:
thread.hthread.cppthread_worker.h
This is the one major original change I added specifically for the Retroid Pocket Classic.
I added:
Common::SetCurrentThreadAffinityMask(u64)
Common::PinCurrentThreadToLittleCores()
On Android, worker threads like pipeline building, pipeline serialization, texture decoding, ASTC decoding, and BCN decoding now get pinned to the little cores where possible and run at lower priority.
The Snapdragon 865 has a 4+3+1 CPU layout:
- CPUs 0–3: Cortex-A55 little cores
- CPUs 4–6: Cortex-A77 big cores
- CPU 7: Cortex-A77 prime core
By default, Android/Linux may move background worker threads onto the big cores under load. That can cause them to compete with the render thread and Dynarmic JIT, which are much more important for frame pacing.
The tradeoff is that first-time shader compilation or texture decoding may be slightly slower, because the A55 cores are weaker. But the work happens in the background, and keeping it off the big cores seems better for gameplay smoothness on this device.
Summary
The main bottlenecks I was trying to address were:
| Problem | Change |
|---|---|
| Driver queue-submit stalls | Vulkan scheduler lock changes |
| Cache-unfriendly GPU lookups | ankerl::unordered_dense in hot maps |
| Pipeline lookup overhead | Faster pipeline/render-pass/shader cache lookup |
| Stock Adreno driver issues | More conservative Adreno device handling |
| Expensive mobile GPU barriers | Barrier batching |
| Texture upload/cache churn | Texture cache and eviction tweaks |
| GPU downclock stutter | Vulkan turbo mode |
| Big-core contention | Worker-thread pinning to little cores |
I intentionally did not change a few higher-risk areas:
- I did not alter
MustFlushRegionbehavior because that could easily regress other games. - I did not force a broader
BASE_PAGE_BITS = 16change because the surface area seemed too large for the measured benefit. - I did not enable questionable Vulkan extensions like
pipeline_creation_cache_controlwithout more validation on Adreno 650. - I left debug-only pipeline statistics and shader feedback paths alone.
Overall, this is basically a Pocket Classic-focused v0.0.4 fork: keep the older compatibility baseline, then selectively backport performance improvements and add device-specific scheduling behavior for the Snapdragon 865 / Adreno 650 stock-driver environment.
P.S. Shout out to the Eden dev team, they are the best and deserve all the credit for the amazing work they put in on this emulator. I'm just trying to add some low-spec continuity so that my wonderful Retroid Pocket Classic isn't forgotten (my fav device 😭).