libretroadmin 5de6d7d261 Optimization pass: no polling sleeps or locks in the hot handoffs
The recompiler thread looked for work every 10 ms, so a function the
interpreter had just run waited 5 ms on average before its translation
began, and every queued address could allocate inside std::queue. Its
queue is now a fixed retro_spsc ring with an eventcount
(PPCRecompilerWorkQueue): a push wakes the thread at once, the thread pops
without the recompiler's spinlock, and a full queue refuses the push so
the address stays unvisited and is offered again later.

Loading the Vulkan pipeline cache napped 10 ms whenever 50 pipelines were
queued and again while the last ones compiled, which held a warm driver
cache to 50 pipelines per 10 ms. The GPU thread now sleeps on an
eventcount (LatteGpuWake) that the compile threads notify after each
pipeline and that a pause request or a stop also notify, so it refills
the queue as soon as there is room and still answers a context loss at
once. The pipeline cache's save thread keeps its 15 s debounce but sleeps
on an eventcount that the renderer's destruction notifies, instead of
looking every 250 ms.

The CPU timer's TSC calibration measured for three seconds and the title
start waited for it, which in RetroArch is every boot. Both clocks are
read back to back, so 25 ms already gives about one part in 10^5: the
first estimate is published then and refined at 200 ms and 3 s by the
same measurement. The rate only divides elapsed ticks, so a refinement
changes no time already counted. The title start waits on an eventcount
instead of a 10 ms poll.

Audio goes to the frontend straight out of the ring buffer (Peek/Consume)
and AX's blocks are converted straight into it (Reserve/Commit), so the
per-frame copy into a 192 KiB flush buffer, the pull buffer and the
conversion vector are gone. The ring moves to LibretroAudioRing.h.

The frame gate and the frame handoff between the GPU thread and retro_run
are atomics and eventcounts in place of two mutexes and condition
variables, with the same predicates and the same timeout.

retro_spsc is synced from RetroArch's libretro-common. test/handoff checks
the work queue (every address once and in order from three serialised
pushers, wake-up from a parked consumer, the bound, the stop), the GPU
wake, and the audio ring (whole-frame spans across the wrap for 2 and 6
channels, a producer and a consumer thread streaming), plain, under TSan
and under ASan/UBSan, in both CIs.
2026-11-07 12:22:08 +01:00
2026-03-24 04:15:04 +01:00
2026-03-24 04:15:04 +01:00

ParaLLEl GX2

Cemu (Wii U emulator) as a libretro core for RetroArch, parallel_gx2_libretro.

Version: 2.6-372-g29f2f1fa, upstream Cemu as merged (29f2f1fa, 15 Oct 2026).

Getting the core

Through RetroArch: Online Updater → Core Downloader → Nintendo - Wii U (ParaLLEl GX2). The libretro buildbot builds the libretro branch nightly for Windows x64, Linux x64 and arm64, macOS x64 and arm64, and Android arm64-v8a and x86_64, so the downloadable core is always the current tree. The CI also builds iOS, tvOS and webOS. There are no releases here on purpose: one more place to download from is one more place to be out of date.

Build it yourself for anything else, or to test a change.

Build

