Skip to content

Switch: build the libretro core from this tree - #22153

Open
WizzardSK wants to merge 9 commits into
hrydgard:masterfrom
WizzardSK:nx-core
Open

Switch: build the libretro core from this tree#22153
WizzardSK wants to merge 9 commits into
hrydgard:masterfrom
WizzardSK:nx-core

Conversation

@WizzardSK

@WizzardSK WizzardSK commented Aug 27, 2026

Copy link
Copy Markdown

Builds the libretro core for the Nintendo Switch from this tree, so the buildbot's Switch core can stop coming from a fork.

Today it is built from libretro/ppsspp-libnx, branch switch, whose upstream base is 6f04f52f5cMarch 2022. That is what Switch users have been running. @m4xw has kept rebasing the port privately (his rebase_2026 branch sits on December 2025), and this is that work carried onto current master, with the parts that were never scoped to the Switch put back.

The first three commits are his, rebased. The rest is mine.

What was in the way

Most of it was the port assuming a toolchain and a tree from four years ago:

  • libnx dropped virtmemReserve for virtmemFindAslr, and now defines BIT and BreakReason, which collide with Arm64Emitter and with the rename block CommonTypes.h already keeps around switch.h.
  • newlib gaps: no <sys/mman.h>, no <sys/syscall.h>, ENETDOWN sharing a value with ESHUTDOWN, <climits> not arriving transitively.
  • T3DCreateGLContext grew a parameter; Common/GL/GLInterfaceBase.h stopped existing (the port carried an #include of it next to a member its own comment marked // unknown leftover??); proAdhoc.cpp had 38 lines of resurrected DNS code calling g_Config.proAdhocServer, renamed to sProAdhocServer long ago.

Things that were not Switch-only, and are now

These are the reason the port could not be merged as it stood. Each one changed behaviour for platforms that never asked:

  • GLEW was switched off for everyone. GLCommon.h had //#include "GL/glew.h" and ~6900 lines of generated extern/#define pasted in behind #if !defined(__LIBRETRO__) && !PPSSPP_PLATFORM(SWITCH) ... #else, so the desktop libretro core lost GLEW too. The generated half now lives in Common/GPU/OpenGL/GLSwitch.h, included only under PPSSPP_PLATFORM(SWITCH); GLCommon.h is back to 99 lines. LibretroGLContext::CreateDrawContext got the same treatment — its glew init had been replaced outright rather than branched.
  • MemArenaHorizon for every POSIX target. The Horizon memory sources were added in the generic else arm of the platform chain in Makefile.common, and MemArenaPosix.cpp was dropped from it, so a Linux libretro build from that branch would not have linked. MemoryUtil.cpp had likewise been moved somewhere Windows, macOS and Android could not see it.
  • -DWITH_UPNP -DGLEW_STATIC -DGLEW_NO_GLU -DMINIUPNP_STATICLIB commented out in libretro/Makefile for all platforms, with -DUSE_FFMPEG hardcoded in their place (twice, as it turns out).

Two upstream oddities the Switch happened to expose

Worth a look independently of this PR:

  • libadrenotools is built for every arm64 libretro target by Makefile.common, though CMakeLists.txt links it only if(ANDROID) and its only consumer is behind #if PPSSPP_PLATFORM(ANDROID) && PPSSPP_ARCH(ARM64). Desktop aarch64 has been compiling it as dead weight; the Switch cannot compile it at all (no <sys/mman.h>).
  • cpu_features' hwcaps.c is likewise built unconditionally, while cpu_features' own CMakeLists builds it if(UNIX) — it needs getauxval through <sys/auxv.h> or dlopen, has neither here, and #errors.

What this does not do

  • No standalone build. PPSSPPSDL needs SDL3 since 34c2218, and devkitPro's Switch portlibs carry SDL2 only — configure stops at SDL3 not found. That is the wall [WIP] GitHub actions: Switch build #20195 has been up against since May, on top of the glslang/EGL issues discussed there; the libretro core sidesteps it because CMakeLists.txt gates the SDL lookup on NOT LIBRETRO.
  • GL only. Vulkan is excluded from the Switch build; libnx has no loader for it.
  • One compromise I would rather not leave: -D__unix in the libnx flags, because ext/aemu_postoffice's sock_impl.h reaches for <netinet/in.h> only under __unix/__APPLE__/__PSP__, leaving sockaddr_in incomplete otherwise. The real fix is a one-line change in that submodule; nothing else in the compiled set tests __unix (the two libretro-common users spell it __unix__).
  • The generated files under libretro/glsym/ and GLSwitch.h are a locally regenerated glsym set — the stock libretro-common one covers a different list of entry points. The other 27 files of the fork's vendored libretro-common copy are gone; they were shadowing the real submodule, which is how RETRO_DEVICE_ID_JOYPAD_MASK went missing.

CI

Both jobs are in: .gitlab-ci.yml for the buildbot, and a build_libretro_switch job in .github/workflows/build.yml so a change that breaks this gets caught here first. Same container, same make platform=libnx.

Testing

make platform=libnx in a devkitpro/devkita64 container, building ppsspp_libretro_libnx.a clean. I have no Switch to run it on, and no other platform was rebuilt here — the changes outside the libnx guards are restorations of what upstream already had, but they deserve a CI pass before this goes anywhere near a merge.

AI disclosure

Per the AI policy: the three commits authored by me were written by Claude Opus 5, through Claude Code, working from m4xw's branch under my direction. The conflict resolutions in the rebase of his three commits are from the same source. I made the calls about scope — port the core rather than the standalone, keep his authorship in the history, take the loader out of GLCommon.h rather than leave it — and the model did the work and wrote the commit messages and this description.

Verification was CI-driven and is exactly what the Testing section says: twenty builds in the devkitA64 container, each failure read out of the build log and fixed, until the core linked. No Switch hardware, and no local build of any other platform — which is the reason I flagged the non-libnx hunks for a CI pass above rather than claiming they are safe.

m4xw and others added 6 commits August 27, 2026 18:17
[Sqash me] Fixes after rebase

[Squash me] Revert reporting disable for standalone

[Squash me] Change GLInterface macro define for libretro purpose

Rework JIT Integration/Masking
-Needs more changes/reverts later on, WIP

WIP fixes after rebase, confirmed partially working at least

More safe buffer sizes

Make it link after rebase

gl3stub not required for switch
Note:
glBindFragDataLocationEXT, glGetProgramResourceLocationIndexEXT, glGetFragDataIndexEXT don't get currently populated

Dirty fix libnx libretro core

Compile and link after rebase for libretro

Compile and link after rebase for standalone

Fixes after rebase

[CI/CD] Libretro CI only for Switch

[CI/CD] Makefile path fix

Fixes after rebase

Split off libretro-common for Switch

[Libnx] Fixes after rebase

[Libnx] Remove leftover

Rebase fixes

Critical mitigation, cant call detach

Fixes after first batch of PR's

No longer required
Cherry-picked from the platform half of hrydgard#20195 (xerpi), which the out-of-tree
branch predates: libnx dropped virtmemReserve for virtmemFindAslr, and it now
defines BIT and BreakReason, which collide with Arm64Emitter and with the
rename block CommonTypes.h keeps around switch.h. Plus the header gaps that
only newlib has - no <sys/mman.h>, no <sys/syscall.h>, ENETDOWN sharing a
value with ESHUTDOWN, <climits> not arriving transitively.

Two hunks resolved against this branch rather than taken as they came:
sys/syscall.h stays excluded for the Switch only, not for every libretro
build (the Linux core's GetCurrentThreadIdForDebug goes through
syscall(__NR_gettid)), and SocketCompat.h keeps the libnx netdb/TCP_MAXSEG
block this port is known to build with.
The out-of-tree port replaced GLEW with a generated glsym loader by
commenting out the glew include and pasting ~6900 lines of extern
declarations and gl* redirections into GLCommon.h. Neither half was scoped:
`//#include "GL/glew.h"` applies to every GL target, and the block itself sat
behind `#if !defined(__LIBRETRO__) && !PPSSPP_PLATFORM(SWITCH) ... #else`, so
the desktop libretro core stopped using GLEW too.

Move the generated declarations into Common/GPU/OpenGL/GLSwitch.h and include
it only under PPSSPP_PLATFORM(SWITCH). GLCommon.h goes back to what upstream
has, plus that one branch - the file is 99 lines again instead of 6961, and
GLEW is restored everywhere it was in use.

Nothing else needed the Switch guards it had grown: USING_GLES2 is never
defined for libnx (the Makefile's GLES flag is not set there, so the build
takes -DHAVE_OPENGL), which is why the plain `#elif defined(USING_GLES2)` and
the gl3stub include are correct as they stand. The stale "../gfx_es2/gl3stub.h"
path goes with them.
The buildbot has been shipping a Switch core all along, from a separate fork
whose base is PPSSPP from March 2022. With the tree building for libnx again
it can come from here instead.

Additive: the CONSOLES section of the include list and the job list were both
empty, and the job goes through libretro/Makefile (.make-defs) rather than
cmake, because RetroArch links the core in statically on that platform and
libretro/CMakeLists.txt only knows how to build a shared library.
@hrydgard

hrydgard commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Looking good, but please follow our AI policy - in short, just mention what LLM you used.

Also, might as well go ahead and ask it to add github CI support as well as gitlab, so we won't break this.

So a change that breaks the Switch build gets caught here rather than on the
buildbot after the fact. Same container and same make invocation as the
GitLab job the previous commit adds - devkitA64 comes from the image, so
there is nothing to install.
@WizzardSK

Copy link
Copy Markdown
Author

Both done.

Disclosure is in the description now — Claude Opus 5 through Claude Code, with the split between what I decided and what it wrote, and what was and wasn't verified. Sorry for missing the policy on the way in.

GitHub CI added in a7d5c87: build_libretro_switch in build.yml, same devkitpro/devkita64 container and same make platform=libnx as the GitLab job, so this breaks here before it breaks on the buildbot rather than after.

One thing worth flagging while you have it open: the workflows on this PR are sitting at action_required, so nothing has actually run against these changes yet. The parts outside the libnx guards — GLEW going back into GLCommon.h and LibretroGLContext, MemArenaPosix and MemoryUtil.cpp back where the platform chain in Makefile.common had lost them — are restorations of what was already there, but they touch every GL and libretro target and I could not build any of them here. Those are the ones I would want the matrix to have an opinion on.

@hrydgard

Copy link
Copy Markdown
Owner

Approved PR to run on our github CI. Let's see how it goes.

@hrydgard

Copy link
Copy Markdown
Owner

OK, lots of CI failures. I'll leave them to you to solve.

CI found three unguarded hunks from the out-of-tree branch that had nothing
to do with the Switch:

- SDL/SDLGLGraphicsContext.cpp included <glsym/rglgen.h> unconditionally,
  which fails everywhere else - it is what killed gcc-normal, clang-normal,
  macos and the alpine headless job. The same file also forced
  SDL_GL_CONTEXT_PROFILE_MASK to 0 and SetGLCoreContext(false) for every
  non-GLES build, so desktop would have lost core profile. Reverted whole:
  the standalone Switch build is out of scope here, it needs an SDL3 that
  devkitPro does not ship.
- GPU/Debugger/GECommandTable.cpp had <cstring> replaced by <strings.h>, and
  GPU/Common/GPUDebugInterface.cpp had <strings.h> added outright. MSVC has
  neither, which is what the four Windows and UWP jobs stopped on. Keep the
  portable include and reach for <strings.h> only on the Switch, where newlib
  does not bring strcasecmp along - behind ppsspp_config.h, so the platform
  test resolves.

Also dropped the UI, portable-file-dialogs and imgui-console changes and the
new icon: all standalone, none of them compiled into the core.
@WizzardSK

Copy link
Copy Markdown
Author

Fixed in 786235f — could you approve another run when you get a chance?

Both causes were hunks the out-of-tree branch had left unguarded, and neither had anything to do with the Switch:

SDL/SDLGLGraphicsContext.cpp included <glsym/rglgen.h> unconditionally, which is what stopped gcc-normal, clang-normal, macos and the alpine headless job. Opening it turned up worse in the same file:

-		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
-		SetGLCoreContext(true);
+		SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, 0);
+		SetGLCoreContext(false);

