100 Commits
Author SHA1 Message Date
LibretroAdmin 4ac4e8d19b Output: lock-free hand-over queue between the GPU and frontend threads
The GPU thread publishes a picture's description into one of four slots
and the generation naming it into an atomic word; the frontend's thread
claims it with a CAS that sets a taken bit, and the GPU thread reclaims
an untaken target with a CAS on the same word, so exactly one of them
owns it. What is being handed over and what was handed are atomic
tags only the frontend's thread writes, the first before its claim.
No slock on either side.
2026-11-07 15:18:37 +01:00
LibretroAdmin 0bd8e77a4d D3D11/D3D12: backend info from the frontend's device only
The frontend picks the adapter and hands over its device, so the core no
longer loads dxgi.dll, creates DXGI factories, enumerates adapters or
creates throwaway D3D11/D3D12 devices to query AA modes and logic op
support at load. Until the device arrives the AA modes are the counts
every device has; the context reset fills them in from the real device
(D3D12 now too) and re-validates the MSAA setting against them.
2026-11-07 15:18:33 +01:00
LibretroAdmin 73c3c0baee Remove Movie (TAS input recording/playback)
Nothing in the core starts a recording or a playback, so a movie is
never active. Each call site keeps its no-movie path: no determinism
toggle, the real clock for the RTC, the configured memory cards, no
pad/Wiimote input mirroring into display strings on every poll. The
state keeps the movie counters' bytes, written as zeroes, so existing
savestates still load.
2026-11-07 15:12:31 +01:00
LibretroAdmin 5e7772c97e Follow-ups to the LIBRETRO ifdef removal
D3D11 sets logic op support from the device again (off on UWP) and
always reports object names: the UWP check's else had swallowed the
object-name line and logic op was never on. The Android input backend
is no longer created, so its source, include and CIFACE_USE_ANDROID go.
Core.h declarations indented with spaces like the rest of the file.
2026-11-07 14:55:52 +01:00
LibretroAdmin aa9a6e9140 Triforce, MagCard, Bluetooth: bytes lent as pointer and size, no spans or maps
Serial devices lend their Rx bytes in place and take Tx as pointer plus
size; JVS frames unescape onto the stack and are consumed without a
scope guard. IO port inputs and outputs are plain pointers with fixed
counts. Mediaboard addresses resolve to a lent pointer and size; the
game table is a flat array. Bluetooth passthrough keeps its link keys
in a fixed sorted array, links live transfers through the header that
already fronts each buffer, and lends ACL packets as pointer and size.
AsU8Span/AsWritableU8Span are gone from BitUtils.
2026-11-07 14:55:48 +01:00
libretroadmin c878c0d818 remove LIBRETRO ifdefs 2026-11-07 14:23:51 +01:00
LibretroAdmin cd38818e52 Common: pointer and size instead of spans, loops instead of STL algorithms
SHA1, HMAC, hex dumps, PNG loading, file search, DHCP options, HTTP
posts and DirectIOFile reads and writes take pointer plus size; callers
pass their buffers straight through. Contains.h is gone; lookups are
plain loops. File search and FAT FST sorting are in-place heap sorts;
min/max/clamp/copy/fill become ternaries, memcpy and memset. Watch
string import and export had no callers and are removed.
2026-11-07 13:37:26 +01:00
LibretroAdmin 6e9be4ba64 Common: drop the Gekko assembler, nothing in the core calls it 2026-11-07 13:37:22 +01:00
LibretroAdmin c033a61899 DiscIO: pointer and size instead of spans, loops instead of STL algorithms
Names, game IDs, patch lists, LBA ranges and hash exceptions are passed
as pointer plus count. min/max/fill/copy become ternaries, memset and
memcpy; FST entries sort with an in-place heap sort, partitions with an
insertion sort. WAD content checks decrypt and hash in 2 KiB chunks
straight from the borrowed input; banner names read onto the stack.
2026-11-07 12:55:07 +01:00
LibretroAdmin 23b8d27c35 IOS FS: host files shared through a fixed refcounted table
Guest handles index one of sixteen host file slots; the last handle to
close a path closes its file. No shared_ptr, weak_ptr or map. Directory
order is an insertion sort over FST positions; lookups are plain loops.
2026-11-07 12:55:04 +01:00
LibretroAdmin 4a6a6489fc IOS ES: bytes lent as pointer and size, no spans or STL algorithms
Signed blobs, TMDs, tickets, certs and signatures are lent as pointer
plus size; readers are filled in place from guest memory and disc
reads. Algorithm calls become plain loops and memcpy. WiiUtils keeps
only WAD install, TMD recovery and the Bluetooth getters the core uses.
2026-11-07 12:55:00 +01:00
LibretroAdmin 15d144087d IOS ES: views written into guest memory, signatures and keys lent
A TMD view and a ticket view are written straight into the guest's
output buffer, the TMD view's size computed rather than measured off a
view built to be thrown away. A ticket, a signature and a public key are
spans into the blob they are part of instead of copies in vectors. A TMD
or ticket the guest passes to the DI view calls is read into its reader
straight from guest memory.
2026-11-07 12:22:51 +01:00
LibretroAdmin 139497cecc IOS ES: signed blobs in one malloc'd buffer, filled in place
A ticket, TMD or certificate holds its bytes in one malloc'd buffer and
hands them out as a span. ResizeBytes() gives the room to fill, so a
ticket or TMD is read from the NAND, a disc, a WAD or guest memory
straight into its reader instead of into a std::vector that is then
moved or copied in.

Ticket, TMD and certificate chain imports take spans, and the IPC
handlers pass the guest's buffers where they lie instead of copying
them out into vectors first. A certificate is read where it lies and
only its own bytes are copied, so parsing a chain no longer copies the
rest of the chain once per certificate. Deleting a ticket closes up the
ones kept in place. The savestate layout is unchanged.
2026-11-07 06:35:01 +01:00
LibretroAdmin 734ceaa90c GBA: ROM, BIOS and saves through the frontend's VFS
The GBA core's files open as an mGBA VFile over a libretro-common
filestream (GBAVFile.c). A read-only map of a mapped file lends the
mapping itself, so the ROM and the BIOS are read where they lie; a save
is mapped into a buffer of its own and written back when mGBA syncs it.
A ROM in an archive or a zip is read or inflated straight into the
memory file mGBA maps, with no buffer in between.

The state and save import/export, e-Reader card queueing, ROM info and
forced disconnect entry points nothing calls are gone.
2026-11-07 06:27:34 +01:00
LibretroAdmin 4d9402dd6e Texture hashes through libretro-common's xxh64
TextureInfo hashes textures and palettes with libretro-common's
xxh64(), the same value XXH64() gives, so dumped and custom texture
names stay as they are. The pipeline hash helpers nothing calls are
gone, and with them Externals/xxhash.
2026-11-07 06:12:53 +01:00
LibretroAdmin 6fe348ec38 Vulkan: device memory from a heap of its own, in C
VKHeap.c takes large blocks from the driver and hands out offsets in
them: the lowest free span that fits, spans kept sorted so a free is a
binary search and a merge. The handle is a small struct kept by value,
so nothing is allocated per allocation. Host-visible blocks are mapped
once and stay mapped; stream buffers and staging buffers write straight
into the memory the GPU reads. Buffers and linear images keep to blocks
of their own, apart from optimal images. What the driver requires or
prefers dedicated, and anything of half a block or more, gets memory of
its own sized to fit. Host blocks are 16 MiB, device blocks 64 MiB, and
neither more than an eighth of its driver heap; one empty block of each
kind is kept and the rest go back to the driver.

