The GPU thread ran the FIFO in 5 ms slices against the wall clock and
slept out each slice's rest, which held every retro_run near 4 ms. The
loop now runs until the CPU thread ends the field or a park or quit
interrupts it (InterruptGpuLoop), and an idle loop parks with no
timeout: no slices, no clock reads, no busy loop, and no GPUSleeper
event to let it sleep (kept registered, never scheduled, for the
savestates that have it queued). retro_run waits for the field as long
as it takes; FIELD_WEDGE_US, one second with nothing arriving, is the
only bound, for a wedged thread.
PERF_TEST=1 builds a core with the frontend's performance counters
around the field and the frame hand-over. AGENTS.md states the rules:
the frontend owns the pace, no timed waits but the wedge guard, stop by
request, park and never spin, no locks.
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.
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.
- 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.
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.
In dual core mode the CPU thread ends the frame with StopGpuLoop() while
retro_run() runs the GPU loop. When the CPU thread got there before the
frontend thread entered RunGpuLoop(), the loop of the last frame still
counted as stopped, the Stop() did nothing, and RunGpuLoop() ran with
nothing to end it: the frontend hung in retro_run() and the CPU thread
stayed paused. StopGpuLoop() now also sets a flag the loop checks on
each pass, cleared once the loop has ended.
Fixes#462
Given how many member functions make use of the system instance,
it's likely just better to pass the system instance in on construction.
Makes the interface a little less noisy to use.
This adds about a frame of latency, and since most games don't change
VI registers during scanout, we can get away with outputting the XFB at
the start of scanout. WWE Crush Hour is the (only currently known)
exception, which has flickering problems when doing it this way.
This adds a path to perform the output at the end of scanout, and gates
it behind an option which defaults to using the latency-reducing
pre-scanout path.
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.
The STL has everything we need nowadays.
I have tried to not alter any behavior or semantics with this
change wherever possible. In particular, WriteLow and WriteHigh
in CommandProcessor retain the ability to accidentally undo
another thread's write to the upper half or lower half
respectively. If that should be fixed, it should be done in a
separate commit for clarity. One thing did change: The places
where we were using += on a volatile variable (not an atomic
operation) are now using fetch_add (actually an atomic operation).
Tested with single core and dual core on x86-64 and AArch64.
Makes the values strongly-typed and gets more identifiers out of the
global namespace.
We are forced to use anything that is not "None" to mean none, because
X11 is garbage in that it has:
\#define None 0L
Because clearly no one else will ever want to use that identifier for
anything in their own code (and is why you should prefix literally
any and all preprocessor macros you expose to library users in public
headers).