— every non-GLES desktop build losing core profile. Reverted the file whole. The standalone Switch build is out of scope here anyway: PPSSPPSDL has needed SDL3 since 34c2218 and devkitPro's Switch portlibs only carry SDL2, so it stops at SDL3 not found before any of this matters.

<strings.h> was what the four Windows and UWP jobs hit. GECommandTable.cpp had <cstring> replaced by it rather than added to, and GPUDebugInterface.cpp had it added outright; MSVC has neither. The portable include is back, and <strings.h> is now reached for only on the Switch — where newlib does not bring strcasecmp along with the rest — behind ppsspp_config.h so the platform test actually resolves.

I also dropped the UI/, portable-file-dialogs, imgui-console and icon changes that came with the branch. None of them is compiled into the core, so they were only ever review surface.

make platform=libnx still links clean after all that, in the same container as the job here. The rest is on your matrix.

@hrydgard

Copy link
Copy Markdown
Owner

A new run is going.

@m4xw

m4xw commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Just so you know, i abandoned the libretro port for switch due to various compat and performance issues that simply didnt occur with my standalone builds and they are virtually identically and i didnt feel like chasing the needle...

@hrydgard

Copy link
Copy Markdown
Owner

@m4xw Good to know - if this continued vibe port works, are you ok with merging it? the changes look fine to me.