The Vulkan Memory Allocator is gone, and with it the memory budget
extension only it used.
2026-11-07 06:06:08 +01:00
LibretroAdmin 3074d3caa0 Riivolution: memory patch values lent, not copied
A memory patch's value file is lent through File::LoanFile and handed to
the patcher as a span, and a value given inline is used where the parser
left it, instead of either being copied into a fresh std::vector each
time a patch is applied.
2026-11-07 05:45:58 +01:00
LibretroAdmin e08e1bd766 Common: whole files lent through File::LoanFile
LoanFile() lends a whole file for reading: straight from its mapping
where the file is mapped, from one malloc'd buffer otherwise; EndLoan()
gives it back. The SSL certificates are loaned through it.
2026-11-07 05:45:54 +01:00
LibretroAdmin e072ee0871 Metal: last finished draw on retro_atomic
The draw counter the command buffer completion handler publishes and the
upload allocators read is a retro_atomic 64-bit word.
2026-11-07 04:50:32 +01:00
LibretroAdmin eb0af9afac DirectoryBlob: headers and FST built in the bytes they are served from
The disc header, BI2, apploader, FST, partition table, region data and
H3 table are each put together in the shared allocation the content
chunk serves them from - read from the host file or the wrapped volume
straight into it, then patched in place - instead of in a std::vector
that Add() copied. The partition header and the bytes read from
header.bin and region.bin go through stack arrays. The FST sort compares
names case-folded in place instead of upper-casing two std::string
copies per comparison.
2026-11-07 04:50:29 +01:00
LibretroAdmin a9e048fee2 DirectoryBlob: content chunks in one sorted array
A disc's content chunks sit in one malloc'd array sorted by end offset
and found by binary search, in place of a std::set walked node by node.
A chunk is plain data - offset, size, kind, source offset - read through
a switch rather than a std::variant visited per read.

