On Windows a thread_local destructor in a module runs after emulated TLS freed
the object (prx_thread_local_destructors). Keep the per-thread map in
HostThreadLocal like the other per-thread objects in libSceAgcDriver.
The draw was split into draw and finishDraw, so the skip flag and the per-program
classification cache declared in draw were out of scope where finishDraw records
the classification. Indirect draws never hand off to the finisher, so both
functions run on the same thread; make the cache and the switch file-scope
accessors that both use.
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]>
A CPU store made inside a completion action (a copied buffer's write-back)
reached the flush hook with PublishScope::None, and PublishShadow skipped
such stores anyway. Nothing was published before the store landed, and
its own stamp then made every unit it touched stale, so the next publish
dropped those units whole: the storage results the store did not cover
were lost, both those already in the unit shadow and those of a pending
image the hook had just stored into it.
The hook now publishes the units such a store covers partly, as outside
completion actions, and its full Sync inside completion actions lands
the copies before the store. PublishShadow, FlushPending and
PublishShadowsOnly take a waited flag for this; other publishes made by
completion stores are still skipped.
On Windows only host imports of 1 MiB or more are settled and stay
write-watched; a smaller import is compared, so its range leaves the
watch and the unit shadow never engages. The address shadow test's heap
was 256 KiB, so both of its tests skipped on Windows with "the heap is
not imported or not watched", with the fix and without it.
The heap is now 2 MiB, filled exactly by a 1024x512 storage image, and
its allocation keeps one unregistered 64 KiB block after the range, so
no other registered range can continue it into a larger import run.
The assertions and Linux's forced watch are unchanged.
An address-based build binds the registered heaps in place and does not
store the storage results pending in them. With unit shadows on, a
write-back that retiled into the shadow reaches the import only through a
publish, and nothing published before such a build, so its pointer reads
saw the bytes of the last publish.
UploadFinish now publishes the fresh units over the heaps the build maps:
the address space's base on the cached path, the exempt in-place regions
on the per-build path. One AnyShadowedOverlaps check over their span
gates it, pending results stay pending, and the copies count under their
own publish reason, "address".
A WRITE_DATA or RELEASE_MEM label the worker queues for the committer
drained the draw finisher first, so that it could not reach the committer
before the draws the finisher still held. In Hades II nearly every drain
of the finisher was one (81,762 of 82,467 per 10 s were WRITE_DATA).
While the finisher holds draws, the label's enqueue now runs as a
finisher job, so it reaches the committer after them, and its write
range and value are a pending write of the finisher until then. A
WAIT_REG_MEM that a pending label satisfies, in the finisher or behind
it, stays beside the finisher as it did beside the committer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On Windows a thread_local destructor in a module runs after emulated TLS
has freed the object, and prx_thread_local_destructors refuses
libSceAgcDriver.prx for it. The draw pipeline of upstream PR 6946
(Raghav314, f8192a64b3) added such scratch objects:
DrawFinisher::Enqueue's and DrawPipeline::Enqueue's reclaim lists,
CachedFragmentOutputs' last key and outputs, DecodeVertexStageInfo's
attribute and buffer tables, and PrepareDrawBindings' pool size counts.
They now live in HostThreadLocal, as the driver's other per-thread
scratch does; on other hosts that is the same thread_local.
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]>
agc_idle_publish_wait fails on RADV (RX 570, Steam Deck, #9781) at the label
flush check: the publish was not waited, it was dropped as stale. The idle
flush's submission is the first to reference the test block's host import,
and amdgpu validates a user pointer's pages with write faults at every
submission, which clears the asynchronous userfaultfd write protection of the
whole import. The test forces APS5_WRITE_WATCH_IMPORTS=watch (the probe in
decideImportWatch would leave such imports compared), so the next collect
reads every unit as stored by the CPU. The test retiled unit 5 without
collecting first, so the label publish's own collect stamped the unit newer
than the retile's generation and publishUnits dropped it; no copy was
recorded and PendingWriteOverlaps was false.
The test now collects before that retile, as it already did before the idle
ones, and models the condition on every driver with an uncollected store
older than the retile. The checks run in an order that names the dropped
publish before the wait.
ExpectTruncated draws with a two-sample D32 depth target. On a device
whose framebufferDepthSampleCounts lacks 2, as lavapipe, the pipeline's
existing sample count check refused that depth target before the
truncated coverage check, and the test failed. The case now reports why
it was not tested: no two-sample depth targets, or neither mixed sample
extension with truncated coverage reduction.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An overrasterized draw (a multisampled rasterizer into one-sample color
targets, VK_NV_framebuffer_mixed_samples) chains
VkAttachmentSampleCountInfoNV into its pipeline. When the pipeline is
linked from graphics pipeline libraries, only the fragment output library
carried it, and the link and the background link-time-optimized relink
did not. The Khronos validation layer (1.4.363) then reports
VUID-vkCmdDraw-multisampledRenderToSingleSampled-07285 for every such
draw, as it does in agc_driver_pixel_sample_coverage, and not with
APS5_NO_GPL=1, where the pipeline is built whole.
Both links now chain a copy of the sample counts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>