Common/ObjectPool.c (C89) keeps objects of one class in chunks, the first
made at backend start: a freed object is pushed on a lock-free stack, an
allocation takes the stack whole (an exchange, so no ABA) and pushes the
rest back, or else takes the chunks' next slot. A pool destroyed with
objects still out is freed with the last of them. Classes take it with
OBJECT_POOL_MEMBERS / OBJECT_POOL_DEFINE: VKPipeline, VKShader,
VKTexture, VKFramebuffer and the vertex loaders, which are now their
own bucket list nodes. Vulkan's descriptor pool and cleanup lists get
their first room at boot.
Play allocations: 58 to 1 on the core's thread, 172 to 1 on the compile
workers (both a first use).
Nothing made a framebuffer with additional colour attachments, so they
go from AbstractFramebuffer, CreateFramebuffer and every backend, and
FramebufferState's additional_color_attachment_count (always 0) with
them: render passes, views, clears, RTVs and blending are for one
colour attachment, in fixed arrays rather than vectors.
Vulkan samplers are kept in an open-addressed table grown by doubling
and render passes in a fixed array, so looking one up never allocates.
Play allocations on the core's thread: 62 to 58.
A disk cache makes room for an entry at the end of what it will write
(LinearDiskCache::AppendBegin), and the backend writes its pipeline data
straight there: GL's program binary from glGetProgramBinary, D3D12's
blob copied once out of the driver's. The Vulkan pipeline cache is built
in place too, vkGetPipelineCacheData writing into the store written to
disk. No vector in between, no copy of it.
Texels are written into it in place, tile by tile, so it is cached
memory: VK_HEAP_UPLOAD_CACHED on Vulkan, a write-back L0 custom heap on
Direct3D 12, the upload heap where the device has none.
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.
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.
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.
Yellow squiggly lines begone!
Done automatically on .cpp files through `run-clang-tidy`, with manual corrections to the mistakes.
If an import is directly used, but is technically unnecessary since it's recursively imported by something else, it is *not* removed.
The tool doesn't touch .h files, so I did some of them by hand while fixing errors due to old recursive imports.
Not everything is removed, but the cleanup should be substantial enough.
Because this done on Linux, code that isn't used on it is mostly untouched.
(Hopefully no open PR is depending on these imports...)
It was reported that some games (Zelda Wind Waker and Zelda Twilight Princess but others may also exhibit the issue) have graphical issues with the max pixel samplers set to 16 on some Android devices (ex: Pixel6); since this was increased for a performance heavy feature (custom shaders) just disable it for now. In the future, this could be handled more elegantly
The benefit to exposing this over the raw BP state is that adjustments Dolphin makes, such as LOD biases from arbitrary mipmap detection, will work properly.
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.
These are only ever used with ShaderCode instances and nothing else.
Given that, we can convert these helper functions to expect that type of
object as an argument and remove the need for templates, improving
compiler throughput a marginal amount, as the template instantiation
process doesn't need to be performed.
We can also move the definitions of these functions into the cpp file,
which allows us to remove a few inclusions from the ShaderGenCommon
header. This uncovered a few instances of indirect inclusions being
relied upon in other source files.
One other benefit is this allows changes to be made to the definitions
of the functions without needing to recompile all translation units that
make use of these functions, making change testing a little quicker.
Moving the definitions into the cpp file also allows us to completely
hide DefineOutputMember() from external view, given it's only ever used
inside of GenerateVSOutputMembers().
Since C++17, non-member std::size() is present in the standard library
which also operates on regular C arrays. Given that, we can just replace
usages of ArraySize with that where applicable.
In many cases, we can just change the actual C array ArraySize() was
called on into a std::array and just use its .size() member function
instead.
In some other cases, we can collapse the loops they were used in, into a
ranged-for loop, eliminating the need for en explicit bounds query.