@WizzardSK Can you manually test and confirm that the current build from this branch works correctly on Switch hardware? That's the last other prerequisite for merging this.

@m4xw

m4xw commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

@m4xw Good to know - if this continued vibe port works, are you ok with merging it? the changes look fine to me.

@WizzardSK Can you manually test and confirm that the current build from this branch works correctly on Switch hardware? That's the last other prerequisite for merging this.

Well the libretro side should mostly work so this is a question of unfucking all the stuff i was too lazy to do clean to begin with. I already am in the progress of updating standalone for SDL3 but this wont matter for the LR port; other than tons of conflicts on my end.

Overall no hard feelings and if the quality is up to @hrydgard 's standard then i'll just take it as a new base

I should note tho, libretro was ALWAYS BUILT use the MAKEFILE for libnx

Only standalone was using cmake in my deployments.

@m4xw

m4xw commented Aug 28, 2026

Copy link
Copy Markdown
Contributor
* libnx dropped `virtmemReserve` for `virtmemFindAslr`, and now defines `BIT` and `BreakReason`, which collide with `Arm64Emitter` and with the rename block `CommonTypes.h` already keeps around `switch.h`.

Here a critical thing I forgot, at libretro we use my forked libnx which has the old JIT and virtmem implementation so this wont actually work on our CI/CD but only with upstream libnx