Where a chunk's bytes come from is a small C struct; the bytes it needs
(a host file's path, a run of memory) live in one reference-counted
allocation shared by every chunk and copy that refers to them, in place
of a std::string per chunk and a shared_ptr to a std::vector. Splitting
a memory chunk for a Riivolution patch moves its offset into the shared
bytes instead of copying the tail into a new vector.

The reference counts and the open-file slots' flags are retro_atomic.
2026-11-07 04:50:26 +01:00
LibretroAdmin 09cac91415 DirectoryBlob: host files kept open across reads
A read from an extracted game's file no longer opens, maps, unmaps and
closes the file every time. The reader keeps 16 slots of open (and,
where the platform maps, mapped) files, picked by a hash of the path; a
read owns its slot for its length through an atomic flag, so reads stay
lock-free, and copies straight out of the mapping. A read finding its
slot taken opens the file for itself, as before. No allocation: the
slots, paths included, live in the reader.

An extracted disc streaming two 600 KiB pictures alternately: 2 opens
over 120 frames instead of 218, pictures identical.
2026-11-07 04:24:31 +01:00
LibretroAdmin 9ffbc33572 IOS SSL: certificates parsed from their mapping
The built-in client certificate, key and root CA are hashed and handed
to mbedtls straight from the mapped file instead of a vector filled by
a read; where the file can't be mapped, one malloc'd buffer holds it.
The path is put together in a stack buffer.
2026-11-07 04:24:27 +01:00
LibretroAdmin 5c90c84330 DiscIO: drop NANDImporter
Nothing the core builds imports a NAND backup; only the Android
frontend's WiiUtils, which is not part of the libretro build, called it.
2026-11-07 04:24:25 +01:00
LibretroAdmin 7278b0416c Common: file layer on const char* paths
IOFile, DirectIOFile and the FileUtil primitives (Exists, IsDirectory,
IsFile, GetSize, CreateDir, Delete, DeleteDir, Rename) take C strings;
the std::string forms are inline forwards, so callers holding a literal
or a stack buffer build no temporary. DirectIOFile keeps its path in one
malloc'd buffer. CreateDirs and CreateFullPath cut the directory part
into a stack buffer instead of a heap string.
2026-11-07 04:17:01 +01:00
LibretroAdmin 4eb9b9450e Wii partitions: blocks decrypted where they lie, into where they go
An encrypted block of a plain image is decrypted straight from the
image's mapping; one the reader cannot lend is read into the volume's
own block buffer, not a new allocation per read. A read that covers a
block's whole data is decrypted straight into the caller's buffer; only
a partial one goes through the decrypted-block cache.
2026-11-07 04:16:57 +01:00
LibretroAdmin 49eb4ab516 I/O paths: plain buffers instead of STL containers and new
The line reader's window, the disk cache's append and alignment
buffers, and the DVD thread's result buffers are malloc'd and grow to
the largest request, then stay. Pending lent DVD reads are a fixed
array, and results are plain structs taken from and given back to the
free stack. A memory card's dirty blocks are a bitmap walked a word at
a time. A GCI flush job is one malloc - the job, its blocks, its name -
and the flush walks the drained chain itself. A page programmed over
EXI is written a run at a time, not a byte at a time.
2026-11-07 03:48:44 +01:00
LibretroAdmin 7ac17da8f9 Boot executables lent from where they lie
A DOL or ELF on the host is read where it lies in its mapped file, or
read once into a malloc'd buffer where it cannot be mapped; one handed
over by the caller is used in place, freed by the reader only when it
takes ownership. A DOL's sections are a fixed array of pointers into
it, copied straight into emulated memory, and an ancast body is
decrypted straight into emulated memory behind its header. An ELF,
whose headers are swapped in place, is copied out of a mapping only
then; the ARM binary IOS boots is swapped where it was read, its ELF
never copied out.
2026-11-07 03:48:40 +01:00
LibretroAdmin ce91925b34 DVD thread: results and their buffers used again
A result the DVD thread made goes back to it once the CPU thread has
finished with it, buffer and all, through a lock-free stack the DVD
thread drains when it runs out. Reads of compressed, encrypted and
assembled images allocate nothing once there are as many results as
reads in flight.
2026-11-07 03:27:42 +01:00
LibretroAdmin 54b8264a05 Caches and saves: read where they lie, write what changed
Shader caches are walked where their entries lie in the mapped file,
each value handed to its reader from there - copied only when
misaligned for its type - with no allocation or read call per entry.
An entry is appended in one write. The Vulkan pipeline cache is made
from the stored blob in place, and stored again only when it changed,
atomically.

A raw memory card writes back only the blocks that changed, a run of
them at a time, keeping a block marked until it is written. A GCI save
is replaced whole or not at all, in two writes. The Gecko code handler
is read from its file straight into emulated memory, the DSP ROMs are
swapped out of the mapping, and system menu files go into the NAND from
the mapping.
2026-11-07 03:27:39 +01:00
LibretroAdmin 30a5010c5a DVD: reads of a plain image copied straight from its mapping
A GameCube disc in a plain image is lent by the volume as it lies in
the mapped file. Such a read never goes to the DVD thread and has no
buffer: it starts by asking the OS to prefetch its pages, and when it
is due its bytes go from the mapping into emulated memory, or to the
DTK stream, in one copy. Compressed, encrypted and assembled images
keep the DVD thread.

A savestate stores pending lent reads as the results the DVD thread
would have made, so states keep their format, and a disc change turns
them into results before the old image goes.
2026-11-07 03:27:04 +01:00
LibretroAdmin 2323f96cf4 File I/O: one path, libretro-common's filestream and VFS
Every file the core touches goes through libretro-common's filestream,
path and dirent layers, taken from RetroArch: vfs_hybrid opens local
paths with libretro-common's own implementation and hands URI paths
(and, on Android, anything a local open cannot reach) to the
frontend's VFS. File::IOFile and File::DirectIOFile sit on RFILE, and
FileUtil, FileSearch and the directory walks on path_stat, path_mkdir
and retro_opendir. Dolphin's fopen, POSIX and Win32 file code,
std::filesystem and std::fstream are gone, with the dual VFS paths and
the core option that chose between them.

Files opened for reading alone are memory-mapped where the platform
can: reads copy straight out of the page cache, DirectIOFile's offset
reads on one mapped object are thread safe, and GetMappedPtr(), View()
and Prefetch() lend and warm without a copy. Text files are read a
line at a time through File::LineReader, lent from the mapping or a
window refilled a block at a time, never a byte at a time; JSON is
parsed straight from the mapping. Ini files and the cheat file are
built in memory and written once, an ini only when it changed and
then atomically.
2026-11-07 03:27:01 +01:00
LibretroAdmin bdd075aa7f JIT: a cleared code space poisons only what was written since
Every savestate load clears the JIT cache. A code block keeps how far
into its region code may have been written since it was last poisoned:
the pointer its emitter leaves and the one it takes up, through
SetCodePtr and ResetCodePtr, and where it is when cleared. A clear
poisons up to there, and the untouched rest of the near and far code
regions, over a hundred megabytes, is neither written nor committed.

On x86-64 and the cached interpreter everything written lies below that
mark. AArch64's JIT writes its regions through other blocks' emitters,
so its blocks poison their regions whole.

A savestate load, repeated as rewind and run-ahead do, takes 10 to 12
ms; the first one 70.
2026-11-07 02:25:50 +01:00
LibretroAdmin c265e74c05 Software renderer: pictures handed to the frontend in memory, no GL
The software and null renderers run without a hardware context. They
come up at the end of retro_load_game, where a frontend with a context
would call context_reset, and go down with the rest at unload.

The software renderer's picture goes to video_refresh straight from its
XFB texture, in one pass into XRGB8888 order, into a buffer that grows
only with the picture: no GL texture upload, no draw, no window of its
own. The GPU thread's g_gfx is the backend's own from the start.
2026-11-07 02:05:40 +01:00
LibretroAdmin bc09a84dd1 GL: programs compiled on the driver's threads, drawn with once linked
Where the driver compiles on threads of its own
(GL_KHR_parallel_shader_compile or the ARB one), the asynchronous
shader modes on OpenGL compile in the background on the frontend's
context alone: a missing program's compile and link are started, a
couple of work items a frame, and its draws use the ubershaders, or
are skipped, until GL_COMPLETION_STATUS says the link is done; its
link is checked, its variables set and its binary written to the cache
then. A program drawn with before it is done is waited for there. A
software rasterizer, which compiles when it first draws, compiles on
the spot as before.

The shader compiler gets a deferred mode for it, without threads,
compiling the items waiting a few at a time, and AbstractPipeline tells
whether a pipeline's compile is still running and whether it can be
drawn with. With the driver's threads alone, every ubershader pipeline
there can be is not built in the background.
2026-11-07 02:05:36 +01:00
LibretroAdmin a390228b51 GL: no contexts of the core's own, the frontend's libretro GL context alone
The OpenGL renderer draws on the context the frontend hands over
through libretro GL, and the core makes no context of its own: the
platform context code goes, EGL, EGLX11, EGL for Android, WGL, AGL,
BGL and the GLX window, with the shared contexts made for the shader
compiler's threads, and GLContext::Create makes the libretro context
alone. Programs are compiled on the frontend's context, so the OpenGL
backend reports no background compiling and its shader compiler runs
no threads; the shared-context compiler, its glFinish and VAO rebinds
and the driver bug that switched it off go with it. Android no longer
links libEGL.
2026-11-07 01:36:34 +01:00
LibretroAdmin 94849325bd libretro GL: shared contexts for the shader compiler's threads everywhere
The shader compiler's threads on OpenGL get contexts sharing the
frontend's on every platform but Apple's: EGL, then GLX, on Linux, the
other Unixes and Android, and WGL on Windows, each loaded at runtime
from the library the frontend has loaded, so the core links none of
them. With compiler threads OpenGL draws with ubershaders while each
draw's own program compiles in the background, as the other backends
do, instead of compiling it on the spot.

GLX finds the frontend's FBConfig among the screen's by its ID where
glXChooseFBConfig matches none, as Mesa's does; WGL makes its contexts
current on a 1x1 pbuffer of the frontend's pixel format, or on the
frontend's DC without WGL_ARB_pbuffer.
2026-11-07 01:23:47 +01:00
LibretroAdmin eb6f00332e Vulkan: ubershader pipelines linked from libraries of their parts
Where the device has VK_EXT_graphics_pipeline_library with fast
linking, an ubershader pipeline, the one built on the spot when a draw
first needs it, is linked from libraries of its four parts instead of
built whole: the vertex input, the vertex and geometry shaders, the
pixel shader and the colour output, each found or built with only what
that part takes. With cull mode, depth state and blending set per draw
the parts are shared widely; a pipeline whose parts exist is linked in
well under a millisecond, where building it whole took tens, and only
a shader's first use builds its library.

The libraries live in the shared pipeline table until the backend
shuts down. A link that fails builds the pipeline whole.
2026-11-07 01:12:01 +01:00
LibretroAdmin 17619fae48 Vulkan: blending set per draw too, one pipeline for what differs there
Where the device has VK_EXT_extended_dynamic_state3's colour blend
enable, equation and write mask, a GX or ubershader pipeline's blending
is set per draw with its cull mode and depth state, and left out of the
pipeline and of the key it is shared by: pipelines that differ only in
blend enable, factors, equations or which channels are written are one
VkPipeline. Logic op and dual-source blending stay in the pipeline, the
latter as it decides the shader's outputs. The state tracker sets the
blending for every colour attachment when it, or the attachments,
differ from what the command buffer has set already.
2026-11-07 00:58:09 +01:00
LibretroAdmin 6bdbdfefd5 TextureCache: EFB copies kept across a savestate load on OpenGL too
Loading a state on OpenGL no longer throws the texture cache away after
restoring it: the EFB copies the state carries, the XFB copies among
them, are found again as on the other backends, instead of the next
picture being decoded from RAM, which the copies never wrote, and shown
as garbage.
2026-11-07 00:43:00 +01:00
LibretroAdmin 6eecf67db6 Core: FIFO logs play in single core, a field each retro_run
In single core the FIFO player's CPU core is injected and left for
RunSingleFrame to run a field at a time, as the CPU thread function
leaves the PowerPC core, instead of entering its blocking run loop on
the frontend's thread, which never returned from the first retro_run.
It is taken out again at unload, before the hardware it drives is shut
down.
2026-11-07 00:42:55 +01:00
LibretroAdmin 16908c6e98 FifoPlayer: no waiting on the GPU once the CPU is asked to stop running
The FIFO player waits after each frame for the GPU to be idle; it now
stops waiting as soon as the CPU is asked to pause, as it already does
while the FIFO is full. A savestate taken while a FIFO log played
waited for the FIFO player's thread to pause, which waited for a GPU
loop that only runs inside retro_run: retro_serialize_size never
returned.
2026-11-07 00:42:51 +01:00
LibretroAdmin 441a0f1762 Vulkan: cull mode and depth state per draw, one pipeline for the rest
Where the device has VK_EXT_extended_dynamic_state, the cull mode and
the depth test, write and compare function of a GX or ubershader
pipeline are set per draw, and pipelines that differ only there are one
VkPipeline: a pipeline asked for whose shaders, vertex layout, blending,
topology and framebuffer match one already built takes that one at once
instead of being compiled. Fewer pipelines are compiled, in the
background and on the spot alike.

The shared pipelines sit in a fixed open-addressed table every thread
building pipelines reads and adds to without a lock, counted by the
pipelines using them; a shader is told apart by a number given it when
it is made, not by its address. The state tracker sets only what
differs from what the command buffer has set already, and sets it again
after a pipeline with the state built in.

The colour blend attachments of a pipeline are kept on the stack.
2026-11-07 00:42:47 +01:00
LibretroAdmin 2a77d9ced6 libretro Vulkan: an instance of the version the loader has
The application info the core hands the frontend for its instance asks
for the highest version the Vulkan loader has, up to 1.2, instead of
1.0. The backend calls the 1.1 queries (vkGetPhysicalDeviceProperties2
and Features2) where the device has them; on a 1.0 instance those were
invalid, and the loader answered them by emulation that drops the
structures chained to them, so no extension feature could be queried.
The memory allocator is told the lower of the instance's and the
device's versions.
2026-11-07 00:42:44 +01:00
LibretroAdmin 2dd80c5c2e VideoCommon: shader compiler workers below the emulation threads
The shader compiler's worker threads run at a lower priority than the
threads that emulate, on Windows (below normal) and Linux (nice 5): on
a machine with fewer cores than busy threads, what compiles in the
background waits, not the frame being emulated. Common gets
LowerCurrentThreadPriority for it.
2026-11-06 23:45:19 +01:00
LibretroAdmin cd384472e6 rslc: DXBC declares only the constant buffers its code reads
A uniform block a shader declares and never reads is no longer declared
in its DXBC, as fxc does: on Direct3D 12 every constant buffer declared
must have a slot in the root signature, so pipeline creation failed for
any shader that declared one it had none for. Dolphin's ubershader
vertex shader with the dynamic vertex loader declares its geometry
block without reading it, and every ubershader pipeline on D3D12 failed
to build: draws that needed one were skipped.
2026-11-06 23:45:15 +01:00
LibretroAdmin ba1e153dad libretro: one picture a run from the inline outputs too
On OpenGL and Direct3D 11, and on Vulkan and Direct3D 12 in single core,
the GPU loop runs inside retro_run, and a run after one that ran out of
time catches up on two fields: both presented, and the frontend's
video_refresh was called twice in one run. A picture drawn or handed
over on the hardware is now reported once, at the end of the run, the
last one drawn, as the threaded outputs already did.

Direct3D 11 version 1 is the exception, as the frontend reads its
texture inside the callback: that output reports at once, the first
picture of a run, and drops a second. So do pictures in memory
(software rendering).

What the output hands over in place, the XFB texture itself, may have
been read in a layout of the core's own since it was presented, as a
copy's source say: before the frontend is told, it goes back to the one
the frontend reads it in, as it already did before a repeat
(FrameOutput::KeepReadable, which Repeat was).
2026-11-06 23:45:12 +01:00
LibretroAdmin e4bac5f75c Shaders: ubershaders by default, seeded per game
Shader compilation defaults to Async (UberShaders): a draw whose own
pipeline is not built yet is drawn with an ubershader one, exact
rendering, while its own builds on the compiler threads, which now
number as many as the machine has to spare (cores - 3, 1 to 4) instead
of one.

What is left to stutter is an ubershader pipeline itself, built on the
spot the first time a draw needs one: one per vertex layout and fixed
function state. Every ubershader pipeline a draw uses is now recorded,
in <game id>.uberuidcache next to the specialized UID cache, and those
are built before the game starts the next time it boots; every other
ubershader pipeline there can be, and the specialized pipelines known,
build in the background while it runs instead of before it starts,
which took 21 s on lavapipe. The record is written when the cache
closes, into room reserved when it opens, so recording allocates
nothing in the middle of a game.

Without compiler threads (OpenGL here, with no shared context for
them), asking for a pipeline in the background builds it on the spot
and an ubershader one besides: the asynchronous modes draw as
Synchronous does then, and nothing extra is built before the game
starts.

Booting a FIFO log no longer hangs: the CPU thread clears the EFB, which
waits on the GPU, before it reaches its run loop, and the frontend's
thread waited for that run loop without running the GPU. It runs the
GPU until then.
2026-11-06 23:07:17 +01:00
LibretroAdmin 58542ddb3a Vulkan, D3D12: the GPU loop on a thread of its own
On Vulkan and Direct3D 12, in dual core, the GPU loop runs on a thread
of its own, as the frontend interface for both lets another thread
submit: retro_run starts the field, waits for it no longer than two of
the frontend's frames (40 ms at most), hands over the newest picture
finished and returns. A shader compiled, or anything else slow on the
GPU side, holds up the GPU thread only. The loop runs in slices of 5 ms,
so a request to park or quit is seen that soon; a slice that ends a
field counts it for retro_run.

Pictures go from the GPU thread to the frontend's through a queue: the
newest published, the one being handed over and the one shown, any of
which the frontend may read. Each output draws into images of its own,
three of them: a picture published and not yet handed over is drawn
into again in place. EndFrame hands over with set_image, or
set_texture_fenced and set_texture, on the frontend's thread, and an
image is free again once the frontend's frame that read it is
submitted. An XFB copy handed on in place is kept alive by the
presenter while the output holds it, and the output puts it back in the
layout or state the frontend reads it in before every submission of
the core's, as the frontend submits on its own thread whenever its
frame is due.

Savestates, cheats and disc changes let the field in flight end on the
GPU thread and park it while the frontend's thread has the video state;
unloading and a context destroyed stop it, and the next retro_run starts
it again.

Direct3D 11 and OpenGL keep the GPU loop on the frontend's thread: the
immediate context is shared with the frontend through lock_context,
which a thread holding it through a slow compile would hold the
frontend's frame up on, and the OpenGL context is the frontend's
thread's.
2026-11-06 22:25:52 +01:00
LibretroAdmin 7f0328d960 libretro: retro_run never waits on a held-up CPU thread
retro_run runs the GPU until the CPU thread ends the field, and the
field ends only as fast as the CPU thread gets through it: a disc read
or anything else holding that thread up held the frontend's frame up
with it, and the frontend counted no frames.

The GPU loop now has a deadline on the wall clock, two of the
frontend's frames and never more than 40 ms: it is checked every 64
chunks of the fifo, and the loop's sleep waiting for work is bounded by
it. Past it the run reports a repeat and returns, the CPU thread goes on
with its field, and the next run takes the rest of the field's work.
A field's end that comes while the loop is out of time is kept for that
next run. Every run reports one frame, before the first picture too,
with the size the frontend was told.

A field swapped while a state is saved or loaded, outside retro_run,
is not presented: the frontend takes pictures only inside retro_run, so
the next field is presented instead.

Whatever pauses the CPU thread from the frontend's (savestates,
cheats, a disc change, unloading) first runs the GPU until the field in
flight ends, so the CPU thread is paused at a field's end as after a
full run, never waiting on GPU work only this thread runs.
2026-11-06 21:38:30 +01:00
LibretroAdmin 6a6d63a114 D3D12: the XFB texture handed to the frontend in place
As on Direct3D 11 and Vulkan, a picture whose draw would be a plain copy
is not drawn: the XFB texture goes over in the state the frontend reads
it in, with the fence its work completes on for version 2, and is read
where it is; version 1 copies it inside video_refresh, which spares the
draw in front of that copy. Nothing is created for it.

