This change reduces the amount of data copied into VkDeviceMemory resources in
`Apply_InitialState`; this uses the new (interval-based) reference tracking.
On a large capture from a recent game, this change reduces the amount of copied
data from 2.2GB to 720MB (plus 100MB cleared to 0). This reduces the time spent
in `ApplyInitialContents` from 1800ms to 1500ms.
Ranges of memory are initialized/reset by either clearing to 0, or copying in
the initial state data, according to the `FrameRefType`, as follows:
- Read-only (`eFrameRef_Read`) regions of memory are initialized by copying
once, before the first replay.
- Read-before-write (`eFrameRef_ReadBeforeWrite`) regions of memory are reset by
copying before each replay.
- Write-only (`eFrameRef_PartialWrite` and `eFrameRef_CompleteWrite`) regions of
memory are cleared to 0 before each replay. This is intended to avoid possible
user confusion when inspecting these regions of memory, as they might
otherwise show data written later in the frame.
- Unused (`eFrameRef_None`) regions of memory are cleared to 0 once, before the
first replay.
* On some implementations, external images have lower memory requirements than
an identical non-external image. To account for the replay being non-external
we create both and pick the worst when reporting memory requirements to the
app, so that it allocates enough for us on replay.
* When checking for validity, some array elements might be valid while
others might not be. This is still OK if the application knew that
only certain elements would be accessed by the given shaders - so we
should still update the others.
* Some memory types may not be compatible with buffers, so allocations
in those memory type indices we just don't create a spanning buffer,
as it won't be needed anyway to restore memory contents.
* This is a backwards-incompatible change, so it's done here before v1.0
so that we can freely break compatibility and not need to define a lot
of ugly compatibility code.
* The primary motivation is to make the serialised data more uniform
and have fewer special cases where the serialised form is naturally
defined from the functions/structs in question.
* There are still some special cases or variances but they should be
more isolated and only where really necessary.
* Remove some cases in the D3D12 struct serialising where we were
directly serialising child struct members in the parent struct.
* We now try to match argument/struct member order as closely as
possible.
* Serialising an array with a count no longer reads the count back out
into that variable, counts must be serialised separately. This means
all members/arguments are explicitly present in the structured data
and also eliminates the awkward case where a count needed to be
serialised separately after an array if we want to have the count be
a valid number even if the array could be NULL. It also means we don't
need the FIXED_COUNT() macro since array lengths can be plain values
and don't have to be a reference type.
* This allows more flexible construction of what params/resources need
to be saved with initial contents data, as well as allowing better
type safety where possible by using driver-specific types for the
stored data.
* Especially it tidies up the ugly 'all data that tags along must be
a written into a single allocated byte pointer'.
* The initial content struct is responsible for destroying itself and
freeing any data.
* For a few primary cases, we check to see if the API call failed (or is
looking like it will fail, in the case of vulkan) and bail out. This
will cause the capture to fail to load.
* In each function, we check that serialisation succeeded before going
on to actually call into the API. If something went wrong, then we
bail out and fail to load the capture.
* For Vulkan and D3D12, we now create a dummy command buffer to ensure
that there's actually a chunk available to correspond to the command
buffer that gets submitted or recorded to.
* These structs were no longer used since the memory wholeMemBuf was
added, and they had some confusing enum usage that triggered static
analysis warnings.
* This was previously needed to be stencil-before-depth to workaround an
nvidia driver bug, but now depth-before-stencil works around a RADV
hang and it no longer seems to be necessary for nvidia.
* The RADV issue is being fixed upstream but this is a 'free' workaround
until that arrives.
* This also applies to stencil even in non-multisampled images.
* Serialisation applies to both network proxying data, initial frame
contents at the beginning of a captured frame, and saving images to
disk.
* This is potentially slightly less optimal as it means the initial
states aren't in GPU memory for a faster copy, but it means we're much
less likely to hit OOM due to way more GPU allocs, and it's still
pretty fast.
* Further optimisation is possible by reducing the number of images that
actually either need initial states at all (detect when images are
first used to clear via a renderpass), or by detecting images that are
frame invariant and we only have initial states for immutable contents
and avoid copying them more than once.
* No need to base readback buffer on image memory requirements which
could be packed tighter than our requirements for readback (e.g. depth
and stencil combined formats)