@WizzardSK

Copy link
Copy Markdown
Author

@WizzardSK Can you manually test and confirm that the current build from this branch works correctly on Switch hardware? That's the last other prerequisite for merging this.

Unfortunatelly, I don't have any Switch hardware. I just noticed that ppsspp is divided into 2 projects, so I wanted to try if there is any possibility to merge those 2 repos.

@hrydgard

Copy link
Copy Markdown
Owner

Without someone to test it, this can't be merged. So, go find someone to do it, and also take m4xw's comments into account.

@m4xw

m4xw commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Fwiw the virtmem stuff is a historic libretro infra issue, its done correct for the normal upstream libnx distributed builds, so maybe i can just add a define to the libnx fork that we can check here and either use the old or new way for virtmem, technically new way is more stable but it caused jit crash logs to be less deterministic or something like that.
This is something i want to eventually fix overall in our libretro infra, but it was so trivial that i just never bothered..

On another note, link what artifact you want tested, i put someone on it

@WizzardSK

Copy link
Copy Markdown
Author

Ok, I made a testing release on my repo https://github.com/WizzardSK/ppsspp-ci/releases/download/0.1/ppsspp-switch-core.zip

@m4xw

m4xw commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Ok, I made a testing release on my repo https://github.com/WizzardSK/ppsspp-ci/releases/download/0.1/ppsspp-switch-core.zip