The texture is not one of the output's, so no sync index is waited on.
Version 2 keeps reading it in the frames that repeat it, so before each
repeat the output puts it back in that state if the core used it in
another one meanwhile.
2026-11-06 20:51:41 +01:00
LibretroAdmin 28b795b859 Core: nothing allocated on the heap for a frame that changes nothing
A retro_run that draws nothing new allocates nothing; before, it made
some thirty-six allocations:

- Option lookups by key (IsUpdated and GetCached, about thirty a run)
  made a std::string of the key for an unordered_map. The options are
  now an array in the order of their definitions with an index sorted
  by key, searched with strcmp.
- The JIT asks for its thread's stack bounds each time it starts
  running, every run with frame stepping, and glibc's
  pthread_getattr_np allocates. A thread's stack does not move: the
  bounds are kept per thread after the first time.
- Every event pushed to the video thread, two a field for the XFB
  swap, was built with new. Events are now built in a ring of fixed
  slots: at most the queue's 1024 wait and one runs, so a slot comes
  round again only once its event is done.
- A frame step, every run, posted a resume command to the real
  Wiimote manager, built with new. A momentary state change no longer
  resumes the remotes; a reported one still does.
2026-11-06 20:47:25 +01:00
LibretroAdmin 64d3730c99 Vulkan: the 2D view the frontend reads made with the texture
A render target of one layer in a 2D array, which is what an XFB copy
is, gets a second view at creation, 2D, of that layer, and keeps it for
its life. Handing an XFB copy to the frontend in place uses it, so
presenting creates and destroys nothing: before, every new picture made
a view and queued the last one for destruction.
2026-11-06 20:47:20 +01:00
LibretroAdmin 065c9c3bea Vulkan: the XFB texture handed to the frontend in place
As on Direct3D 11, a picture whose draw would be a plain copy is not
drawn: the XFB image is handed over with set_image and the frontend
reads it where it is. It goes over in shader-read layout, submitted on
the core's thread before the frontend is told, through a 2D view of its
one layer made for that picture: the frontend's shaders read a 2D
image, and a view is not kept for the next picture, as an image's
handle can come back for another image once it is destroyed. The view
goes once the core's next submission completes, which follows every
frontend frame that read it.

