A dispatch whose group count exceeds maxComputeWorkGroupCount aborted the
process; a software device (lavapipe) reports the spec minimum 65535, and
Ghost of Yotei (PPSA26344) dispatches 129199x1x1 in its presentation path.
The count is now recorded as consecutive vkCmdDispatchBase chunks of at
most the limit per dimension, each continuing the grid at its base offset
(a plain vkCmdDispatch would restart group ids at 0 and re-execute the
first chunk's workgroups), with a barrier between chunks so a later chunk
sees an earlier one's writes. Compute pipelines are created with
VK_PIPELINE_CREATE_DISPATCH_BASE_BIT, which a nonzero base requires.
A split on NVIDIA throws instead of dispatching: measured on an RTX 5070
Ti, the driver drops the first workgroups of a vkCmdDispatchBase dispatch
with a nonzero base (reproduced with a standalone Vulkan program outside
this repository), so its 65535 Y/Z limits cannot be split through. A split
of a dispatch whose LDS lives in device memory also throws: its buffer
slot base is computed from gl_NumWorkGroups, which inside a chunk is the
chunk's count, so workgroups of different chunks would alias (a 1D X split
only works because the base degenerates to the group id).
Fixes#3212
GuestMemory.cpp carried its own procmap_query fallback and a ProcMapsQueryFd that opened /proc/self/maps once, probed PROCMAP_QUERY and honoured APS5_NO_PROCMAP_QUERY, the same as HostMapping::QueryFd. It now includes HostMapping.hpp and calls HostMapping::QueryFd.
On an RTX 4060 (Ada, driver 617.42) the optimized link of a pipeline built
from libraries loses the device (VK_ERROR_DEVICE_LOST) as soon as it is
bound, which fails agc_driver_pixel_valid_mask in 16 of 20 runs. With
APS5_NO_PIPELINE_LTO=1 it passes 20 of 20. Reporters see the same loss on an
Ampere card with a depth attachment; two Blackwell cards do not lose it.
A device whose driver is VK_DRIVER_ID_NVIDIA_PROPRIETARY keeps the
fast-linked pipeline and no longer queues the link-time-optimized relink.
Other drivers are unchanged.
Refs #2866
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
sceAgcFuseShaderHalves returns a fused hull as a copy of its back half
(type 7) that is not registered: type 3, no user data, and shader
registers copied to scratch memory with the checksum, RSRC1/RSRC2 and
the LS program address rewritten. sceAgcCreatePrimState accepts only that
type-3 header, and graphics ABI resolution refused it as a replaced
shader header.
SameHeader now also takes a type-3 header as the registered type-7
header for the same code when every other field is equal and its shader
registers differ only in the fused ones. Graphics ABI resolution then
applies the bound header's shader registers over the registered state.
PPSA26344 fuses its hull halves during its loading screen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
QueueState held the 48 KiB constant RAM inline, so every QueueState
cleared 48 KiB when constructed and copied it when copied.
ResolveShaderAbi and ResolveGraphicsStagesAbi construct one on each call
(sceAgcLinkShaders, sceAgcCreatePrimState, interpolant mapping),
resolveGroupAhead copies the live state, and a queue reset builds a new
one, although few command buffers write the constant RAM.
The constant RAM is now a vector allocated when WRITE_CONST_RAM first
writes it. ConstantRam returns a shared all-zero view until then, so
DUMP_CONST_RAM of unwritten words still stores zeros, and the bounds use
QueueState::ConstantRamWords. The store view ResolveStore returns is
copied by the recorder within the packet, as before.
testMemory dumps unwritten constant RAM and checks for zeros, and checks
that a copied queue state keeps the words written before the copy.
A QueueState is 336 bytes instead of 49464; constructing one takes 0.3
to 0.8 us instead of 1.5 to 2.1 us, and copying one 0.4 to 0.6 us
instead of 1.9 to 3.8 us (GCC 13 -O3). On the title screen of PPSA21402
(Balatro) the clears from the two resolvers took 30 to 129 of 3459 to
7838 CPU samples per 10 s.
Iterating a Registers advances with nextPresent, which recomputes the
word, mask and next bit from the index on every step, about 3.5 ns per
register. BuildRegisteredAbiKey walks all of a state's registers, about
300 with the context, and ResolveGraphicsStagesAbi walks each stage's
registered state to merge it, on every sceAgcLinkShaders,
sceAgcCreatePrimState and interpolant mapping call.
Registers::ForEach visits the set registers in ascending order straight
from the presence words, and both paths use it. The key and the merged
state are unchanged.
testRegisterFile checks that ForEach visits what iteration visits, in
order, and skips an erased register.
Building the key for 297 registers takes 200 ns instead of 1030 ns (GCC
13 -O3). On the title screen of PPSA21402 (Balatro) the two functions
took 0.18 to 0.29 CPU samples per sample of the title's own code, and
0.06 to 0.12 with this change.
The window was read from APS5_HOST_IMPORT_WINDOW_MIB on the device.
It is now always 8 MiB, the value the previous commit was measured
with; SetHostImportWindow stays for the tests. TechnicalDebt records
the window as a choice.
one span. A title that grows a heap 16 KiB at a time then re-imports the
whole chain on every growth: Ratchet & Clank: Rift Apart re-imported about
1.5 TiB while loading its first scene, and NVIDIA refused grown spans with
VK_ERROR_OUT_OF_DEVICE_MEMORY, which leaves those ranges copied per draw.
Ranges are now joined only when a chain covers a whole aligned window (8 MiB
by default, APS5_HOST_IMPORT_WINDOW_MIB on the device); until the window is
complete each range is imported on its own, and a span never crosses a
window edge. Growth therefore re-imports a range once more at most when its
window completes, instead of the chain on every growth. Joining any part of
a window was tried first and still re-imported 2 GB per second in Ratchet,
as each growth re-imported the window's partial span. A single range larger
than the window is imported whole. SetHostImportWindow(0) keeps the
unbounded spans, which is what the existing tests use.
The new importWindowTests (agc_driver_graphics) checks that a full window is
one span, that a partly filled window imports its range alone, that a view
crossing a window edge gets no import, and that completing one window leaves
the other window's import in place; with the window ignored it fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DecodeShaderStages accepts LS, HS and an NGG passthrough ES, reads the
3-bit VGT_TF_PARAM fields, refuses its other bits but DISTRIBUTION_MODE,
and needs HOS levels that keep [1, 63]; 0x286-0x287 join the draw key.
Local and domain programs are prepared with their stage group: its
headers are merged over one set of defaults, and a local program left
unbound by the hull header is prepared at its first draw.
The local program ends at its jump into the hull half.
TessellationLowering lowers LDS and ring addresses that are affine in
the control point and relative index to tessellation interfaces with
strides from the code, and refuses accesses outside the patch, per-patch
writes from other control points and a modified primitive export. Hull
lanes read their own saved exec bit, also of host-subgroup-masked
copies; other masks are refused, gl_InvocationID loads as a scalar and
factors clamp to 63.
The typed descriptor heaps write a null descriptor for a null guest
image, and the device therefore required VK_EXT_robustness2's
nullDescriptor. MoltenVK has the extension but not the feature, so on
macOS no Vulkan device opened and every GPU test was skipped. The
feature is now enabled when the device has it. Without it, the device
creates a placeholder image per typed image heap: 1x1, cleared to zero,
of the heap's dimension, sample count, numeric class and access, and a
null guest image binds the placeholder of its heap. A heap whose
placeholder the device cannot create throws when a null image reaches
it. TechnicalDebt.md lists where a placeholder differs from a null
descriptor.
agc_placeholder_images checks every image resource the recompiler maps
to a heap against that heap's placeholder, so the two cannot drift
apart, and agc_shader_device_profile now expects a device without
nullDescriptor to be accepted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Rebased onto main as one commit (the six commits of #374). A uniform fill over a depth surface's whole
HTILE (DMA_DATA, the fill kernels or a CPU store) clears the depth and stencil aspects its ZMask and
SMem fields say, to DB_DEPTH_CLEAR / DB_STENCIL_CLEAR, when the surface is next bound or sampled.
Main's RADV clear_htile_mask recognition (#2229) now goes through the same pending clear: an HTILE clear
marks the matching surfaces' depth aspect, and one for a surface not created yet is applied when it is
first bound. Main's HTILE address decode and this one are merged into one (TILE_SURFACE_ENABLE, a
nonzero DB_HTILE_DATA_BASE and a depth format).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A draw or dispatch whose shaders have guarded descriptor slots (or a rect
list) but no BDA table gets a fault buffer, and with it NeedsCompletion(),
which kept its resources out of the resource cache, and NeverReusable
skipped the cache lookup for it. In PPSA36082 nearly every draw in the hub
is such a build, so the cache served none of them.
Such an object has no table to revalidate, no stores through BDA and no
copied writes, so it is now reusable. Each use of a reused object checks
the fault record when its batch completes (CheckFaults) instead of
running the once-only write-back; draw recipes over such a template
register the same check, and dispatch recipes still skip templates with
completion work. NeverReusable now refuses what noteReusable refuses: a
BDA table, or a fault buffer with APS5_LOOP_GUARD set (its fault resets
the buffer).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain the original WAIT_REG_MEM fix from #2985 and resolve its test-registration conflict with main d7cfa728. Preserve both upstream test blocks and the original commit ancestry.
Local conventions and whitespace checks pass. Windows/GPU results cited in issue #2832 belong to their reporting contributors. No local GPU execution.
Shaders with f64 instructions now declare DenormPreserve for 64-bit floats
when the device reports shaderDenormPreserveFloat64. Without it Mesa ANV
flushed the subnormal result of v_fma_f64 to zero.
(cherry picked from commit 7df6c709de994cea0019707d45b6e7b96698fad1)
The buffer fill and copy fast paths returned before the partial last group
was applied, so a PARTIAL_TG_EN dispatch of the fill kernel filled whole
groups, and a zero last group did not throw. The partial sizes are now
decoded before the fast paths, which are skipped when the last group is
partial; the dispatch then runs the shader with the partial thread counts.
PartialGroupThreads validates all three axes before an empty dispatch
returns, and throws when (groups - 1) * full + last does not fit in 32 bits
instead of wrapping. Validate rejects PARTIAL_TG_EN combined with
USE_THREAD_DIMENSIONS (initiator 0x63).
agc_driver_pattern_buffer_fill now runs the fill kernel with a 16-of-64 last
group through the driver and checks that 80 dwords are written.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV sets the draw and dispatch indirect base with SET_BASE to the
argument buffer address plus the command's offset, which Vulkan requires
only to be a multiple of 4; vkQuake's base ends in 0x14. The validator
required 8-byte alignment. The records are read as 32-bit words, so a
4-byte-aligned base is now accepted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV emits 0 as the start-instance location of DRAW_INDIRECT and
DRAW_INDEX_INDIRECT when the vertex shader does not read the base instance,
and as the draw-index location of the multi forms when draw IDs are off.
The validator accepted only 0x280 as "no register". Location 0 is now
accepted and decoded as 0x280; vkQuake's draws use it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV clears ORDER_MODE in the dispatch initiator for a dispatch that
needs its waves launched in order, and the validator required it set.
In-order launch is the stricter guarantee and the driver does not reorder
waves either way, so a direct or indirect dispatch now needs only
COMPUTE_SHADER_EN; vkQuake's map load issues one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV writes every COPY_DATA memory destination with DST_SEL 5
(COPY_DATA_DST_MEM in Mesa's sid.h), the current form of the deprecated
GRBM-synchronized DST_SEL 1 that was already accepted. vkQuake's timestamp
writes use it. It is now validated, resolved and executed as memory like 1
and 2; GDS and performance-counter destinations are still rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Where the GPU has no LOAD_CONTEXT_REG_INDEX, RADV loads an image's clear
values into DB_STENCIL_CLEAR/DB_DEPTH_CLEAR and CB_COLORn_CLEAR_WORD0/1
with COPY_DATA from memory to a register (DST_SEL 0), one or two dwords.
The validator rejected every register destination.
A COPY_DATA from memory to one or two context registers is now accepted.
It is not a memory store, so the packet drains like other COPY_DATA the
driver cannot record on the GPU, and executing it reads the source then and
writes the registers. Register, GDS, immediate and clock sources, and
destinations outside the context registers, are still rejected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RADV sets PARTIAL_TG_EN in the dispatch initiator for an unaligned direct
dispatch, with each axis's last-group size in the upper half of
COMPUTE_NUM_THREAD_X/Y/Z, and TUNNEL_ENABLE on every GFX10 dispatch. The
validator rejected PARTIAL_TG_EN, and DecodeComputeStageInfo read the
whole register, so a partial size would have widened the workgroup.
A direct dispatch with PARTIAL_TG_EN now runs (groups - 1) * full + last
threads on each axis through the dispatch-size limit USE_THREAD_DIMENSIONS
already uses, or plain groups when every last group is complete; a zero
or oversized last group throws. The workgroup size is NUM_THREAD_FULL.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DB_ALPHA_TO_MASK 0xaa01, the enable bit with the four offsets at 2 and no OFFSET_ROUND, reaches the pixel stage, which keeps a pixel when its MRT0 alpha is at least one half. Dithered or rounded settings, and an MRT0 that is missing, swizzled, packed or exported without a floating-point alpha, still throw. God of War Sons of Sparta (PPSA28997) stopped on it a few minutes into play.
InitialContextRegisters built the clear-state context registers on every
call: about 200 emplace calls from its list and loops, with the register
vectors growing several times on the way to offset 0x3b8. Each
QueueState starts from it, so it ran for every CLEAR_STATE, and
ResolveShaderAbi and ResolveGraphicsStagesAbi construct a QueueState on
each call, which sceAgcLinkShaders, sceAgcCreatePrimState and
sceAgcCreateInterpolantMapping make while a title draws.
The registers are now built once into a table that is never destroyed,
so a queue cleared while the process exits still finds it, and each call
returns a copy of the table.
A call takes 71 ns instead of 746 ns, and constructing a QueueState 885
ns instead of 1378 ns (200000 calls each, GCC 13 -O2). On the title
screen of PPSA21402 (Balatro) the two functions took 44 to 47 of about
2300 CPU samples per 10 s and no longer appear in the profile.
An unregistered graphics program is read raw only while the shader registry
is empty; with shaders registered it throws, as before. A draw that uses a
raw program is noted, and RegisterShader throws once that has happened,
since a title that registers shaders after drawing unregistered programs is
not understood. The draw cache revalidates a raw program by reading it raw.
AI-assisted (Claude Code).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The register draw key identifies an unregistered program only by its
address, and a key hit reuses the entry's decode without reading the
program again. A different program uploaded to the same address with
the same registers would run the old compiled shader. A hit now checks
the decode's unregistered programs against guest memory and drops the
entry when one changed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>