Check the enabled layers before querying pipeline binary features. A
validation or gfxreconstruct layer older than Vulkan 1.3.294 wraps
handles but passes the extension's commands to the driver unchanged, so
keep the driver pipeline cache path there and when enumeration fails.
Cover the layer decisions with a fake enumerator.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A costly build whose binaries cannot be extracted or stored, and a
rejected capture build, now leave a capture-failure marker, so later
runs build that recipe through the driver cache instead of repeating a
capture build with a null pipeline cache. Lowering the time threshold
does not retry these markers; a driver change does. Existing
below-threshold markers stay compatible and retryable.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Restore the cheap size-and-header check for the 10-second worker save,
with the size query still under the snapshot lock, so an unchanged cache
is no longer copied and hashed on every tick, including the first tick
after loading a current cache. A legacy import is still written by the
first periodic save. SaveNow and the final save keep the content
comparison that writes equal-size changes.
Cover periodic copy avoidance after saving and after reopening, legacy
migration and equal-size persistence.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Capture costly compute builds and restore matching recipes after checking
full recipes, driver fingerprints and Vulkan compatibility keys. Add size
and time thresholds, opt-out, atomic persistence and corruption/rejection
fallback.
Remember below-threshold recipes in separate files so competing decisions
cannot overwrite captured binaries. Prefer saved binaries and retry
capture when the time threshold is lowered.
Cover compatibility, fallback, deterministic competing writers and
cross-process capture, restore and corruption.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Keep driver blobs outside source-version cleanup and import compatible
legacy caches. Persist equal-size content changes and retry incomplete
snapshots without replacing the previous file.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The label read trap skipped every image sharing a page with another
pending image. Large tiled images are never stored at a label, so an
image inside one no longer counts as sharing. A store of an image over a
trapped one now stores the trapped one in the same flush, and an
eviction or explicit write-back stores the trapped images inside it
first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The trap was released when FlushPending took the image out of the pending
registry, before it waited for GpuMutex, so a read in between saw the old
bytes; cache eviction and explicit write-backs did not release it at all.
Every store now releases it in writeBackLayers: after recording the GPU copy
into the import, or before the first CPU access on the CPU path, which also
releases the traps of other images on the same pages. A trapped image is
stored whole, and direct memory mapped at a second address is not trapped.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Before a label, a pending linear or small storage image that shares no page
with another pending or storing image now gets a write-watch read trap
instead of its store. The first access to its pages stores it through the
host-read hook; any other store releases the trap first.
APS5_NO_LABEL_READ_TRAP=1 keeps the store at the label.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Module records are SPIR-V and take almost all of a journal's bytes, so
APS5_PIPELINE_JOURNAL_MAX_MIB fills with modules and later pipelines
are not recorded (#8309). A module record now keeps its identity, stage
and encoded size, followed by a zstd frame (level 1) of the encoded
result; EncodeModule writes it, and loading and replay expand it before
decoding. The journal format becomes 11: main is at 10 (8 for the point
lists of #6801, 9 for the 4_4_4_4 UNORM targets of #8564, 10 for the
colour logic operation of #9348).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A direct mesh draw split into layers now pushes the groups per layer in the
upper half of the mesh draw's index word, and the mesh prologue numbers its
group WorkgroupId.x + WorkgroupId.z * stride. A draw in one layer pushes 0,
so its number is WorkgroupId.x whatever the device reports for the other
components, and the mesh program reads NumWorkgroups only where main did.
Layers past an X limit above 65,535 keep at most 65,535 groups so the stride
fits.
UnlandedOwnLabel took a queued entry only when it was the caller's own and
fell back to the recorded one otherwise, so an older recorded store of the
waiting queue counted as its newest even when another queue had a newer
queued store of the dword, and an older own queued store won over a newer
recorded store of another queue. It also answered nothing unless every
awaited dword was the queue's own, so an 8-byte wait passed on memory that
the queue's pending reset of one dword was about to change.
The lookup now takes the newest tracked store of each dword, queued or
recorded, by stamp, and reports the value and the dwords whose newest store
is the queue's own and not overwritten. OwnStoreRejects composes what the
wait would read once those stores land (own dwords from the table, the rest
from memory) and keeps the wait only when that value fails the compare, so
the wait's mask and function decide as before and another queue's later
store of a dword still releases it.
A direct mesh draw with no instances dispatched zero Y groups before the
split and must not stop the title now. The total-group check divides the
limit instead of multiplying the three factors, which could wrap.
A WAIT_REG_MEM passes as soon as guest memory satisfies its compare. A queue
that resets a label right after its wait on it passed, and waits on it again
before that reset reached memory (the store is recorded on the GPU behind
other work, or still queued), found the old value in memory and passed the
second wait without a new store from the other queue. Skyrim (PPSA03747)
does this on queue 0x28: two passes on 0x112a800020 per signal of queue 0
let the two queues drift a step apart, and later signals overlap and get
lost until both wait forever.
Recorder::UnlandedOwnLabel reports the value a queue's own newest stores of
a label leave once they land, when every awaited dword's newest tracked
store is that queue's, still queued or in a batch not reaped yet, and no
later recorded GPU write covers it. A wait whose memory satisfies the
compare while such a store rejects it keeps waiting (at entry, in the poll
loop and on the pipelined queue-0 path), and reaps while it sleeps so the
store's entry leaves once it landed. Landing waits are unchanged.
Record an HTILE clear's mask/value with its pending note and recorder ID.
When that write covers the HTILE and determines its ZMask/SMem bits,
expand the metadata on the GPU without a readback. Retire mask proofs
with their recorder or depth device, preserving other owners and the
bounded cache's conservative overlap invalidation.
Recognize the structured-buffer variant and carry its completion range
through preparation, upload and binding. Admit only that range while
its native owner has a pending clear, retaining color guards and
excluding these resources from dispatch and recipe caches.
Cover store-back, PM4 buffer clears, guard refusals, recorder recreation,
live owner isolation and scoped cleanup with GPU byte/wait assertions.
AI-assisted: yes
The whole-window retile check bound slabPieces[part.pieces].copies[part.copy]
before testing part.slab. A piece stored into the import records pieces = 0,
and when no piece of the write-back went to a slab slabPieces is empty, so
the lookup binds a reference through a null pointer (UBSan: reference
binding to null pointer, Texture.cpp:3514).