VideoInterface read four settings with Config::Get per field and per SI
poll; the first field after boot found the cache stale and parsed the
value through an istream, allocating. They are read where config
changes, as the VI overclock factor already was.
A compile worker given nothing during boot made its first shader text
block in play. Each worker now makes its rslc arena and three text
blocks as it starts, at boot.
Both play allocation baselines are 0.
Where the device sets the logic op per draw, its mode is no longer part
of a pipeline, so one ubershader pipeline with integer output draws
every mode. The precompile also builds the logic op pipelines with
dual-source output asked for, as BlendingState::Generate leaves it.
The stats build counts the pipelines the driver compiles or links, and
those a draw waits for.
make STATS=1 builds parallel_gx1_libretro_stats with counters for each
kind of draw-path compile, wait and cache write, worker compiles, and
every shader and pipeline made; release builds have none. Tools/stutter
generates a synthetic FIFO corpus, runs it in the four cache states,
times parent and candidate ABBA, compares pictures, and gates the
counters against baseline.txt and the parent.
The shader cache keeps its compile items in chunks and reuses them, so a
shader or pipeline compiled while the game runs allocates nothing. Each
item embeds the compiler's link and callbacks, so a finished one is pushed
back as it is, and a pipeline waiting on its stages queues itself again.
The compiler keeps the waiting items in a heap by priority and order, and
is a member of the shader cache rather than an object of its own.
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.
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.
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.
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.
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.
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().
SPDX standardizes how source code conveys its copyright and licensing
information. See https://spdx.github.io/spdx-spec/1-rationale/ . SPDX
tags are adopted in many large projects, including things like the Linux
kernel.
Currently, when immediately compile shaders is not enabled, the
ubershaders will be placed before any specialized shaders in the compile
queue in hybrid ubershaders mode. This means that Dolphin could
potentially use the ubershaders for a longer time than it would have if
we blocked startup until all shaders were compiled, leading to a drop in
performance.
- In D3D, shaders could be compiled on the main thread, blocking
startup.
- Reduced the latency between a pipeline being requested and used in all
backends in hybrid ubershader mode, when no shader stages were present.
- Fixed a case where async compilation could cause the same UID to be
appended multiple times to the UID cache.
- Fix incorrect number of threads being used when immediately compile
shaders was enabled.
This enables shaders to be compiled while the game is starting, instead
of blocking startup. If a shader is needed before it is compiled,
emulation will block.