The image is not one of the output's, so no sync index is waited on.
The frontend keeps reading it in the frames that repeat it, so before
each repeat the output puts it back in shader-read layout if the core
used it in another one meanwhile, a copy's source say: FrameOutput gains
Repeat(), called at the end of a run that reports a repeat.
2026-11-06 20:39:04 +01:00
LibretroAdmin 14e21584dd D3D11: the XFB texture handed to the frontend in place
With version 2 of the frontend interface, a picture whose draw would be
a plain copy is not drawn: the XFB texture itself is named with
set_texture and the frontend reads it where it is. That is the case
when the XFB fills the whole output from its top-left corner at the
same size, with one layer, no stereo, no colour correction and the
default resampling; with crop overscan on, a full-height XFB at the
internal resolution. Anything else is drawn into the output's targets
as before.

The immediate context orders the frontend's reads before anything later
drawn into the texture, so no sync index is waited on. Fields that
repeat the picture keep its XFB copy locked as the one being shown, and
fields that blank the screen lock it until another picture replaces it,
so the copy the frontend shows again is never written meanwhile.

The hook is FrameOutput::PresentTexture, reached through
AbstractGfx::PresentTexture: an output that only hands on its own
targets returns false and the picture is drawn.
2026-11-06 20:27:28 +01:00
LibretroAdmin 1329ca90c6 Video: a repeated picture reported as a repeat, with nothing drawn
Every retro_run hands the frontend exactly one frame. A field that only
repeats the picture before it is not drawn on any backend, OpenGL and
Direct3D on Xbox included: the frontend is told at the end of the run
that the last picture repeats (video_refresh with no data, at the last
picture's size), and shows again what it already has, the target it
reads in place or its own copy. A run that shows something new hands
it over as before, now always as a valid frame.

Loading a state no longer presents from inside retro_unserialize, where
the frontend takes no pictures. The next field presents the loaded
picture inside the next run and is never taken for a repeat of what was
shown before the load.
2026-11-06 20:15:08 +01:00
LibretroAdmin c09a3946df D3D11: version 2 of the frontend interface
The core asks for version 2 of the D3D11 interface and, when it gets
it, shares the immediate context with the frontend by taking turns: it
holds the context lock around the frame's emulation, savestates, the
context reset and destroy and unloading, and releases it around
video_refresh and the sync index wait. It keeps one output texture per
sync index and names the frame's with set_texture, so a frontend
presenting from another thread reads it in place instead of copying
it.

When the frontend has used the context since the core last held it,
everything the core relies on is bound again: the state manager's
graphics and compute bindings and the viewport and scissor, which the
backend now keeps. Version 1 rebinds the same way after video_refresh.
2026-11-06 20:15:05 +01:00
LibretroAdmin 48356ca5f5 Externals: rslc expanding calls in place in Direct3D bytecode
The vendored rslc expands calls where they are made instead of
emitting subroutines, so Wine's Direct3D 11 and 12 accept every
generated stage and the D3D backends boot there.
2026-11-06 19:29:01 +01:00
LibretroAdmin 919e8c5181 Video: the frontend's frame targets in place of swap chains
The frontend owns the window and presents, so the D3D11, D3D12 and
Vulkan backends draw their picture into a FrameOutput, a render target
the frontend reads where it is, rather than a swap chain. D3D11 hands
over one texture at pixel shader slot 0. D3D12 negotiates version 2 of
its interface and keeps one texture per sync index, handed over with
the fence its picture completes on, falling back to set_texture on
version 1. Vulkan keeps one image per sync index and hands it over with
set_image, so the fake swap chain and the WSI calls hooked under it are
gone, and the device may come without a surface. The outputs are RGBA8
and report that format to the presenter.

The DXGI and Vulkan swap chains, the backends' standalone device
creation and exclusive fullscreen are removed.
2026-11-06 19:11:38 +01:00
LibretroAdmin fb7fb73c64 libretro-common: libretro.h and the D3D11, D3D12 and Vulkan interface headers from RetroArch
The D3D12 interface's version 2 hands a frame over by sync index, read
in place behind a fence, and is asked for through the negotiation
interface.
2026-11-06 19:11:34 +01:00
LibretroAdmin 27a5d7f434 Externals: rslc with gl_Layer in a pixel stage
The vendored rslc takes gl_Layer in a pixel stage, so the stereoscopic
pixel shaders, which read the layer the geometry stage chose, compile
for Vulkan and Direct3D.
2026-11-06 18:30:50 +01:00
LibretroAdmin c11f067627 D3D, D3D12: geometry stages to bytecode with rslc; drop d3dcompiler
The geometry shader generator and the layered pass-through geometry
stage now write GLSL for Direct3D too, as for OpenGL and Vulkan, and the
vendored rslc compiles them to DXBC like the other stages. Direct3D
binds the geometry block at b0.

With every stage compiled by rslc, the runtime compiler is no longer
used: d3dcompiler_47.dll is not loaded, D3DCompile and its target table
are gone, and the core no longer needs the DLL on the system.

GenerateVSOutputMembers loses its Direct3D geometry-stage case and its
HLSL semantics, which only the HLSL generators used.
2026-11-06 18:09:39 +01:00
LibretroAdmin aeb0819e40 D3D, D3D12: vertex, pixel and compute stages to bytecode with rslc
The vendored rslc compiles the generators' GLSL straight to DXBC of the
feature level's shader model, so these stages no longer go through HLSL
and the runtime compiler. The geometry stage is written in HLSL and is
still compiled by D3DCompile.
2026-11-06 17:17:12 +01:00
LibretroAdmin 01fc84250d Vulkan, Metal: the bounding box's subgroup reduction again
The vendored rslc prints subgroup operations, so a device that runs them
in the pixel stage gets the subgroup header back: each subgroup reduces
its bounding box and one invocation writes it. Vulkan offers it where
the device has the basic, arithmetic, ballot and shuffle operations in
the fragment stage; Metal on Mac2 and Apple7 GPUs, whose
simd_is_helper_thread is probed at init for the inverted answer some AMD
drivers give. The driver bug that breaks the reduction with discard
still keeps it off.
2026-11-06 16:14:14 +01:00
LibretroAdmin e13a260b8c Metal: framebuffer fetch again, through rslc's subpass input
The vendored rslc reads a subpassInput and prints it to MSL as the
colour attachment's [[color(N)]] value, so the pixel header defines
INPUT_ATTACHMENT_BINDING and FB_FETCH_VALUE again and the backend
reports framebuffer fetch on Apple GPUs and on the Intel GPUs whose
probe draws a pixel that reads back doubled.
2026-11-06 15:10:21 +01:00
LibretroAdmin f073fc9448 Remove glslang and SPIRV-Cross
Every backend compiles its shaders through rslc now: the two submodules,
VideoCommon/Spirv.cpp and their build go.
2026-11-06 14:12:01 +01:00
LibretroAdmin 9d1e91e8d1 Metal: print shaders to MSL with rslc instead of glslang and SPIRV-Cross
Each stage gets its own header whose bindings are the Metal indices the
backend binds at, as the SPIRV-Cross resource table had them: the vertex
stage's blocks at buffers 1 and 2 and its vertex loader input at 0, the
pixel stage's blocks at 0 and 1 and the bounding box at 2, compute's
block at 0, its buffers at 2 and 3 and its image after the eight
samplers. A stage rslc refuses is logged and written to the dump
directory.

rslc has no 8- and 16-bit types, so the texture decoders and the palette
converter read Metal's texel buffers a word at a time, picking bytes and
halves out of it; the buffers are allocated 16-byte aligned. It has no
subpass input or subgroup operations either: framebuffer fetch is no
longer reported, and the subgroup header, the helper-invocation probe
and the Intel framebuffer-fetch probe go.
2026-11-06 14:11:57 +01:00
LibretroAdmin 899b5301f2 Externals/libretro-common: rslc with MSL for the Metal backend
Array, multisampled and buffer textures, further blocks, storage buffers,
clip distances and compute in MSL, laid out as SPIRV-Cross laid them out
with the Metal backend's bindings.
2026-11-06 14:11:54 +01:00
LibretroAdmin 275126d933 Build glslang and Spirv.cpp only where Metal needs them
Vulkan and D3D compile through rslc, so glslang only serves Metal's path
through SPIRV-Cross. It and VideoCommon/Spirv.cpp are built with
SPIRV-Cross on Apple platforms and nowhere else.
2026-11-06 04:34:35 +01:00
LibretroAdmin 05a95eb519 VideoCommon, backends: remove the shader includer
Nothing ever made one: every backend was handed a null includer, and the
OpenGL backend's #include resolver returned the source untouched. The
parameter goes from CreateShaderFromSource and everything beneath it,
and ShaderCompileUtils with it.
2026-11-06 04:34:32 +01:00
LibretroAdmin c327f0676d D3D: print shaders to HLSL with rslc instead of glslang and SPIRV-Cross
The vertex, pixel and compute stages go from the generators' GLSL to HLSL
through rslc; the geometry stage is HLSL already. A stage rslc refuses is
logged and written to the dump directory. Windows no longer builds
SPIRV-Cross; Metal still uses it.
2026-11-06 04:23:38 +01:00
LibretroAdmin f2ab570af7 Externals/libretro-common: rslc with HLSL for the D3D backends
In / out blocks, clip distances, storage buffers and the compute stage in
HLSL, laid out as SPIRV-Cross laid them out.
2026-11-06 04:23:34 +01:00
LibretroAdmin db9802e1ec Vulkan: compile compute shaders with rslc, decode textures on the GPU again
The compute header comes back for the decoders, and compute shaders and
GPU texture decoding are reported as supported once more. The core
option still decides whether textures are decoded on the GPU.
2026-11-06 03:50:36 +01:00
LibretroAdmin 573ae3be97 Externals/libretro-common: rslc with a compute stage
Storage images, shared memory, barriers and the compute builtins: what
the GPU texture decoders are written in.
2026-11-06 03:50:33 +01:00
LibretroAdmin 38d1f25303 Libretro: do not boot when the video backend failed to start
With no vertex manager the boot's first flush read through a null
pointer and took the frontend down. Log it, tell the user and ask the
frontend to close the content instead.
2026-11-06 02:43:28 +01:00
LibretroAdmin 2361eb8509 Externals/libretro-common: rslc with unnamed blocks, gl_in and block bools
The shared shaders the Vulkan backend compiles when it starts declare
their in / out blocks without an instance name and read gl_in in the
pass-through geometry stage; the ubershaders keep bools in their uniform
block, and the depth resolve writes gl_FragDepth. rslc refused the first
two and made invalid SPIR-V of the others, so the backend never came up.
2026-11-06 02:43:24 +01:00
LibretroAdmin 0af0070f38 Externals: remove imgui and implot
The on-screen UI that drew with them is gone; nothing builds or includes
either library any more.
2026-11-06 02:43:21 +01:00
LibretroAdmin e2c5a83f49 Vulkan: bring back the bounding box and the dynamic vertex loader
rslc now has storage buffers and atomics (updated from RetroArch's
libretro-common), so the Vulkan backend offers again what needs them:
the bounding box where the device has fragmentStoresAndAtomics, the
dynamic vertex loader, and the vertex-shader line and point
expansion. GPU texture decoding stays off until rslc has a compute
stage.
2026-11-06 02:43:17 +01:00
LibretroAdmin e3e2f2a230 Vulkan: compile the shaders with rslc instead of glslang
Vendors rslc from RetroArch's libretro-common (gfx/shaders, built with
RSLC_EXTENDED) and has the Vulkan backend hand every vertex, geometry
and pixel stage to it for SPIR-V: no glslang on that path, no global
lock, any number of compiles in parallel. D3D and Metal still go
through glslang and SPIRV-Cross until rslc writes their code.

