Inside Libretro: The Core/Frontend Architecture Behind RetroArch
RetroArch supports dozens of systems without reimplementing shaders, netplay, or rewind for each one — because the emulator logic and everything around it are deliberately different programs.
RetroArch supports several dozen systems — NES, SNES, Genesis, PlayStation, N64, arcade boards, and far more — without each one having its own separate settings menu, its own shader pipeline, its own netplay implementation, or its own rewind feature. That’s not because someone wrote all of that logic dozens of times over; it’s because RetroArch’s actual emulation logic for any given system is deliberately not part of RetroArch itself.
The split: core and frontend
The libretro API defines a small, fixed set of C functions any emulator can implement — initialize, load a ROM, run one frame, read/write save-RAM, serialize/deserialize state — compiled into a shared library called a core. A frontend (RetroArch is the reference implementation, though not the only one) loads a core, calls those functions in a defined sequence, and handles everything the core itself doesn’t: video output, audio mixing, input mapping, on-screen menus, configuration, and save file management.
┌─────────────────────────────┐
│ RetroArch (frontend) │ video, audio, input, shaders,
│ │ netplay, rewind, achievements,
│ │ UI — the same code for every core
└─────────────┬───────────────┘
│ libretro API calls:
│ retro_init(), retro_load_game(),
│ retro_run(), retro_serialize(), ...
┌─────────────▼───────────────┐
│ snes9x_libretro.so (core) │ the actual SNES emulation logic —
│ (or any other core) │ CPU/PPU/APU emulation, nothing else
└──────────────────────────────┘
A core’s job shrinks down to just accurately emulating one system. It doesn’t open a window, doesn’t touch an audio device, doesn’t read a gamepad directly — it hands the frontend a frame buffer and an audio buffer each frame, and receives input state as plain function parameters.
Why this decoupling is the actual point
Because shaders, rewind, netplay, and achievement-tracking all live in the frontend rather than in each core, a core author gets all of them essentially for free, correctly, the moment their core implements the standard API — rather than needing to reimplement rollback-capable state serialization or a shader pipeline once per system. Conversely, an improvement to RetroArch’s shader system or netplay code benefits every core simultaneously, without needing to touch a single line of any individual emulator’s logic.
Where this architecture came from
This design wasn’t RetroArch’s starting point — it grew into it. The project began in 2010 as SSNES, a lightweight SNES-only frontend built by Hans-Kristian “Themaister” Arntzen on top of libsnes, a decoupled SNES emulation backend designed by the pseudonymous developer Near specifically to separate emulation logic from frontend concerns. On April 21, 2012, SSNES was renamed RetroArch to reflect a broader ambition: generalizing that same core/frontend split — originally built for one system — into “libretro,” an API any emulator for any system could implement, with RetroArch itself becoming a frontend for all of them rather than only SNES.
The trade-off: a lowest-common-denominator API
A single API covering wildly different hardware — a Game Boy versus a PlayStation 2 — necessarily can’t expose every system-specific quirk directly; it has to define a reasonably generic contract (frame buffers, audio buffers, generic input state, generic save-state blobs) that every core adapts its own internals to fit. This occasionally shows up as friction — a system with an unusual output format or an exotic peripheral needs a core author to work around the generic API’s assumptions — but it’s a deliberate, worthwhile trade against the alternative of a frontend that has to know something special about every system it supports.
Why this matters beyond RetroArch specifically
The core/frontend split is a genuine instance of a broader software design principle — separating a narrow, well-defined “what” (emulate this exact hardware) from a broad, shared “everything else” (how the user sees, hears, and interacts with it) — applied to a domain where, historically, almost every emulator had bundled both together. The result is less a single emulator than a shared platform that dozens of independent emulation projects plug into.