A sampled view that a live depth surface's plane cannot serve (another
layout, more levels) is left to guest memory when a later write retired
the surface, and refused while the surface is the last writer of its
memory. Only writes the CPU can see counted. A compute pass that writes
storage images over the plane is a later writer too, but its results
stay pending in storage images, so the surface still looked current.
Forspoken (PPSA03984) builds a 5-level R32 float chain over the memory
of its 1920x1080 D32 depth target. When it sampled the whole chain
before those results reached memory, it stopped on "sampling the depth
plane ... levels 0-4 ... is not implemented". A pending storage result
over the plane now retires the surface for such a view, as a CPU write
there does.
PendingChainCase writes level 1 of a two-level R32 float chain over a
plane with a compute dispatch and flushes it, so the chain's storage
image exists, as the title's does from frame to frame. It then draws a
D32 surface there, writes the chain again and leaves the result pending.
A two-level view must then be left to guest memory with the depth
surface retired. On main the view is refused with the title's message.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On NVIDIA's proprietary driver, function arrays that live in local memory
and are read under a data-dependent branch return wrong values in
subgroups 1-3 of a mesh workgroup; subgroup 0 is correct. A wave64
geometry program whose relative addressing keeps its VGPRs in the vector
window therefore loses register writes in every wave but the first. In
the clipping geometry program of Sony's PS5 PSP emulator (PPSA07550) the
empty waves never cleared their candidate primitive slots and wrote
degenerate triangles over wave 0's, so the 3D menu drew spikes.
On that driver a mesh program's window now lives in a device-local
storage buffer of 256 workgroup slots of 65536 dwords, claimed through a
per-slot lock and released after the final barrier. The pool is created
before the first draw that uses it records anything, and its header is
zeroed in a batch of its own that is submitted and waited for; the pool
is published only after that wait, and a failure releases it. Direct
register accesses keep one function variable per register; a direct
store also writes the buffer when a buffer read can see it, relative
reads load the buffer, relative writes store only to it, and a direct
read that may follow a relative write loads the buffer and refreshes the
variable. A buffer entry is zeroed only when a buffer read can see it
before a write. APS5_NO_MESH_WINDOW_BUFFER=1 keeps the arrays.
A cached address space keeps a heap mirror whose range has left the write watch while the budget refuses the range's import, and such a mirror is copied in full at every build. When the budget is held by imports a build uses every time, as the pieces #8380 imports of a heap over the budget are, the import was refused at every build, each refusal counted, and every build copied the whole range (about 1.1 GiB in Astro Bot). The check now evicts for that import as the copy cap does: idle imports, and imports a heap mirror can replace that no build in progress holds.
An address-based build that copies a range the budget refused and no mirror serves (a range outside the write watch, or not releasable) evicts idle imports when the copy would pass APS5_ADDRESS_COPY_MAX_MIB. Imports used in the current or the previous frame (before two frames have ended, within the last 256 lookups) never count as idle, so once a build uses an import every frame, as it does for the pieces #8380 imports of a heap over the budget or a whole heap imported before, nothing can be evicted and the build aborts. That eviction now also takes imports the build has not taken yet whose single range is releasable and write-watched: the build then serves that range from a heap mirror instead.
The barycentric tessellation path refused any draw with the primitive
restart bit set. A non-indexed strip ignores the bit, and on a device
without VK_EXT_primitive_topology_list_restart a list never restarts on
the host, since its indices are compacted on the CPU first, so both now
take the patches. Indexed strips with primitive restart still throw,
now also in Graphics::Draw, which knows the draw is indexed.
Patch-list pipelines never enable primitive restart, which Vulkan does not
allow for them without primitiveTopologyPatchListRestart.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A capture snapshot read every committed block through the coherent path,
which refuses memory under a multisampled color target, its FMASK or
native multisample depth whose samples exist only on the host, so the
capture stopped there. The snapshot now writes those bytes as zeros,
counts them as host_only_sample_bytes in capture.json and adds a
warning, and the replay starts those targets without their earlier
contents.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A surface retires when a write overlaps either of its planes. The stencil
plane's pending GPU results were dropped with it even when the write only
covered the depth plane. Retiring for a color target, a storage write or a
guest write now stores a stale stencil plane first unless that write
replaces the stencil plane itself.
AI-assisted: yes
A cube T# whose last array slice is 6n - 1 is a cube array of n cubes.
The depth plane path accepted one cube only (last array 0 or 5) and
threw for the rest; it now gathers all 6n slices into the 2D array
view, as the texture path already lays cubes out. The refusal message
also names the last array slice and the copy format.
Uncharted 4's orphanage samples a two-cube D32 shadow array this way.
AI-assisted: yes
Only the depth plane of a depth surface reached guest memory: stencil
lived in the host image alone. A title that moves stencil with buffer
copies read stale bytes from the source plane, and the destination
surface never saw what the copy wrote. Uncharted 4 clears its G-buffer
stencil plane with a fill, then copies it into the lighting depth
target and on into last frame's planes with a buffer copy kernel every
frame; its lighting kept the stencil marks of earlier frames, so
characters left trails and the image doubled while the camera moved.
The stencil plane of a single-sample surface with the 64 KiB Z swizzle
is now marked drawn when the surface is bound, stored whole when a read
overlaps it (FlushDepthPlanes, like the depth plane) and reloaded from
guest memory when a collected guest write changed it before the surface
is bound or its stencil sampled, unless a stencil clear is pending.
agc_driver_stencil_plane_memory draws stencil, reads the plane through
FlushDepthPlanes, then writes it from the CPU and tests it with stencil
EQUAL; it fails without the change.
AI-assisted: yes
PS4 color and depth targets, PS4 graphics EOP events, and the PS4 tile
modes (1D/2D thin and display, depth with tile split, PRT, linear
general) on the AGC executor, with a GPU detiler for every mode.
Textures sampled over the padded corner of a render target are GPU block
copies, and the PS4 SDK pattern-fill compute kernel runs as a GPU fill
when it repeats one dword, so render targets and HTILE are cleared
without a CPU drain. Two-dword patterns keep running the kernel: as
fills, Limbo's clears of its 64-bit target made its gameplay advance
about half as far per frame.
Tests cover the tiling equations against addrlib formulas, the GPU
detiler for every mode and element size, and the fill ranges.
writeBackLayers printed "StorageTexture writeback submit" and "StorageTexture
writeback done" unconditionally around the blocking submit of every storage
image writeback. In five GTA V (PPSA04264) runs they were 54 to 978 lines per
run, 5 to 55 % of the log. They now use APS5_LOG_CHARS_OUT_DEBUG, like the
other per-operation lines of the AGC driver, and are compiled out unless
APS5_DEBUG_LOG is defined.
Co-Authored-By: Claude Opus 5.5 <[email protected]>