Most of the texture cache's hashing is one whole buffer a frame: the
XFB, checked against its copy before it is shown. On x86-64 with
VPCLMULQDQ on ZMM, a whole buffer of 512 bytes or more is now the
CRC-32 of libretro-common's encoding_crc32, which folds 512 bits at a
time; smaller and sampled hashes, and every other CPU, keep the four
CRC32C chains. Folding 128 bits at a time is no quicker than the
chains, so PCLMULQDQ alone keeps them too.
Throughput here (Zen 5), chains against encoding_crc32:
512 B 25 to 44 GB/s, 2 KiB 25 to 70, 32 KiB 26 to 91,
a 600 KiB XFB 25 to 52-67.
encoding_crc32.c, its tables and crc32.h are copied unchanged from
libretro/libretro-common.
States keep texture cache hashes, and a copy whose picture is only on
the host would be lost to a mismatch: the first check of a copy a
state brought back also takes the old hash of its memory
(common_hash64_v1), and keeps the new one from then on. A state the
parent saved, loaded by this build, takes its XFB copy that way.
An EFB or XFB copy kept only in VRAM fills its RAM with the uninitialized
pattern, then hashed all of it back to record it unchanged. Those are the
same bytes for the same row size, stride, rows and sample count every
time, so the hash is kept per shape (four of them) and taken from there
when the entry's shape is the fill's; the first copy of a shape hashes
the memory as before. On the stutter corpus the kept hash matched the
memory's in every one of 8500 copies, and retro_run, single core, went
from 323M instructions to 266M.
The texture hash, the CRC32 helpers, Adler-32 and the Ector hash are C;
the texture hash picks CRC32 or MurmurHash3 once with a branch instead
of calling through a function pointer each time, CPUDetect still
deciding which. The hashes are bit for bit the old ones, so cached and
saved hashes stay valid.
Every Statistics counter, the projection copies and the widescreen ratio
averages were only written since the debug UI went; the counters bumped
per primitive, per triangle and per pixel in the software rasterizer.
The widescreen heuristic keeps only the two vertex counts it compares,
from one projection/viewport ratio per flush instead of up to three.
GetCPRegInfo, the CP register describer, has no caller.
A pooled texture unused past the threshold stays while the pool holds
less than 64 MB, the oldest dropped first, so a config that comes back
reuses one where a new image was made. Over the corpus, textures made
after boot fall from 1031 to under 51, and the core's thread's
allocations from 1079 to 62.
An encoder reads the rest of the copy (the EFB's format, depth, YUV,
filter rows, overflow, gamma) from its uniforms, in what was the
block's padding, so one per copy format draws them all. Where copies
to RAM may be made, every format's is built on the workers before the
game starts: a copy to RAM has no other pipeline to draw with. The
gate checks pictures with copies to RAM too, and fails one that waits
for its encoder; the corpus made the parent build 201 on the draw path.
With GPU texture decoding, the decoding dispatch and the copy into the
texture are recorded in the init command buffer, which runs before the
draws, instead of between them: decoding a texture no longer ends the
render pass. The scratch texture is used there alone, and a decoded
texture is new or from the pool, which this command buffer's draws do
not use, so the layouts each tracks still hold. Other backends record it
in order, as before.
The stutter gate counts a frame as differing only when the pictures it
was presented as have none in common between the two runs.
The EFB copy pixel shader reads the copy's filter rows, overflow,
gamma, alpha, depth and intensity from its uniforms, each branch taking
the path a shader built for that copy would, so one is built per
format. One more, built with the shared pipelines, reads the format
from them as well and draws a format's copies until the format's own is
built on a worker.
The stutter corpus copies the EFB to a texture in every colour,
intensity and depth format, with the EFB with and without alpha, draws
it back textured, and cycles the copy filter and gamma. Each frame
draws its number, which the harness reads back, so the picture check
compares the two cores frame by frame in both synchronous modes. The
baseline is the new corpus's.
An entry keeps its first few references in place and more on the heap,
XFB stitching keeps its candidates in an array it reuses and sorts by
insertion, and the EFB copies waiting for RAM sit in an array counted as
handles would count them, in the order they flush.
Textures kept for reuse sit in a plain array, found by their packed
config and swapped out of it when taken or dropped; it grows only past
the most a game has had pooled at once.
The address and hash indexes keep the order a multimap kept, equal keys
as they went in, and a node stays where it is while others come and go,
so every walk and savestate sees the textures as before. Their nodes are
reused, so caching a texture while the game runs allocates none.
Entries come from chunks kept for the core's life and go back to them
when their last handle goes, so a texture cached while the game runs
allocates no entry. Their handles count in the entry itself, with plain
increments on the video thread.
XFB copies kept only in VRAM go with the texture cache when the video
backend is made again or a state is loaded. RAM then holds only the
uninitialized marker, so no picture is fetched from it and the frontend
keeps the one it shows until the game's next XFB copy.
AbstractTexture::LendLoad lends the upload stream's memory for a level,
and CommitLoad uploads it: Vulkan for any rows, Direct3D 12 for rows at
its pitch alignment. The texture cache decodes textures and XFBs into
it; it decodes into its own buffer and Loads that where the backend
lends none, or when the arbitrary mipmap detection reads the levels
back. The detector keeps its levels in a fixed array.
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.
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.
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.
The warning can be seen since 1f72403ec7 when GCC 13.3.0 is used:
```counterexample
[1540/1898] Building CXX object Source/Core/VideoCommon/CMakeFiles/videocommon.dir/TextureCacheBase.cpp.o
/w/dolphin/Source/Core/VideoCommon/TextureCacheBase.cpp: In member function 'void TextureCacheBase::ApplyMaterialToCacheEntry(const VideoCommon::MaterialResource&, TCacheEntry*)':
/w/dolphin/Source/Core/VideoCommon/TextureCacheBase.cpp:3136:19: warning: loop variable 'texture' creates a copy from type 'const VideoCommon::MaterialResource::TextureLikeReference' [-Wrange-loop-construct]
3136 | for (const auto texture : material_data->GetTextures())
| ^~~~~~~
/w/dolphin/Source/Core/VideoCommon/TextureCacheBase.cpp:3136:19: note: use reference type to prevent copying
3136 | for (const auto texture : material_data->GetTextures())
| ^~~~~~~
| &
```
Accomplished using `run-clang-tidy` with `performance-move-const-arg,performance-unnecessary-value-param,modernize-pass-by-value`.
Changed arguments to const references, removed them where inappropriate (e.g. sink parameters). Same with std::move.
Manually reviewed each change to make sure that it makes sense, and do something more appropriate if possible.
After an EFB operation is turned into a texture, if there is a graphics mod action that provides a custom material, update the EFB texture with the material
* Add a 'AfterEFB' function to graphics mod action that can return a Material
* Rename previous EFB graphics mod function to 'BeforeEFB' to differentiate from 'AfterEFB'
* Rename previous XFB graphics mod function to 'BeforeXFB' to mirror EFB
TextureCacheBase::LoadImpl has a hot path where the passed-in
TextureInfo never gets used. Instead of passing in a TextureInfo, let's
pass in the stage and create the TextureInfo from the stage if needed.
This unlocks somewhere above an additional 4% performance boost in the
Hoth level of Rogue Squadron 2 on my PC. Performance varies, making it
difficult for me to measure, so treat this as a very approximate number.
The TextureInfo constructor creates a vector of MipLevels. This could be
good for performance if MipLevels are accessed very often for each
TextureInfo, but that's not the case. Dolphin creates thousands of
TextureInfos per second that it never accesses the mipmap levels of
because there's a hit in the texture cache, and in the uncommon case of
a texture cache miss, the mipmap levels only get looped through once.
To make the common case of texture cache hits as fast as possible, let's
not create a vector in the TextureInfo constructor. This commit
implements a custom iterator for MipLevels instead.
In my testing on the Death Star level of Rogue Squadron 2, this speeds
up TextureInfo::FromStage by 200%, giving an overall emulation speedup
of a bit over 1%. Results on the Hoth level are even better, with
TextureInfo::FromStage being close to 300% faster and overall emulation
being over 4% faster. (Single core, no GPU texture decoding.)
Fix some common anti-patterns with these data structures.
- You can dereference the iterator returned by `find` to access the
underlying value directly, without an extra `operator[]`/`at`.
- Rather than checking for an element before insertion/deletion, you can
just do the operation and if needed check the return value to
determine if the insertion/deletion succeeded.