derive() learned the LS-to-HS stride from the control-point term of hull LDS reads, or from the patch term of reads at a computed control-point index. A hull with one input control point reads it at constant offsets, so neither applies and the stride stays unknown ('the hull shader reads no LDS per control point, so the local stride is unknown'). Every hull read already has a patch term of exactly inputControlPoints * localStride, so take the stride from it for reads without a control-point term too. Resident Evil Village's quad patches have one input control point.
Every hull program with a BDA access was refused with "BDA fault
termination requires a barrier-safe tessellation-control execution
protocol". The refusal predates BdaInvocationsMayStop: a program with
s_barrier no longer stops a faulting invocation; the fault is recorded,
the read returns zero and every invocation reaches the barrier. In a
hull, s_barrier is the only control barrier the backend emits after the
guest code starts, so that rule already covers it, and a hull without
one can stop an invocation without stranding a barrier.
The tessellation test draws hulls that scale their levels by a BDA read,
with and without s_barrier, and requires an unmapped read to report a
tessellation-control fault (not on llvmpipe, which runs hull loads that
the unmapped-address check skips). The contract test now accepts a hull
with s_barrier.
Hull programs whose relative patch index (v1 bits 7:0) reached anything
but a tessellation ring or LDS address threw, because the lowering runs
every Vulkan patch as relative patch 0. The quad terrain hull 0x80002fc700
in Ghost of Yotei is one, and so is the domain 0x8068fbd900 (v7).
Such hulls and domains now take (PrimitiveID mod GE_CNTL.PRIM_GRP_SIZE)
mod VGT_LS_HS_CONFIG.NUM_PATCHES, as if hull threadgroups took NUM_PATCHES
patches at a time out of each primitive group; both stages see the same
PrimitiveID for a patch. This holds only when every instance starts a new
threadgroup, so it needs GE_CNTL.BREAK_WAVE_AT_EOI (bit 18); with the bit
clear the instances share threadgroups and such programs still throw.
PRIM_GRP_SIZE and BREAK_WAVE_AT_EOI join the tessellation configuration
(draw keys, hull and domain cache keys, request version 24). A missing
GE_CNTL throws, and so do local programs, whose relative vertex index
depends on vertex reuse that Vulkan does not expose.
A wave64 hull program narrows EXEC with s_and_saveexec_b64 and restores it
later, so its exec words reach the lane-bit test as ANDs with the host
ballot, as loop or join phis, and through separate ballots for the two
halves. Tessellation lowering refused them as "uses a lane mask other
than its own lane's bit".
The own-lane bit of a mask pair is now rebuilt from the words' operations
(ballots, bitwise ops, selects, phis, constants); both halves must have
the same shape and ballot the same predicate. Dead hull cycles such as a
saved-exec loop are removed before the remaining-ballot check, so a mask
whose other lanes are read (s_bcnt1_i32_b64 exec) is still refused. The
words are replaced through identities and block conditions are resolved,
so a branch on a compare copied to VCC keeps its condition. Local and
domain programs keep their exec words for the half-wave matcher.
Block conditions fix and HullCompareBranch test from EagleBearerYS.
A join runs before any region confirms an import and a retired import lives
through the retire-hold, so refusing it for held overlaps kept the joined run
copied for the rest of the build.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
agc_driver_graphics (BdaResources.cpp), each failing without its change:
- a view in the last window of a range whose first window was imported
binds through an import of the whole range;
- an address-based build over the budget leaves the range's window in
place (3 builds, same import);
- two uses across neighbouring cell edges share one import over 10
rounds;
- a range mapped at the budget is refused at map time and retires
nothing.
agc_driver_host_import_windows: windows follow their range, and
ClearHostImports drops them.
A registered range larger than 64 MiB is imported one 64 MiB window at a
time for buffer and fill accesses instead of whole, and the live imports
stay under a budget: APS5_HOST_IMPORT_MIB, else the larger of 6 GiB and
half the physical memory, at most the largest host heap less 2 GiB
(imports and type-3 mirrors share it; #167's rule). An import past the budget
retires the least recently used imports not used in the last 256
lookups.
- An access across window edges imports the cells it touches, grown to
the union of the range's imports it overlaps, so uses across
neighbouring edges share one import instead of replacing each other.
- An address-based build (AcquireRegistered) imports the range whole; a
window over its first byte does not serve it.
- Every refusal (alignment, a failed run, a held import, the budget with
what may be evicted) is decided before anything is retired.
- An import made when a range is mapped never evicts: the map observer
holds no GPU lock.
The window size and the budget have setters for the tests.
The mimalloc in PS5MC (PPSA99420) reads vm.overcommit at start-up and
stopped on the unsupported name. It reports FreeBSD's default, 0.
Co-Authored-By: Claude Opus 5.5 <[email protected]>