The descriptor web entry and APS5_STRICT_DESCRIPTOR_WEBS said "texels";
the fold takes image loads and samples only. A path through an image
gather or an image atomic's returned value still throws.
AI-assisted.
MoltenVK has no VK_EXT_image_view_min_lod, so since #130 the first T#
with a MIN_LOD clamp above its base level threw. ASTRO BOT (PPSA21564
01.007.000) hits it on the title screen, about four minutes after boot.
MoltenVK does offer shaderResourceMinLod, which maps the SPIR-V MinLod
operand to Metal's min_lod_clamp.
On a device with shaderResourceMinLod, without the extension and
without graphics pipeline libraries, the new target flag
shaderImageMinLod moves the clamp into the shader:
- DescriptorBindingBuilder writes each direct image's clamp, relative
to the view's base level and bounded by its last level and MAX_MIP,
as a float specialization constant at MinLodBase + image.
- Samples of direct images take it as the MinLod operand, or as an FMax
on an explicit LOD when the clamp is above 0, combined with an
instruction's _cl clamp.
- The image view no longer chains the clamp there; image table entries
with a clamp are still rejected, and devices with the extension are
unaffected. APS5_NO_IMAGE_VIEW_MIN_LOD forces the shader lane.
- It throws when the clamp sits above a paired S#'s MAX_LOD or has no
mip filter, follows an explicit LOD or gradient with a nonzero S# LOD
bias, pairs with a run-time S# or different min and mag filters, or
meets image_get_lod, gathers at a clamp of a level or more, or the
emulated depth-compare or 11:11:10 sample.
- ValidateShaders accepts CapabilityMinLod when the device enables
shaderResourceMinLod.
- The flag is written after hostInterpolationFirst as request
serialization version 53; older versions read it as false.
The gaps, including llvmpipe applying the S# bias after the clamp, are
recorded in docs/dev/debt/shader.md.
Tested on an M-series Mac (MoltenVK 1.4.2, x86_64 under Rosetta):
image_sample_lod_clamp samples a T# with fractional and level clamps
over BASE_LEVEL 0 and 1 in both lanes, and fails without the shader
clamp or with an absolute one; graphics, shader disk cache, shader
memory and request serializer tests pass. PPSA21564 renders the title
and reaches the warp sequence after it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
SpirvEmitter.cpp: main now calls Finalize() before filling info and checks
spanned-wave barriers on the words; keep that and run this branch's
VerifyScratchPoolReturns(state, words) on the same words before returning.
RADV gives the second triangle of a full-screen quad PerVertexKHR values
in an order that does not match gl_BaryCoordKHR, so passes whose pixel
shader uses plain v_interp_p1/p2 show a diagonal seam (Silksong final
pass on a Radeon 780M). On RADV, pixel shaders are now first translated
with host interpolation; only shaders the host interpolation checker
rejects keep native VK_KHR_fragment_shader_barycentric.
APS5_NO_HOST_INTERPOLATION_FIRST=1 restores the old behaviour.
AI-assisted. Tested by me on my own machine.
On an RTX 4090 (driver 617.42) a scratch pool buffer of 2049 MiB returned wrong scratch values in the scratch dispatch bench, while 2047 MiB was correct, though maxStorageBufferRange is larger. One pool is now held under 2 GiB, under APS5_SCRATCH_POOL_MIB and under APS5_SCRATCH_POOL_TOTAL_MIB, so a large APS5_SCRATCH_POOL_MIB no longer creates a pool over the total budget or over 2 GiB.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Each compute program that uses scratch kept its own pool of up to APS5_SCRATCH_POOL_MIB (64 MiB) until device teardown, so the pools of many such programs added up without bound.
All pools together are now held under APS5_SCRATCH_POOL_TOTAL_MIB (default 256 MiB). A new or enlarged pool first drops the least recently used pools until it fits; the newest pool is always kept. A dropped pool stays alive until the batches that recorded it complete, since the recorder keeps a reference, and the program gets a new pool with a zeroed header when it dispatches again.
The test dispatches two scratch programs over a 1 MiB budget and checks that the second evicts the first, and that the first then runs correctly in a recreated pool.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
NVIDIA's front end spends tens of seconds per large compute program on the
dynamically indexed Function arrays that held the per-lane scratch. A compute
program that uses scratch (scratch_* or a FLAT access that the aperture split
routes to the private aperture) now keeps it in a storage buffer instead, so
no Function array is declared or indexed. FLAT routing is unchanged.
The driver allocates one buffer per compute pipeline on its first dispatch,
capped by APS5_SCRATCH_POOL_MIB (default 64), holding a lock word and an
invocation counter per slot followed by the slots, and binds it in descriptor
set 2 (the layout of the device-memory LDS buffer). The shader takes the slot
count from the array length. Invocation 0 of each workgroup claims a slot with
a compare-exchange scan that starts at its linear workgroup id and spins until
it wins one; a barrier publishes the slot. Every invocation decrements the
slot's counter on every compute exit and the last one frees the lock, so
divergent exits and concurrent dispatches of one pipeline share the buffer
safely. The emitter fails the compile if a compute return lacks the release.
Programs without scratch keep their SPIR-V and pipeline layout.
The buffer is decorated Coherent: without it NVIDIA lost earlier byte stores
of a sequence of byte read-modify-writes to one dword. Lane-private
read-modify-writes no longer use atomics.
Tests: a new execution test covers the private aperture reached through a
table load, a literal and a computed address (store, atomic, and both ways
through scratch_*), a callee reached through s_swappc_b64, isolation of 64
workgroups over one or two slots, divergent wave exits with a second dispatch
reusing the slots, and the module shape (no Function array, no binding in a
program without scratch). The guarded memory load fixture skips the slot
acquisition.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A flush that publishes unit shadows waited only for writers over the
layers its images store and for heap leases over the range. A shadowed
unit elsewhere in the range is dropped by the publish when a newer
import write has stamped its block, which most writers do when they are
recorded; a small label or packet store deferred to a completion and a
CPU write-back of a copied buffer stamp only when their batch is reaped,
so such a unit, retiled after them by a flush that waits for no writer,
could be published over their bytes. The flush now also asks for
writers over the live units the publish would select, as whole units,
hulled beyond eight runs like the stored layers: a registry lookup when
there are none, and a conservative wait when there are, since a writer
whose stamp would make the publish drop the unit is waited for too.
The storage flush ordering test gains the case: an image stored into the
shadow without a publish, a fill over its first unit, a second image
flushed over the range with the default scope. The flush waits for the
fill, the fill's bytes stay, the untouched unit is published and the
second image's texel is stored. Skipped where the unit shadow is off or
the write-back did not retile into it.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The dedicated runner returned 0 when the helper refused a nonzero host
import alignment that does not divide 64 KiB, so CTest reported a run
that tested nothing as passed. The helper now reports whether it ran and
names the alignment it refused, and the runner turns a refusal into exit
code 77, which the test's SKIP_RETURN_CODE maps to Skipped. The recorder
tests take ANYPS5_TEST_HOST_IMPORT_ALIGNMENT in place of the device's
alignment, so both skips and the run itself can be exercised on one
device; a value that is not a number or not a multiple of the device's
alignment fails the run instead of skipping it.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
FlushPending waited for the newest unfinished batch that writes anywhere in the flushed range (#6364),
but its write-backs store only the pending layers the range overlaps, or every pending layer when the
image stores whole (DCC keys not uncompressed) or may widen a partial store. A writer elsewhere in the
range cannot be overwritten by those stores, yet each such wait submitted the open batch or finished the
in-flight ones under the GPU mutex. The flush now asks for writers over the 64 KiB blocks of the layers
it stores, the granularity of the write-back's own unit publishes; an image with many separate pending
runs is checked over their hull. For an image that stores whole, that includes its pending layers
outside the range, which the range check left out. A DCC image with more than one mip is checked over
all of its pending layers too: more than today's write-back stores for it, so the check stays sufficient
if the write-back stores such an image whole, as #8707 proposes. When the flush publishes unit shadows,
an address-based dispatch's heap lease over the range is still waited for: such a dispatch stamps its
pages only at completion, so the publish could not tell its writes from fresh shadow units otherwise.
APS5_FLUSH_WAIT_WHOLE_RANGE=1 checks the whole range as before.
The storage flush ordering test gains two cases, each with a GPU fill noted in the open batch: a fill
inside the flushed range but past the image's pending layer, which the flush no longer submits or waits
for while both the image's texel and the fill land; and a fill inside the pending layer, which it still
waits for, so that the fill's block keeps its bytes and a texel in another block is stored. A recorder
test pins the DCC clause: a three-mip image with uncompressed keys, flushed over its first unit,
completes the batch of a writer over its last pending unit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
PendingWriterSerial counts an address-based build's lease as a writer of
every leased range, because such a build marks the pages it stored to
through BDA only when its batch completes. A build whose shaders never
store through BDA marks nothing at completion; its descriptor writes are
already noted as pending writes when it is recorded. Its lease is now
noted as ReadKind::AddressBasedReadOnly, which PendingWriterSerial skips
and every other read check treats like any pending read.
A texture refresh flush over a range such a build leased no longer waits
for its batch. APS5_NO_READ_ONLY_LEASE_SKIP=1 notes the lease as
AddressBased, as before. LinearTargetDrawBindings runs its scenario
after a storing and after a read-only address-based draw: the drawn
target's store syncs the batch after the first and is recorded into it
after the second, unless the switch is set.
AI-assisted: yes
- ImportMappedRanges: main's `replacing` structure, with UnwatchHookLocksGpu in place of AnyPendingOverlaps.
- markLayersPending: main's `!layerPending.AllIn(first, count)` block around this branch's pendingMiss bookkeeping and captureGuestBytes(&unsaved, true). With every unit already pending, that bookkeeping changed nothing, so skipping it keeps the behaviour.
- writeBackWindows: no unit shadow destination when the stored span is not watched or the image is at most SmallStoreBytes.
- Tests: agc_storage_pending_after_unwatch and agc_unwatch_hook_lock_order move to tests/cmake/agc_driver_recorder_tests.cmake.
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]>
Every dispatch records a barrier before and after itself, and on NVIDIA
any barrier between two dispatches drains the GPU.
A dispatch of a compute ring (queue != 0) with only SET_SH_REG and plain
NOPs between it and the ring's previous dispatch in the same submission
may join that dispatch's run: it records no leading barrier, and the
run's trailing barrier is deferred until any other command records it.
It joins only when its buffer ranges and storage images are disjoint
from the run's (reads against writes, writes against writes); an
address-based or BDA build counts as accessing everything, and a run
never takes the same resources object twice. A dispatch that records
GPU checkpoints (NV checkpoints or AMD buffer markers) never joins.
APS5_NO_DISPATCH_OVERLAP=1 turns it off.
AI-assisted: yes
Recorder keeps both this branch's LookupStoredBytes and main's UnlandedOwnLabel, with both test groups in --completion-labels-only and the full recorder run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The replay kept the newest APS5_PIPELINE_JOURNAL_ENTRIES entries of
either kind, so compute pipelines used up the bound meant for the
graphics pipeline store, and the entries left out were not reported.
Graphics entries now count against that bound alone, compute entries
against APS5_PIPELINE_JOURNAL_COMPUTE_ENTRIES (1024); each kind keeps
its newest entries in record order, and a line says how many of each a
replay leaves out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>