Skip to content

Nintendo 64

codingncaffeine edited this page Jun 6, 2026 · 1 revision

Nintendo 64

Emutastic supports two libretro N64 cores: parallel_n64 (default) and mupen64plus-next. Both are based on mupen64plus but differ significantly in architecture and plugin support.

Core Library Status
parallel_n64 parallel_n64_libretro.so Default — what Emutastic launches an N64 game with unless overridden per-game. On Linux it runs the GLideN64 (glide64) OpenGL plugin, not ParaLLEl-RDP (see below)
mupen64plus-next mupen64plus_next_libretro.so Alternative — supports GlideN64 (OpenGL) and (on builds with Vulkan) ParaLLEl-RDP; statically links GlideN64 which has teardown implications

Linux renderer note: On Linux, N64 hardware rendering goes through the OpenGL path (a GLideN64-style GL context rendering into an FBO), not a Vulkan swapchain. ParaLLEl-RDP is Vulkan-only; the Linux build runs parallel-n64-gfxplugin = glide64 so the core uses GPU-accelerated GL HLE instead of falling back to the slow software (angrylion) renderer. The Vulkan/ParaLLEl-RDP integration documented for the Windows build does not apply here — the upscaling and accuracy advantages of ParaLLEl-RDP are deferred until a Vulkan HW-render path lands. See N64Handler.cs.

Visual Upgrades

The N64 renders natively at 320x240. On Linux, resolution scaling is handled by GLideN64 (parallel-n64-screensize for parallel_n64, or mupen64plus-43screensize / mupen64plus-169screensize for mupen64plus-next):

Option Native Upscaled (suggested)
parallel-n64-screensize 320x240 640x480 or higher (1280x960)
mupen64plus-43screensize* 320x240 640x480 or 1280x960

* mupen64plus-next only. GlideN64 also supports MSAA (up to 16x), FXAA, and texture enhancement filters (xBRZ, HQ variants) on either core.

Note: The parallel-n64-parallel-rdp-upscaling option is exposed in the cog menu (labelled "Upscaling ⚠ restart") but only affects the ParaLLEl-RDP plugin, which the Linux build does not use by default. With glide64 active, use the screensize options for resolution.

Graphics Plugins (parallel_n64)

parallel_n64 selects its plugin via parallel-n64-gfxplugin:

Plugin Option Value Renderer Status
glide64 (GLideN64) glide64 OpenGL Default on Linux — GPU-accelerated HLE, good compatibility, resolution scaling via screensize options
parallel (ParaLLEl-RDP) parallel Vulkan compute Bit-accurate LLE, GPU-upscaling — Vulkan-only, not active on the current Linux build

GLideN64 (OpenGL)

GLideN64 uses the standard libretro OpenGL HW rendering path (RETRO_HW_CONTEXT_OPENGL). On Linux the game runs in the separate --game-host process: the core renders into an FBO via a GL context (GLX or EGL), and the frontend reads back the pixels through a never-blocking 4-slot PBO readback ring so the GPU never stalls waiting on the CPU. This is the same path PPSSPP, Dolphin, and other OpenGL cores use on Linux.

GLideN64 works out of the box — resolution scaling, save states, and basic gameplay all function without special handling.

Core Options (glide64)

parallel-n64-gfxplugin                = glide64
parallel-n64-cpucore                  = dynamic_recompiler
parallel-n64-audio-buffer-size        = 2048      (only supports 2048 or 1024; 4096 is rejected)
parallel-n64-screensize               = 640x480   (4:3; affects glide64, NOT ParaLLEl-RDP)
parallel-n64-pak1                     = memory

Shared Context

parallel_n64 is built on mupen64plus, which always spawns an internal EmuThread even for software plugins. The Linux handler returns false for SET_HW_SHARED_CONTEXT (AllowHwSharedContext => false): glide64 then runs on our own emu thread, sharing the same GL context as context_reset and the FBO readback. Returning true would put rendering on mupen64plus's own EmuThread with a separate GL context where our context_reset FBOs aren't visible — a black screen. See N64Handler.cs.

Teardown (OpenGL)

mupen64plus uses global state, so all cleanup must be synchronous before relaunching, and the core library must be fully unloaded between sessions. The Linux build defers the previous session's core handle and dlcloses it (via .NET NativeLibrary) before the next dlopen — if the order were reversed, dlopen would bump the refcount on the still-loaded .so and the new dlclose would only decrement it, so the library never unloads and globals stay stale, crashing retro_run on the second session. Key ordering rules carried over from the Windows port:

  1. retro_deinit must run with a current GL context
  2. Skip context_destroy — the EmuThread is still running cleanup
  3. Release/destroy the GL context after a short drain delay (the driver may call back into the core)
  4. dlclose the previous core before the next dlopen

Presentation & In-Game Overlay

On Linux the game window is a native Wayland xdg_toplevel that owns its own EGL surface and swaps directly (RetroArch's model — a bare top-level reaches a clean ~60fps where a composited SDL surface caps lower). SDL3 stays in the loop for gamepad and audio; the toplevel owns the game window and keyboard. There's an X11/SDL fallback when Wayland isn't available. See WlToplevelPresenter.cs.

The in-game overlay (pause HUD pill, cog menu, save/load slots, recording controls) is GL-drawn directly into the game window via Skia → GL upload — see GlOsd.cs. There's no separate overlay window and no Win32 layered-window airspace problem to work around (those were Windows/WPF-specific). The overlay feature set matches the Windows build.

Hotkeys: F5 save state, F7 quick load, F9 record, F11 fullscreen, F12 / PrintScreen screenshot.

Controller Pak Swap (Memory ↔ Rumble)

The N64 controller has a single accessory slot — it can hold either a Memory Pak (saves) or a Rumble Pak (vibration), but not both at once. This is a hardware limitation of the original console, and games that use both (Forsaken 64, Banjo-Kazooie, Ocarina of Time, etc.) only see whichever pak is currently inserted.

When playing an N64 game, the cog menu in the in-game overlay shows a P1 Pak entry. Clicking it cycles Player 1's accessory between Memory Pak and Rumble Pak. The change is persisted to the core's saved options and the core picks it up immediately via check_variables() — no relaunch required for parallel_n64.

A typical workflow for a game that wants both:

  1. Boot the game with P1 Pak: Memory Pak so it can find existing saves.
  2. After the title screen / save load, swap to P1 Pak: Rumble Pak for gameplay feedback.
  3. Swap back to Memory Pak before saving in-game.

For non-Player-1 ports or for the Transfer Pak (titles that use it for save transfer with cartridge games), use the full Core Options panel to set parallel-n64-pak2/3/4 or pick transfer directly.

Emulation Speed / Timing

The N64 path uses audio-drain timing to pace retro_run. Input is polled via SDL3 gamepad and audio is delivered through an SDL3 audio stream. Pacing keys off the audio buffer draining at the native rate; the GL toplevel swap presents tear-free on top of that. (See Emulation Timing.)

Audio Buffer Size

parallel-n64-audio-buffer-size only accepts 2048 or 1024; passing 4096 is rejected by the core. The Linux default is 2048.

Clone this wiki locally