A static Makefile builds the core and everything it uses: the libraries are submodules built with it, and the network layer is libretro-common's (HTTP and TLS, with no libcurl, OpenSSL or mbedTLS), so nothing but a C++20 compiler and, on Linux, the system's X11 headers (for Vulkan's surface declarations) is needed.

# Ubuntu/Debian: the headers Vulkan's X11 surfaces are declared with
sudo apt install -y g++ make libx11-dev libxrender-dev libx11-xcb-dev

# Clone with submodules - the libraries and the community graphic packs live
# in them. On an existing clone: git submodule update --init --recursive
git clone --recursive https://github.com/libretro/parallel-gx2.git
cd parallel-gx2

make -j$(nproc)
# Result: parallel_gx2_libretro.so

The same Makefile builds the other targets: make under MSYS2 MinGW64 (or with CC/CXX set to a MinGW-w64 cross compiler) for Windows, platform=osx with arch=arm64 or arch=x86_64 for macOS, platform=ios-arm64 and platform=tvos-arm64 (Metal only), platform=android-arm64 and platform=android-x86_64 with NDK_ROOT set, and a webOS SDK compiler as CC/CXX for webOS. DEBUG=1 builds without optimisation, SANITIZER=address with a sanitizer.

Install

cp parallel_gx2_libretro.so ~/.config/retroarch/cores/
cp parallel_gx2_libretro.info ~/.config/retroarch/info/

Setup

Everything the core reads lives under Cemu/ in RetroArch's system directory (<system>), except the emulated NAND, which is in the save directory.

  • Keys: keys.txt in <system>/Cemu/. Encrypted .wud/.wux images and NUS titles need it; a disc image's key can also be kept beside it, with the same name and the extension .key (game.wux, game.key), 16 bytes or 32 hex digits.
  • Games: .wua, .wud, .wux, .iso, .rpx, .elf, or title.tmd of an unpacked NUS title.
  • Updates and DLC: put them in <system>/Cemu/titles/, one folder per update or DLC, either as downloaded (.app/.h3 next to title.tmd and title.tik) or unpacked (code/content/meta). They are found by title id and used without being copied.
  • Graphic packs: the community packs are built into the core and unpacked to <system>/Cemu/graphicPacks/ when content loads. They are refreshed whenever the core is a newer build, so don't edit them there: your own packs, and changed copies of the built-in ones, go in <system>/Cemu/customGraphicPacks/. A pack there at the same path as a built-in one (the folder with its rules.txt) replaces it, and survives core updates. Each pack the loaded game has shows up under Graphic Packs in the core options, with an Enabled switch and its presets. Changes take effect the next time the game is loaded.
  • Shared fonts: CafeStd.ttf, CafeCn.ttf, CafeKr.ttf and CafeTw.ttf in <system>/Cemu/resources/sharedFonts/. Games need them for Japanese and other CJK text.
  • NAND (mlc01): <save>/Cemu/mlc01/, created with the default folders on first start, as standalone Cemu does.
  • Account names: System > Rename Active Account opens a name entry over the game: with a pad, Up and Down change the last character, Right adds one, Left removes one, Start accepts and Select cancels; a keyboard types the name (Enter accepts, Escape cancels). A core option cannot hold free text, so the accounts also take their names from <system>/Cemu/account_name.txt. The core writes the file itself the first time a title starts, with one line per account, <id> = <name>; change a name there (up to the console's ten characters) and the account has it the next time a title starts. The file is written again at each start and whenever an account is made or removed in System, so it always lists the accounts there are.
  • Logs: <system>/Cemu/log.txt. Its first lines name the build it came from, so please attach it, with the RetroArch log, to bug reports.

Controls

Port 1 is the Wii U GamePad by default; ports 2-4 start with nothing plugged in. Pick a device type per port in Quick Menu → Controls → Port N Controls → Device Type:

Port Device types
1 Wii U GamePad, Wii U GamePad + Wii Remote, Wii U GamePad + Wii Remote (sideways), Wii U Pro Controller
2-4 None, Wii Remote, Wii Remote (sideways), Wii U Pro Controller, Classic Controller
  • Touch screen: the mouse or a touchscreen acts as the GamePad's touch screen. In the layouts that show both screens, only touches on the GamePad's part count. A mouse gets an arrow cursor over the GamePad picture while it moves.
  • Motion: Controller → GamePad Motion (off by default) gives the GamePad's gyroscope and accelerometer from the sensors RetroArch reads on port 1: a phone's or tablet's own, or a controller's. The log says "GamePad Motion: on" when RetroArch has them. On Linux with the udev controller driver, a DualSense or DualShock 4 has its sensors on a second input device ("... Motion Sensors") that a user cannot read by default; a rule such as SUBSYSTEM=="input", KERNEL=="event*", ATTRS{name}=="*Motion Sensors", TAG+="uaccess" in /etc/udev/rules.d/, then sudo udevadm control --reload && sudo udevadm trigger, makes it readable. RetroArch's SDL driver hands the sensors over with swapped axes, so aiming is skewed there; use udev.
  • Rumble: the GamePad, Wii Remote and Pro Controller motors go to the frontend's rumble, scaled by Controller → Rumble Strength (100% by default).
  • Stick dead zone: Controller → Stick Dead Zone (Player 1) for the GamePad or the controller on port 1, and (Players 2-4) for the rest; 25% by default, as in standalone Cemu.
  • Screens: Controller → Number of Screen Layouts and Layout 1-5 choose which layouts to cycle through (Default Screen, GamePad Screen, Side by Side, Top Bottom, Picture in Picture). Next Screen Layout picks the button combination that switches between them.
  • Portals: Skylanders, Disney Infinity and LEGO Dimensions portals are emulated: the option for each (Emulate Skylanders Portal of Power, Emulate Infinity Base, Emulate Dimensions Toy Pad) is at the bottom of the core options, shown only while one of its games is loaded.

Status (2026-10-23)

Games boot and run on Vulkan and on OpenGL, with sound, controllers, both screens and graphic packs. The core is synced with upstream Cemu (see below), and its options are translated through libretro's Crowdin.

Known issues:

  • Some games crash or misrender, as in standalone Cemu, and some only in the core. Reports with log.txt and the RetroArch log are welcome on the issue tracker.
  • No save states. Cemu cannot serialize its state, so retro_serialize_size returns 0.
  • The core stays loaded between games. RetroArch unloads it, but the library itself stays in memory (on Linux on purpose, see -z nodelete below). Any state Cemu keeps for the whole process can leak from one game into the next. The known cases (DLC list, graphic pack module list, controller callbacks) are fixed.

Planned: output above 60 fps for games with high frame rate packs. The Cheats graphic packs have their own category, and games, updates and DLC are installed from the core options (Title Manager).

Architecture

  • Vulkan renders on the frontend's device (RETRO_HW_CONTEXT_VULKAN with the context negotiation interface) and hands RetroArch each finished image. OpenGL uses a 4.5 Core Profile HW context of the core's own (RETRO_ENVIRONMENT_SET_HW_SHARED_CONTEXT) and nothing else: no WGL, GLX, EGL or CGL, and no GL library linked. Every GL function comes from the frontend's get_proc_address, and since the context is current only on the frontend's thread during the core's callbacks, Cemu's GPU thread runs there as a libco cothread (LatteGpuCothread.h): retro_run runs it until it finishes a frame, and wherever it would wait for the emulated CPU it hands the frontend's thread back. The frame is blitted from the backbuffer into the frontend's framebuffer on that same context, with no readback.
  • The Vulkan device belongs to the frontend, so the core asks it for the extensions and features standalone Cemu enables, and the renderer then uses only what was actually enabled. An extension the GPU advertises is not necessarily enabled on a device RetroArch created, and vkGetDeviceProcAddr then returns null.
  • cache_context = true keeps the Vulkan context across fullscreen toggles and video driver reinits, the present path asks the frontend for its render interface every frame, and the GPU thread parks at a command boundary while the frontend rebuilds its driver.
  • Audio goes through LibretroAudioAPI, a lock-free ring buffer paced by the frames the frontend asks for.
  • Input: libretro joypads drive VPAD (GamePad) and WPAD/KPAD (Wii Remote, Pro Controller, Classic Controller); pointer input is the GamePad touch screen.
  • When the emulated process exits, CafePPCProcessExit only records it; RETRO_ENVIRONMENT_SHUTDOWN goes out from retro_run, because the callback fires on the emulated PPC thread.
  • RetroArch calls the core's Vulkan destroy_device after dlclose, so on Linux the core is linked with -z nodelete.
  • The standalone's GUI, input and audio backends are removed, and .upstream-excluded keeps them from coming back with an upstream merge.

Key files

  • src/libretro/CemuLibretro.cpp - the libretro core: retro_* exports, GL/Vulkan context negotiation, input, video pipeline
  • src/libretro/libretro_core_options.h - core option definitions (translations in libretro_core_options_intl.h, generated from Crowdin)
  • src/audio/LibretroAudioAPI.{h,cpp} - audio backend
  • src/libretro/LibretroWindowSystem.cpp - WindowSystem without wxWidgets
  • src/Cafe/OS/libs/vpad/vpad.cpp, src/Cafe/OS/libs/padscore/padscore.cpp - GamePad and Wii Remote/Pro Controller input

Syncing with upstream Cemu

Upstream is cemu-project/Cemu and its branch is main, not master:

git remote add cemuup https://github.com/cemu-project/Cemu.git   # once
git fetch cemuup main
git merge cemuup/main

Last sync: upstream 29f2f1fa (merged 23 Oct 2026). The core reports its version as git describe --tags --match v2.6 cemuup/main (2.6-372-g29f2f1fa), set in retro_get_system_info and parallel_gx2_libretro.info; update both, and the version at the top of this README, on every sync.

The fork has no main of its own: upstream is merged straight into libretro, the only long-lived branch here.

What tends to break on a sync, none of which the conflict resolution shows:

  • Source lists. The core builds from the Makefile, not upstream's CMake (which .upstream-excluded removes on every merge): a source file upstream adds or removes has to be added to or removed from Makefile.common by hand. Every unit of the core is compiled with precompiled.h force-included, as upstream's cemu_use_precompiled_header() does.
  • Boost and the network libraries. The core has no Boost: the parts the emulator uses are small headers in dependencies/boost-compat, and dependencies/curl-compat carries libcurl's API over libretro-common's HTTP and TLS. Upstream code that reaches for another Boost header, or a libcurl or OpenSSL call these do not have yet, fails to build until they do.
  • Copied functions drifting. The libretro VulkanRenderer constructor is a copy of upstream's adapted for the shared device; upstream keeps changing the original underneath it.
  • Extension gating. See Architecture above - upstream adds calls guarded by device extension flags, and those flags cannot be trusted on a device the frontend created.
  • File access. Upstream code that opens files with FileStream has to go through VFSFileStream here, otherwise the frontend's VFS (and Android SAF) is bypassed.

Debugging a crash on aarch64: backtrace() cannot unwind past the signal frame, so the posix handler also records fault/pc/lr/sp into <system>/Cemu/log.txt. Convert lr with the module base from that log's parallel_gx2_libretro.so(+0xOFFSET) [0xABSOLUTE] line and run addr2line -Cfie parallel_gx2_libretro.so <offset>.

S
Description
Cemu - Wii U emulator
Readme MPL-2.0
34 MiB
0 Stars 1 Watchers 0 Forks
Languages
C++ 80.5%
C 18.6%
Python 0.5%
Objective-C++ 0.2%