A stage rslc refuses is logged and written, with the error, to the
dump directory as bad_<stage>_Vulkan_<n>.txt.

rslc has no compute stage, storage buffers, atomics or subgroup
operations yet, so for now the backend does not offer what needs them:
GPU texture decoding (decoding stays on the CPU), the bounding box,
the dynamic vertex loader and the vertex-shader line and point
expansion (the geometry shader expands them).
2026-11-06 01:50:24 +01:00
LibretroAdmin c3ec7979c2 Common, Core: std::atomic -> retro_atomic
Nothing in the core includes <atomic> any more (Metal aside).

- ChunkFile: Do() takes retro_atomic_int_t and retro_atomic_64_t
  words in place of the std::atomic template.
- LogManager's level and per-log enables.
- Core: the state machine is an int word with acquire/release loads
  and stores and CAS transitions. s_stop_frame_step and the presented
  frame hook that set it are deleted: nothing ever read the flag.
- DSP: the mailboxes and the external interrupt flag; the DSP JIT
  tests the flag as a 32-bit word. DSPLLE's cycle count.
- GBACore: the Joybus reply wait was std::atomic::wait; it is now an
  eventcount the GBA thread notifies when it clears the pending flag.
- EXI Ethernet's interrupt register, the Wiimote scanner's mode, a
  real remote's link flag, the IO backends' flags, the Wiimote
  sources, BT passthrough's sync button, the USB microphones' state,
  ActionReplay's log switch and the CPU-thread config callback flag.

The DSP's interrupt flag is now saved as a 32-bit word, so the save
state version goes to 194.
2026-11-06 00:19:39 +01:00
LibretroAdmin 53238c0a06 VideoCommon, backends, input, DiscIO: std::atomic -> retro_atomic
- CommandProcessor: the FIFO registers shared by the CPU and GPU
  threads are retro_atomic words, read and written through LoadReg and
  StoreReg; MMIO's LowPart/HighPart take those words.
- Fifo: the video buffer write and seen pointers, the sync ticks and
  the stop request.
- PerfQueryBase: the query count and results sit behind base-class
  helpers that every backend (Metal too) uses instead of touching the
  atomics.
- Custom assets: the loader's memory delta and an asset's last load
  time are 64-bit words; Present's swap flag is an int.
- D3D's input layout, Vulkan's waiting_for_submit.
- InputCommon: a NumericSetting's value and the Wiimote and Android
  rumble levels are kept as their bits in a 64-bit word; the
  ControllerInterface flags, counter and aspect ratio are int words.
- CachedBlob's fill position and stop flag.

UICommon/GameFileCache is deleted: only the Android app's game list
used it, and that is not part of the core.
2026-11-06 00:19:39 +01:00
LibretroAdmin d3b454d48b Common: make the lock-free maps C89
AtomicHashMap.h was a pair of C++ templates. Common/AtomicMap.{h,c}
replaces it with the same structure in C89, keyed by bytes: each key's
node is pushed at its bucket's head with a CAS and stays until the map
is freed.

