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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
- 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.
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.
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/.
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.
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.
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.
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.
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.