The snapshot check that let Materialize accept an attempt during which the
guest allocation generation moved looked only at the registered image
views. A remap of the memory the capture read descriptors from left those
views unchanged, so the attempt was accepted with descriptor words read
before the remap. The generation check on main did not cover this either:
ShaderMemory keeps the pages it read across attempts, so a retry read the
same cached words.
ShaderMemory now records the guest allocations behind each page when it
first reads it. The runtime hook, now snapshotCurrent, drops every page
whose allocations changed, so the next attempt reads it again, and accepts
the attempt only when no page changed and the image views are current.
agc_shader_memory gains a directly bound sampled array image case through
the real CaptureRegisteredViewsCurrent (an unrelated mapping during the
attempt is accepted and keeps the view, a resize of the image's own range
is retried) and a capture whose descriptor page is remapped mid-capture,
which must return the descriptor written after the remap.
AI-assisted.
Scalar and vector loads through a present oversized unregistered V#
must return 0 before anything is stored through it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A present V# beyond the storage buffer range is bound to its own
placeholder, which was created without a fill. A new device allocation
or a pooled block is not zero, so loads through such a V# read
uninitialized or stale bytes instead of 0. Zero it like the empty
buffer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Where robust loads are not exact, a driver may round a binding's range up
to a DWORD (RADV does), so a V# of 7 bytes gets a length of 2 DWORDs and
the length check passes the DWORD at byte 4 that the V# size excludes.
Specialize the low two bits of the V# size in the free fourth word of the
buffer's specialization slots, key the binding plan and the result memo
on it, and keep the carry-only guard only when both the base and the size
are whole DWORDs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The mock graphics context set only the null V# placeholder, so binding an
unregistered V# past the range limit now found no buffer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A present V# whose size exceeds maxStorageBufferRange and whose base is not
registered was bound to the same 16-byte placeholder as a null V#. Its
guest checks pass, so a store through it could write that placeholder,
and a later scalar read through a null V# (which now relies on the length
check alone) would return the stored word instead of 0. Bind such a V# to
a separate placeholder, so the zeroed one is only bound for V#s the guest
checks treat as not present and no store reaches it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A scalar buffer read through a static V# checks the carry, that the V# is
present and the offset against the V# size in 64 bits, and then the
offset against the binding's length. When the V# base is DWORD aligned,
the binding starts at the base and spans the V# size (less where it is
cut), and a V# that is not present is bound to the empty placeholder, so
the length check alone gives the same value once that placeholder reads
as zero. Zero the placeholder when it is created and keep only the carry
check there. A base off a DWORD boundary keeps every check, since its
view is rounded up a DWORD. Devices with exact robust loads have no
length check and are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A PPS that refers to a second SPS is valid H.264; the decoder only does
not read picture metadata from it yet. The check now keeps what it had
before: nothing is written past the declared size, and a query that
throws changes nothing but the validity word.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ResourceMaterializer::Materialize retried whenever the guest allocation
generation moved during an attempt, and threw "runtime resource snapshot
did not stabilize" after three. Any allocation change moves it, so a
title that maps memory while it loads can move it during every attempt.
PPSA22408 aborted that way during loading in 2 of 2 runs with #11346 and
its COND_INDIRECT_BUFFER change, and in about 1 of 3 otherwise.
A new SrtRuntime hook, imageViewsCurrent, checks whether the image views
a snapshot registered still match their allocations. The driver backs it
with CaptureRegisteredViewsCurrent, the check its dispatch cache already
uses. Materialize accepts an attempt whose generation moved when the
check passes, and retries as before when it fails or no hook is set.
AI-assisted.
#14302 records the blocks a storage write-back keeps for the CPU with the
unit's generation, and keptForCpu holds while the unit keeps that
generation: new GPU results then leave the unit's pending-since as it
was, so a CPU store made before them still wins at the next write-back.
A re-upload ends the record by giving the unit a new generation. On
memory outside the write watch the generation is 0 before and after the
upload, so the record held for good: every later result of the unit kept
the pending-since of the first, the CPU store that the upload had just
taken counted as newer than the new results, and the next write-back
kept the CPU's bytes over them.
The contention checks of agc_driver_image_atomics show it: their texel is
in a global array outside the write watch. After the image_atomic_add
check, the umax check rewrites the texel while the add's results are
pending, the umax dispatch keeps those bytes and re-uploads them, and the
read-back dispatch kept them again, so the texel read back its initial 5
instead of the largest stored value.
An upload now forgets the kept blocks of each unit it restamps, since the
image takes the guest bytes there.