Thats only a static library, you need to link this with retroarch, I cant do that locally since our toolchain missmatches

cb96567 swapped virtmemReserve() for virtmemFindAslr() as part of moving
the port onto a current libnx, but the two do not do the same thing. The
old call found free address space and reserved it. The new one only finds
it - libnx's own header says the manager mutex has to be held across the
find-and-map, and that virtmemAddReservation() is what stands in for the
mapping "when the memory won't be mapped straight away".

Neither applied here. Find4GBBase() took two 256 MB slices back to back
with no lock and nothing claiming either, and then mapped only the second
one. memoryBase is not mapped until CreateView() runs, so between the
lookup and that mapping there was nothing to stop the next caller - or the
second virtmemFindAslr() a line below - from being handed the same range.

So take the lock around both lookups and reserve each slice. The
reservations are deliberately never removed: the two addresses are cached
in statics and reused across ReleaseSpace()/Find4GBBase() cycles, and
ReleaseSpace() unmaps the code memory while keeping the address, so the
claim has to outlive the mapping.

memoryCodeBase now comes from virtmemFindCodeMemory() rather than
virtmemFindAslr(), since it is handed to svcMapProcessCodeMemory(), which
wants an address out of the code region and not out of general purpose
address space. This is the one behaviour change here that wants confirming
on hardware; the rest only stops address space from being handed out twice.

A failed lookup now returns nullptr instead of carrying on with a null
base. MemoryMap_Setup() already treats that as failure.
@WizzardSK

WizzardSK commented Aug 28, 2026

Copy link
Copy Markdown
Author

@m4xw Linked into RetroArch now: https://github.com/WizzardSK/ppsspp-ci/releases/download/0.2/retroarch_switch.nro

Single-core build, so drop it next to an existing RetroArch install for the assets. Core 2c5b37dcc, build.

Your libnx point may already be moot. This was built in reallibretroretroarch/libretro-build-libnx-devkitpro:v4-10-0, whose Dockerfile replaces libnx with your deprecated-features fork — and MemArenaHorizon.cpp, the only file that touches any of it, compiled and linked there clean. So the fork carries virtmemFindAslr, virtmemFindCodeMemory and virtmemAddReservation as well, and no define is needed. Worth confirming, since the v4-10-0 tag may predate that Dockerfile revision.

One heads-up before your tester starts: 2c5b37dcc fixes Find4GBBase(). Moving onto current libnx had swapped virtmemReserve() for virtmemFindAslr() one-for-one, but the old call reserved the space and the new one only finds it — two 256 MB slices were being taken unlocked and unclaimed, with only the second mapped on the spot. Both are now taken under virtmemLock() and reserved, and memoryCodeBase comes from virtmemFindCodeMemory() since it goes to svcMapProcessCodeMemory(). That last change may move where things land.

@hrydgard all 25 CI jobs are green including the new build_libretro_switch, so hardware testing is the only prerequisite still open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants