Every import lookup walked the lease out from the containing range to find
the run of continuing registry ranges, which is linear in the run length.
That walk depends only on the lease snapshot, the containing range and the
import window block, so its first and last index are now memoized per
thread under a weak reference to the snapshot, the range index, the window
and the window's low end. APS5_NO_RUN_MEMO walks every time.
Everything that depends on other state runs on each call as before: the
largest size, the budget, the failed blocks, the remembered refusals and
the halving block search. importRunBounds returns the chosen indices, so
sameRange, runCovers and the whole-run lookup in importAllocation compare
against the lease without copying the run's range pointers.
importRunFailureShrinkTests records a failed 8 MiB block between two
lookups on an unchanged registry and requires the run to shrink to its
4 MiB blocks. importRunGrowthTests and importRunReplacedTests check that a
run grows and shrinks when its ranges are registered or replaced.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The second half of a pipelined draw (stage materialization, cache insert,
enqueue) runs on a finisher thread, overlapping the next draw's packet
work; draws whose stages could write memory are held back. Commits skip
work whose inputs did not change since the last commit, color metadata
passes are ordered through the pipeline, resource and storage-image proofs
are reused within one collect epoch until a write breaks them, and a shape
entry is refreshed in place at the second sight of the same read user words.
APS5_VERIFY_DRAW_PIPELINE checks reused proofs against a full check.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A read of memory inside a stale D32 Z_X depth plane (FlushDepthPlanes) stored
the whole plane: a copy of the depth image into the mirror, MarkWholeWritten
and a write-back of every byte. In Super Meat Boy 3D (#8840) a read-only
buffer ending about 3 KiB inside a stale 3840x2160 plane makes
ShaderResources::Revalidate flush ~1000 times per 5 s, each one moving
31.9 MiB, bound by transfer bandwidth.
A depth surface now tracks its plane as 64 KiB units, each one 128x128 tile
of the mirror's block units: a stale bit and a write generation per unit
replace memoryStale and the plane-wide syncedGeneration.
- FlushDepthPlanes stores only the stale units the range overlaps: the depth
to mirror copy takes one region per unit rectangle (UnitRects, from the
mirror's slice windows), the mirror marks just those units written
(MarkUnitsWritten) and the write-back covers their hull. The other units
stay stale and the plane stays registered. Stale bits are cleared only
after the copy was recorded, and the stored units' generations advance
only when FlushPending stored them. A depth fast clear still pending at
the store rewrites the whole image, so it makes every unit stale first.
- SyncFromMemory copies from the mirror only the units the guest wrote since
their own generation (one ChangedBlocks scan; untracked memory counts as
unwritten, as WrittenSince did), so a guest write into one unit no longer
replaces GPU depth that is still unstored in the others.
- Flushes followed by a retire keep the whole-plane store: the color target
flush in Draw and a written storage view (DepthPlaneScope::Whole). The
retire of an overwritten surface in DepthSurfaceTexture first stores its
remaining stale units the guest did not write.
- A mirror without block units (APS5_NO_BLOCK_TRACKING, unaligned base)
falls back to the whole-plane copy, and APS5_DEPTH_PLANE_WHOLE_STORE=1
tracks the plane as one unit, the previous behaviour.
- DepthPlaneStoreCounts reports stores, units and bytes stored.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When a draw's stage fails to prepare, capture or recompile, the packet
worker fails the queue and the next Submit or WaitIdle rethrows the
error, which ends the title. With APS5_SKIP_REFUSED_DRAWS set,
materializeDrawStage rethrows that error as a RefusedDraw; the packet
loop skips the draw, counts it as a thrown skip, logs the shader once
per address and runs the rest of the submission. The frame then lacks
that draw. Compute refusals and errors outside the stage build fail as
before, and without the switch nothing changes.
agc_driver_draw_unsupported_buffer_type_skip_refused submits a draw
whose pixel shader is refused twice with the switch: the WRITE_DATA
after it lands both times and the color target stays untouched.
AI-assisted: yes
The title allocates, maps and releases about 48 direct-memory blocks per frame.
Each allocation created a pagefile-backed section and each release closed it.
A released section whose pages were never stored to (resident pages keep their
data in private memory and saved buffers, not in the section) is still blank,
so up to 256 such sections of at most 16 MiB, 256 MiB in total, are kept by
size and handed to the next allocation of that size instead of a new
CreateFileMappingW. APS5_NO_DIRECT_MEMORY_SECTION_POOL keeps the old behaviour.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
F12 (the new Screenshot input action) requests a capture of the next
presented display buffer. The presenter records it with the existing
APS5_DUMP_FRAMES GPU readback at full resolution, and the frame dump
writer thread encodes it with decoder_png into anyps5-screenshots/
beside the executable, or ANYPS5_SCREENSHOTS.
The fast proof of a cached ShaderResources proves each sampled texture
from write stamps, keys and the pending registry. It never asks whether
the sampled lookup would still return the bound Texture, so a template
whose surface memory is unchanged keeps binding an old object:
- After the sampled cache drops or replaces the entry (#6492 marks the
texture released), Find keeps serving the template until the next
Insert, and its fast proof passes.
- When the lookup's answer changes without any lookup running (a
snapshot whose surface became host-imported is viewed through its
storage image from then on), nothing replaces the entry and the
template keeps the snapshot until it leaves the resource cache.
The fast proof now fails with 'replaced texture' when a bound texture
is released. A template whose last full walk is older than
APS5_TEMPLATE_AUDIT_MS (default 1000, 0 turns it off) takes the full
walk instead of the fast proof ('audit'), so a different lookup answer
fails the proof and the template is rebuilt.
Build the call tracer for Linux as well as Windows. The thunk area is a
memfd mapped twice, writable and executable, and threads are named by
gettid. Linux has no crash report, so the first traced import installs a
SIGABRT handler that prints the last calls and the most called imports,
then passes the signal on to the previous handler.
guest_call_trace and guest_call_trace_abort now run on Linux too.
Queue 0's draw committer records the draws the packet worker hands
over. When the worker's APS5_SAMPLE_WORKER stacks wait in
DrawPipeline::Enqueue, the committer is the thread that bounds the
frame, and only APS5_SAMPLE_THREADS sees it: 40 addresses, 12 frames
deep, without whole stacks.
APS5_SAMPLE_COMMITTER=<file> attaches the worker's sampler to the
committer when its thread starts and writes the same two files. Both
use one start function, and StopWorkerSampler stops both.
An image's own VkDeviceMemory was allocated and freed one at a time, and in a scene that re-creates its storage images at every dynamic-resolution step the dispatching queue worker spent half of its image-table resolution freeing evicted images and most of the rest allocating new ones. The pool now keeps the device memory of a released storage or sampled image under its own budget (APS5_IMAGE_POOL_MIB, a quarter of the device tier's budget by default) and hands it to the next image of the same memory type and size.
An indirect dispatch with USE_THREAD_DIMENSIONS read its thread counts on the
CPU, which waits for the recorded work that stores them. The argument kernel now
turns them into group counts on the GPU and copies the counts into the
ShaderData thread limit, so the dispatch stays on the GPU path.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Smurfs - Dreams (PPSA21607) reads a 1x1 RGBA16F R64KBX storage image
on the CPU after a label. The label-time store took only linear images, so
the image stayed pending and the sky light read 0. Tiled images up to
SmallStoreBytes are stored before the label as well.
Reported and tested by Pacuka.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
When the list read at submission is invalid, the worker now always
executes the copy read again where the packet runs. With
APS5_SKIP_INVALID_INDIRECT_REGISTERS=1 only that copy's invalid entries
are skipped, so the valid entries of a partly updated list are written
instead of the stale ones from submission.
testPartlyUpdatedRegisterListSkipsOnlyItsInvalidEntries submits a list
that is invalid at submission, updates it to a valid entry plus an
invalid one before its packet runs, and reads the register back with
COPY_DATA.
SET_*_REG_INDIRECT lists are read when the command buffer is submitted.
When that copy holds an offset dword that is no register offset, the
queue worker now reads the list again where the packet executes (as the
console PFP does) and uses that copy if it is valid.
PPSA23566 (UFC 6) intermittently submits a SET_CONTEXT_REG_INDIRECT
whose first entry is 0x038f038f or 0x03900390 in both copies, followed by
180 valid pairs, and the title stopped with "extended register semantics
are not implemented". With APS5_SKIP_INVALID_INDIRECT_REGISTERS=1 the
invalid entries are skipped and the other registers are written; without
it the packet throws as before.
An indirect draw read its count and records only after every stage had been captured and
compiled, so a draw whose records all have no vertices or no instances (what GPU-side culling
leaves for most draws in a culled scene) cost a full stage build to draw nothing.
When the front program's last indirect draw took the CPU path, the draw now reads its count and
records first, through the same flush hook, and returns before the stage build when no record
draws. APS5_NO_EMPTY_INDIRECT_SKIP=1 builds them as before. The [draws] profile line counts the
skipped draws. agc_driver_empty_indirect_skip_tests draws one record, two empty ones and one
more, and checks that the empty ones were skipped and the last one draws as the first.
Co-Authored-By: Claude Opus 5.5 <[email protected]>