- atomic_slot_map: one atomic pointer slot per key, for the OGL
  pipeline programs (keyed by the PipelineProgramKey bytes) and the
  D3D state cache (keyed by each state's hex word).
- atomic_value_map: values that begin with a quiesce_node_t, published
  whole and freed once no reader is inside, for the asset library's
  id-to-files and path-to-id maps (keyed by the string's bytes).

The OGL program cache's reference count and shader id counter move to
retro_atomic too, and PipelineProgramKeyHash goes with the template
that used it.
2026-11-06 00:19:39 +01:00
LibretroAdmin 033f4905cc libretro: keep the shader caches in the system directory
They followed the user directory, which sits in the save directory when the frontend has one. Shader caches belong to the core and the host's GPU, not to a game's saves: they now always go in <system dir>/dolphin-emu/User/Cache/Shaders/.
2026-11-05 23:34:36 +01:00
LibretroAdmin f38b307112 Audio: hand the frontend each field as the hardware made it
The mixer, its granule queues, fades, time stretching and resampling,
the sound stream, the frontend audio callback modes and frame time
callback, Dolby Pro Logic II decoding, audio dumping and the
FreeSurround library are removed.

AudioCommon/FieldAudio.c (C89) is the AI's output. The DSP's DMA
samples go straight into the current field's buffer at the DMA rate,
only byte-swapped. Disc streaming is added in place on top of them and
converted only when its rate differs from the DMA rate, as the AI does
when it mixes the two. The frontend is told the DMA rate, and told
again if a game switches it; it does any other resampling itself.

The remotes' speakers, the Skylanders portal and connected GBAs are
mixed in place too while they play, each converted from its own rate:
a remote's samples as its ADPCM decoder produces them, the portal's
straight from the request, and a GBA's read by mGBA straight into a
lock-free ring that the CPU thread mixes from at the field's end.

Each field end seals the buffer, its length the emulated time since the
last one at the output rate, and retro_run hands every sealed field to
the batch callback straight from that buffer: one video frame and one
consecutive run of samples per run, with no queue or copy in between.

Save state version 193, as the audio interface no longer carries mixer
state.
2026-11-05 23:34:32 +01:00
LibretroAdmin 49abc7d61f libretro: call the frontend only from retro_run
In dual core the CPU thread called into the frontend while the
frontend's own loop was running: every button and axis read went to
input_cb from SI polling, each field's audio went to the batch
callback, and rumble went to the rumble interface.

retro_run now reads every input of the libretro devices once, right
after poll_cb, and emulation reads those values. Rumble the emulation
asks for is kept until the end of retro_run, which hands it over
together with the audio the frame produced.
2026-11-05 23:34:29 +01:00
LibretroAdmin a2404c6e43 Remove the on-screen messages and the ImGui overlay
Nothing is drawn over the game any more. The OSD message queue, the
ImGui overlay that drew it (with the shader-compile progress window and
the statistics, projection and scissor windows), and the imgui and implot
libraries are removed from the build.

Core::DisplayMessage now writes to the log, and the former OSD calls
go through it. Shader compilation no longer presents frames outside
retro_run while it waits.

Deleted with it: the "OSD enabled" core option, the OSD, FPS, speed,
graph, statistics and internal-resolution display settings, and the
ImGui shaders and texture conversion.
2026-11-05 23:34:25 +01:00
LibretroAdmin c478df9181 Measure elapsed time by retro_run, not the host clock
FrameClock also sums frame lengths at the refresh rate the core reports,
and the core-wide Clock (CommonTypes.h) reads that sum. Everything that
measured elapsed time now follows the frontend: it advances a frame at a
time and stands still while the frontend pauses. That covers input
expression timers, cursor and gyro integration, Wiimote-as-device
timeouts, the DSU client, VR controller velocity, Free Look, IOS socket
timeouts, the BBA TCP timers, custom asset load times, OSD message
lifetimes, Common::Timer and the Dolphin IOS device's elapsed time.
Local steady_clock aliases are removed so nothing bypasses it.

Calendar reads stay: the RTC's starting date, file and log timestamps,
and generated file names still take the host's date and time.

Deleted: SteadyAwakeClock, the debugger's unused CodeTrace, and the
host-time diagnostics in IOS request handling and DVDThread.
2026-11-05 22:05:07 +01:00
LibretroAdmin 1e2dbf6616 Remove all sleeping and wall-clock pacing from the core
retro_run runs one video frame and the frontend paces it, so the core no
longer paces itself:

- The speed limiter is gone: CoreTiming::Throttle and its host-time
  targets, VI skip, the presenter's presentation-time wait,
  PrecisionTimer, PerformanceMetrics/PerformanceTracker, and the
  emulation speed, precision timing, rush/smooth presentation and VI
  skip settings and core options. The Dolphin IOS device reports 100%
  and ignores speed limits a guest asks for.
- Common/FrameClock.c (C89) counts retro_run calls. Background threads
  that acted every so many milliseconds wait for a number of frames
  instead: real-Wiimote pool upkeep, scanning, write tests and
  reconnect retries, USB scanning, the USB Gecko and GBA link
  listeners, the BBA read thread, memory card flushing and the WC24
  scheduler. Shutdown wakes every waiter.
- Waits that polled or timed out now block until signalled: dual-core
  boot, PauseAndLock in the GPU loop, CPU thread jobs, shader
  compilation progress, BlockingLoop and ParallelFor setup and
  teardown.
- Bluetooth passthrough submits transfers straight to libusb; the
  output worker and its timed sleep are deleted, HCI command timeouts
  count emulated time, and blocking firmware commands wait for the
  adapter's events.
- Real Wiimote reports go out when produced; delayed ones carry a
  frame number.
- Common::Flag sits on retro_atomic.
- Deleted: SleepCurrentThread, YieldCPU, Common::SleepUntil,
  Event::WaitFor, BlockingLoop::WaitYield, timer resolution changes,
  the play-time tracker, Host_YieldToUI, FrameTiming::ThrottleFrame
  leftovers and the never-used DO_WAIT_FOR_WIIMOTES mode.
2026-11-05 22:05:03 +01:00
LibretroAdmin 1d3415fa2c Ignore the core under its new parallel_gx1 name
The rename left .gitignore matching the old parallel_gamecubewii library names, so a built core showed up as untracked.
2026-11-05 22:05:00 +01:00
LibretroAdmin 2e6ddc0424 Common: rewrite CallGate in C89
call_gate_t is the same counters as before, zero-filled when open.
The gates a thread is inside are now a list of call_gate_frame_t on
the callers' own stacks, linked through an rthreads TLS key, instead of
a thread_local array: no nesting limit and nothing to free per thread.
The TLS key and the shared eventcount are made on first use and
published with a CAS, so racing threads never wait on each other.

Builds rthreads with HAVE_THREAD_STORAGE for the TLS key.
2026-11-05 22:04:57 +01:00
libretroadmin 9bf2934a40 Change name to parallel_gx1 2026-11-05 20:13:01 +01:00
LibretroAdmin cb214d1034 Core, VideoCommon: drop std::this_thread waits
Waits that polled now block on an eventcount that the waited-on thread
notifies:

- Booting in dual core waits for the CPU thread to reach its run loop
  instead of polling every millisecond.
- AsyncShaderCompiler::WaitUntilCompletion wakes as each worker
  finishes an item instead of every 33 ms.

Timed sleeps go through Common::SleepUntil and SleepCurrentThreadUs,
built on retro_sleep_us: the CoreTiming throttle, PrecisionTimer, real
Wiimote report pacing, Bluetooth timed transfers, the Linux L2CAP
connect retry and the BBA read thread's idle wait.

FrameTiming::ThrottleFrame had no callers and is deleted.
2026-11-05 20:11:34 +01:00
LibretroAdmin f7d5095739 Core, InputCommon, VideoCommon: remove the remaining locks
Every std::mutex, lock_guard, unique_lock, scoped_lock,
condition_variable, call_once and semaphore left in Source/Core is
replaced by a lock-free design:

- Input: the device list, control bindings, stick calibration and
  default device are published snapshots read under a quiesce; a
  rebind requested while another thread is rebinding is handed to
  that thread instead of waited for.
- WiimoteReal: the pool thread owns the slots and runs commands from
  an MPSC queue; the active Wiimotes are atomic slots.
- EXI/SI/IOS devices: socket and packet traffic goes through SPSC
  rings or MPSC handoffs owned by one thread; USB scanning publishes
  its device list, and libusb transfers drain through a CallGate.
- Memory cards: the flush thread applies change records queued by
  the CPU thread.
- Custom assets: loads are scheduled as a published batch with an
  atomic cursor, results come back over an MPSC stack, and busy
  assets are skipped and retried.
- D3D state and GL program caches use insert-only atomic maps
  (Common/AtomicHashMap.h).
- Logging appends with O_APPEND / FILE_APPEND_DATA; FatFs reports
  busy instead of waiting; per-socket WSAStartup/WSACleanup.

Deleted as unused by the libretro core: the profiler, the FIFO
recorder, the volume verifier, the touch InputOverrider and the
GameCube adapter's libusb path.
2026-11-05 20:11:31 +01:00
LibretroAdmin 407a632adc Core, VideoCommon: replace the per-frame locks with atomics
CPU: the state machine drops its two mutexes and two condition
variables.  The state is one atomic word, which the JIT still reads in
place; PowerDown sticks through a CAS.  The CPU thread marks itself
active and then rechecks for a pause or a state change, and a pausing
thread stores its request and then waits for the flag to drop, a
Dekker pair, so neither misses the other.  Waits park on eventcounts.
A step request and its Event travel as one exchanged pointer, pending
jobs are an MPSC stack run in order, and SetStepping and PauseAndLock
still run one at a time, through a claimed flag.

Core: the recursive core mutex goes.  Stop moves the state with a CAS,
and PauseAndLock pairs are already serialized by the CPU's claim.  Host
jobs are an MPSC stack the host thread moves, in order, into its own
queue, which keeps HostDispatchJobs re-entrant.  RunOnCPUThread waits
on a Common::Event instead of a binary semaphore, and OneShotEvent is
removed.

State: the transferable shared mutex that counted save tasks is a
counter and an eventcount; the slot readers and the flush hook wait for
it to reach zero.  TransferableSharedMutex is removed.  WaitableFlag
parks on an eventcount.

PixelEngine: the token, its interrupt flags and the raised-event flag
are one atomic word; the video thread merges into it and schedules the
event only on the edge, the CPU thread takes the flags with a CAS.
Savestates keep the old fields.

VertexLoaderManager: the loader map is a fixed set of buckets, each a
list pushed with a CAS and read without a lock by the main and the
preprocessing thread; two threads making the same loader keep one.

AsyncShaderCompiler: the pending queue is the video thread's own.  It
hands the most urgent items to two slots per worker, and finished items
come back on an MPSC stack, so neither side takes a lock.

OSD messages are posted to an MPSC stack and taken in by the video
thread when it draws; a screenshot request is one exchanged string.

Also removed, being unreachable: the surface change and resize requests
nothing sends (and the swap mutex guarding them), the ImGui keyboard
and mouse input nothing feeds (and the ImGui mutex, as only the video
thread touches the context now), ClearMessages, and the libretro Vulkan
WaitForPresentation with its mutex and condition variable.
2026-11-05 20:11:27 +01:00
LibretroAdmin 68c8ee8851 Common: lock-free Config, cached Info values and HookableEvent
Two small primitives carry it:

Quiesce (C89) frees read-mostly shared data without locks.  Readers
bracket an access with quiesce_enter/exit; a writer swaps in a new
pointer and retires the old one, and whichever thread sees the reader
count reach zero frees it.  Nobody waits, and a reader that retires
something from inside, such as a callback removing itself, only defers
the free.

CallGate lets a callback be unregistered while other threads may be
calling it.  Calls enter and leave the gate with one seq_cst add each;
closing it shuts out new calls and returns once the ones on other
threads have left, parking on an eventcount meanwhile, so the owner can
free what the callback captured.  A callback closing its own gate does
not wait on itself.

Config: the layers are a fixed array of published pointers indexed by
LayerType instead of a map under a shared_mutex, and the change
callbacks a fixed array of slots claimed by CAS instead of a vector
under a recursive_mutex.  Readers, OnConfigChanged among them, walk
them under the quiesce; RemoveConfigChangedCallback closes the
callback's gate, so as before nothing it captured is used once it
returns.

Info's cached value drops its spin mutex.  A value of four bytes or
less shares one 64-bit word with its config version, so Config::Get
reads it with a single load and updates it with a CAS; a wider one is a
published copy freed through the quiesce.  Common/Mutex.h had no other
user and is removed.

HookableEvent publishes its listeners as an immutable array, replaced
whole on register and unregister, so Trigger takes no lock.  A hook
registered from inside a callback is first called by the next Trigger;
destroying a hook while another thread runs its callback waits for that
call, as before.

Checked with a stress harness under TSan and ASan/UBSan: three readers
against two writers retiring 400000 objects, and hooks registered and
destroyed 20000 times against two threads triggering.
2026-11-05 20:11:24 +01:00
LibretroAdmin d452125b02 Remove the achievements code and the unit tests
None of it is built.  USE_RETRO_ACHIEVEMENTS is never defined, since a
libretro frontend runs achievements itself; what stays is the stub
AchievementManager the rest of the core already compiled against.
Source/UnitTests has had no build since the core moved to
Makefile.common.

They also held a good share of the remaining std::mutex users, which
the following commits take out of the code that is built.
2026-11-05 20:11:20 +01:00
LibretroAdmin 472dc5ae3f Remove user post-processing shaders and graphics-mod custom shaders
Both fed user-written GLSL to the backends at runtime, which the move to
bytecode emitted straight from the shader generators leaves no compiler
for. The frontend's own shader pipeline covers post-processing.

The presentation pass keeps a plain copy and the built-in correction
shader for output resampling, colour space and gamma correction and HDR
scaling, now compiled in rather than loaded from Sys/Shaders. The
graphics-mod system keeps its draw, EFB, projection and texture actions;
custom pipelines, materials, shader, mesh and sampler assets, the custom
constant buffers in every backend and the two mods built on them are
gone, along with the shader generators' custom-shader hooks.
2026-11-05 15:57:56 +01:00
LibretroAdmin 767c898df8 Move every thread onto rthreads and add a lock-free parallel_for
No std::thread, std::async, std::future or std::promise is left. Each
thread is an sthread with a plain entry point: Common::ThreadEntry<&C::F>
turns a member function into one, Common::JoinThread joins and clears a
handle, and Common::StartDetachedThread covers the fire-and-forget users.

Common/ParallelFor.{h,c} (C89) runs fn(ctx, i) for i in [0, count) on a
persistent pool of one worker per extra core plus the caller. Jobs are
claimed with an atomic counter, workers park on an eventcount between
batches, and the batch hand-off is a seq_cst users/active pair, so there
is no lock anywhere. A caller that finds the pool busy runs its batch
serially instead of waiting. retro_deinit joins the pool.

- VolumeWii::HashGroup spawned 64 threads per 2 MiB group and
  EncryptGroup another one per core; both are now parallel_for batches
  on the pool. The H1/H2 tail is a few small hashes and runs inline.
- VolumeVerifier spawned up to five std::async threads per chunk; the
  chunk's tasks now go to one persistent worker that fans them out with
  parallel_for, still overlapping the next read. The Redump lookup is an
  sthread joined in Finish or the destructor.
- The dual-core "GPU thread" in Core only set a thread_local on itself
  and exited, since the frontend runs the GPU loop. It is gone, along
  with the non-libretro branches of Core.cpp.
- The emu thread is started through Core::StartEmuThread.
- CPU core counts come from cpu_features_get_core_amount().
2026-11-05 13:00:34 +01:00
LibretroAdmin 8c7aa58f6b Remove UPnP, the unused host input backends, disc conversion and DolphinTool
None of these run in the core: UPnP served NetPlay; the DInput, XInput,
WGInput, Win32, Quartz, Xlib, evdev and Pipes input backends are never
enabled, since input comes from libretro; and disc conversion, with its
multithreaded compressor, was only reached from DolphinTool. The WIA,
RVZ and GCZ readers stay.
2026-11-05 13:00:29 +01:00
LibretroAdmin 12247f1f50 Rebuild Event, BlockingLoop and the thread helpers on libretro-common
Common::Event is an atomic flag with a retro_eventcount to park on, so
Set takes no lock and makes no syscall while nobody waits. BlockingLoop
loses both of its mutexes: waiters park on an eventcount and re-check
IsDone(), so any number may wait at once, and Prepare hands setup to
the one thread that moves the loop out of the stopped state.

The thread helpers name, yield and sleep through rthreads and
retro_timers; the unused affinity and SwitchCurrentThread helpers go.
2026-11-05 13:00:26 +01:00