Storage images of a SINT format threw "storage image descriptor uses
an unsupported format"; only write-only 32_SINT was accepted.
The recompiler now writes 16_SINT, 8_8_SINT, 32_SINT, 16_16_SINT,
8_8_8_8_SINT, 32_32_SINT, 16_16_16_16_SINT and 32_32_32_32_SINT
storage images with the Uint numeric class. 8- and 16-bit components
are saturated (EmitFormatStoreComponent) and 32-bit ones stored
unchanged, as measured on RDNA2 (gfx1035); image_store_pck stores
32-bit components unchanged too. The driver binds these surfaces
through a cached view of the same-size UINT format.
8_SINT, 32_32_32_SINT, the packed SINT formats (11_11_10, 10_11_11,
2_10_10_10, 10_10_10_2), D16 stores, packed stores of 8- and 16-bit
components and atomics other than 32-bit ones on 32_SINT and 64-bit
ones on 32_32_SINT still throw. New test: agc_driver_image_store_sint,
with DST_SEL X,Y,0,1 on the two-channel formats, as with X,Y,Z,W the
hardware writes channel 1 from the W data slot (TechnicalDebt).
NVIDIA 610.62 cannot sample R8G8_SRGB, and lavapipe can sample neither
it nor R8_SRGB, so an 8_8 sRGB T# made an image the device does not
support: creating it crashes the NVIDIA driver and lavapipe loads zeros.
The device's format support is read once. An 8 or 8_8 sRGB texture the
device cannot sample is created and viewed as R8_UNORM or R8G8_UNORM,
and SpirvTarget::srgbDecodeFormats makes the materializer mark the
image, so image loads map every channel through a 256-entry table of
the sRGB EOTF rounded to nearest even at bf16 precision, which is what
RDNA2 (gfx1035) returns; both channels of 8_8 are sRGB, and DST_SEL 0
and 1 map to themselves. Samples and gathers of such an image throw,
and its bindless table entries are unmapped like converted formats.
APS5_SRGB_SHADER_DECODE=1 takes this path on every device. Shader disk
cache entries and keys carry the mark: cache format version 7.
DS opcodes 0x6e and 0x6f exchange two qwords at offset0 and offset1
scaled by 8 (st64: 512) and return both old values. Each address is
exchanged with its own 64-bit LDS exchange on the locked 64-bit path.
Equal offsets and a destination past v255 throw, as for the b32 forms;
64-bit GDS throws like the other 64-bit DS atomics.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comparison sampling of a color texture threw on the _o, _b and _cl
forms and on a sampler LOD bias. The emulation fetches the texels
itself, so the 6-bit offsets of the address are added to the point or
bilinear texel indices, as on the hardware and in Vulkan. The emulation
runs only at level zero or on a single-level view, where an LOD bias or
clamp cannot select another level, so they are accepted with no effect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LookBackScan runs a 43-instruction decoupled look-back written for the
test: one aggregate per block, the block index from an atomic counter,
{flag, value} published with buffer_atomic_swap_x2 and read back with
glc buffer_load_dwordx2. Seven single look-backs and 50 scans of 4096
blocks are checked against a CPU prefix sum; without the recompiler
change the first whole scan fails (3 of 3 runs).
TechnicalDebt records that only glc, aligned, in-bounds dwordx2 loads
in programs with the 64-bit storage view are one atomic load: loads
without glc, and loads in programs without 64-bit buffer atomics, can
still tear against a concurrent swap_x2.
A glc buffer_load_dwordx2 is one 64-bit access on the GPU, so it never
sees half of a buffer_atomic_swap_x2. It was emitted as two dword loads,
which can: the first can read the flag from before the swap and the
second the value from after it.
GTA V gives its BVH nodes their record indices with a single-pass
decoupled look-back prefix sum. Each workgroup publishes a {flag, value}
pair for its block with buffer_atomic_swap_x2 and reads the previous 64
blocks' pairs with glc dwordx2 loads. A torn read counts earlier blocks
twice, two nodes then share records, the BVH is corrupt and the ray
tracing refit hangs. It only happens in scans of more than 64 blocks (the
largest BVHs) and depends on timing.
In a program that has the u64 storage-buffer view (it uses 64-bit buffer
atomics), a coherent raw two-dword buffer load that is 8-byte aligned and
in bounds is now read with one device-scope OpAtomicLoad on that view and
split with OpUConvert and a shift (not OpBitcast to a uvec2, which
crashes NVIDIA's shader compiler). All other loads keep the per-dword
path.
tests/execution/LookBackScan runs the game's scan kernel: the look-back
from given blocks, and 50 whole 125-block scans checked against a CPU
prefix sum. Without this change the first whole scan fails.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
image_atomic_* with a two-dword data mask (four for cmpswap) is the
64-bit form of an integer image atomic. The decoder rejected it with
"invalid MIMG data mask". PPSA14632 (Unreal Engine) uses
image_atomic_umax this way in a compute shader during its first frames.
Swap, add, sub, signed and unsigned min/max, and, or, xor and cmpswap
now decode with 64-bit data and translate to new ImageAtomic*64 IR
opcodes. An image updated by them gets its own descriptor binding
range, declared as an R64ui image of 64-bit integers, and the module
uses Int64ImageEXT from SPV_EXT_shader_image_int64. The driver enables
shaderImageInt64Atomics when the device offers it and binds an
R64_UINT view of the 32_32 UINT, SINT or FLOAT surface. Without the
feature, recompiling such a shader throws.
64-bit inc, dec and the float atomics still throw, as does any other
access in the same shader to an image it updates with 64-bit atomics.
The new agc_driver_image_atomics64 test runs every operation on 32_32
UINT, SINT and FLOAT surfaces and checks the returned and stored values
against edge cases across the dword boundary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The resource tracker refused a program's 33rd distinct V# with "buffer
resource limit exceeded". The 32 was a recompiler constant, not a
Vulkan limit: every buffer is one constant-indexed element of the guest
buffers binding, which ShaderResources checks against the device's
storage-buffer limits.
Drop ShaderInfo::MaxBuffers and the tracker's refusal, and size the
SPIR-V emitter's per-buffer offset list from the binding layout. When
the user data and offsets do not fit in the push constants, they all go
to the shader data buffer as before. ShaderResources counted only
storage buffers against maxPerStageResources (200 on Intel UHD) and now
adds the stage's sampled and storage images.
agc_driver_many_buffers reads and writes 52 and 124 buffers, and
agc_driver_graphics rejects buffers plus an image above the limit.
An S# with FILTER_MODE (word0 bits 29-30) set threw in
DecodeSamplerResource. Modes 1 and 2 now map to Vulkan's MIN and MAX
sampler reduction through VK_EXT_sampler_filter_minmax, which the device
enables when it reports both filterMinmax properties; mode 3 throws.
Min/max still throws with anisotropy, a linear mip filter (lavapipe and
NVIDIA disagree there), depth comparison, color-texture compare
emulation, and, in PointFilteredSamplerWord (S# words 0 and 2), when it
filters between texels or mips of an image the recompiler point-samples.
A texture sampled through a linear min/max sampler needs
SAMPLED_IMAGE_FILTER_MINMAX on its view format; the image-sampler pairs
travel in DescriptorBinding::imageSamplers (shader disk cache format 5).
Adds the agc_driver_image_sample_reduction GPU test and extends the
sampler, emulated color compare and disk cache tests.
GFX10 format 6 (8_SINT) T#s threw "sampled image descriptor uses an
unsupported format": neither the recompiler's format table nor the
driver's texture format table had a row for it. Format 6 now reads as
a SINT image through an R8_SINT view, so image_load sign-extends each
texel (0x80 reads -128) and image-table entries of format 6 decode.
8_SINT storage still throws.
GFX10 image format 30 (10_11_11_UNORM: X and Y are unorm11 in bits
0-21, Z is unorm10 in bits 22-31, per Mesa's gfx10 tables) has no
Vulkan format, so its sampled and storage descriptors threw at decode.
Format 30 now takes the R32_UINT route of 10_11_11_UINT: the driver
binds the raw dwords as R32_UINT and the shader extracts each field and
converts it with the buffer format helpers (NormalizeFormatComponent on
loads, EmitFormatStoreComponent on stores); DST_SEL 1 reads 1.0f.
Samples, gathers, LOD queries, 16-bit data and loads or stores whose
DST_SEL names the missing W channel throw.
Converted textures are now viewed with an identity component mapping,
since the shader applies DST_SEL itself: a 10_11_11_UINT view whose X
selector was not X lost the packed dword. DCC clear codes 0001 and 1110
of a converted texture throw instead of filling one R32_UINT channel.
The masked case loaded EXEC from a scalar literal. A scalar mask is read
by subgroup lane, so on a host whose subgroups are narrower than the wave
(llvmpipe: 8 lanes) lane 16 took bit 0 and stayed active. v_cmpx_gt_u32
on the thread id gives the same low 16 lanes on any subgroup size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both swap data0 with the dword at addr + offset0 and data1 with the dword
at addr + offset1, and return the two previous values in vdst and vdst + 1.
The offsets are scaled by 4 bytes, or by 256 bytes for the st64 form.
Each location goes through the existing SharedAtomicSwap32.
An encoding whose two offsets are equal is rejected at decode time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
image_gather4_lz_o on a 1D view threw. The 1D gather emulation picks
the two texels around the coordinate itself, so the 6-bit x offset of
the address is added to the left texel index before both texel
centres are sampled, as the hardware offsets the footprint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 2D shader samples with computed texel offsets, which the recompiler
accepts only with VK_KHR_maintenance8; without it the whole test failed
before the 3D and 1D cases ran.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An S# with FORCE_UNNORMALIZED threw in DecodeSamplerResource. It is now
bound through a Vulkan sampler with unnormalizedCoordinates when every
use stays where the S# and the host select the same texels.
ShaderInfoCollector records the live uses of each sampler after Apply
and dead-code elimination, and the emitter shares its explicit-LOD
predicate. Populate flags a direct S# with the bit that only explicit-LOD
image_sample without offset, comparison, derivatives or _a uses, paired
only with direct 1D or 2D images without a format conversion; anything
else throws. The driver checks the S# filters, clamps, TRUNC_COORD and
MC_COORD_TRUNC, creates the sampler with nearest mips and a zero LOD
range, and requires a single-level, single-layer 1D or 2D view of mip 0.
The shader disk cache format becomes 5.
v_mov_b32_sdwa threw 'VOP1 SDWA destination selector ... is not
supported' when a byte source (src0_sel BYTE_0..3) met a partial
destination (dst_sel BYTE_0..3 or WORD_0/1): the VMovB32 rule allowed
only WORD and DWORD sources there. Every src0_sel, src0_sext, dst_sel
and dst_unused combination is now accepted. Translation already follows
RDNA2 for all of them (applyBitSourceModifiers, writeRawU32): the
selected field is zero- or sign-extended, and the result's low bits go
to the dst_sel field with the other bits zeroed, sign-extended above
and zeroed below, or preserved. Reserved selectors, dst_unused 3, and
neg/abs on sub-dword sources still throw.
agc_driver_sdwa_move_selectors runs all 294 combinations as wave32 and
wave64 on the GPU, plus the in-place byte broadcast under a partial
exec mask, which is not checked on subgroups narrower than 32 lanes,
as in the other EXEC-masked execution tests. agc_vop1_sdwa_move_decode
checks the decoded operands and the reserved encodings.
A loop whose exits have no common post-dominator, such as a normal exit
to one s_endpgm tail beside an early-out to a kill tail, got no merge
block, and Structurize threw "loop at block N has no structured
merge/continue". With three or more exits, detectNaturalLoops threw
first, on a lookup of InvalidControlFlowId.
Such a loop now leaves through one new merge block that dispatches on
route variables. They are cleared before the header and set on each
exit edge to a target other than the latch's exit. Inner loops are
routed first; a routing that would change the natural loops throws.
A loop-exit tail branch whose in-body target is the continue block no
longer merges there, which spirv-val rejects ("Header block ... is
contained in the loop construct ..., but its merge block ... is not").
The FLAT decoder took a scalar base address (SADDR) only from a
numbered SGPR pair, so global loads, stores and atomics and
global_load/store_dword_addtid with SADDR = vcc (106) threw "FLAT
scalar address register range overflow". LLVM's GFX10 global saddr
operand accepts s, vcc, ttmp, null and off.
A two-register base may now be VCC. The address translation already
reads it as VCC_LO:VCC_HI through offsetOperand. The same helper
serves the scratch SADDR, where vcc_lo becomes the 32-bit offset.
VCC_HI, M0, EXEC and the trap temporaries still throw.
SOP1 opcode 33 with SDST null keeps no return address, so it is exactly
s_setpc_b64; the decoder now reads it that way, as KytyPS5 does. A call
with a real destination still throws "unsupported SOP1 opcode 33".
GraphBuilder resolves two more jump shapes. A long branch is
s_getpc_b64 and s_add_u32/s_addc_u32 or s_sub_u32/s_subb_u32
immediates, also through vcc, all in one block. A dword jump table
branches to the table base minus an entry that s_load_dword reads at an
index bounded by s_min_u32 and scaled by 4; the resolver follows
register writes back through straight-line code, and anything else is
still refused. The code table load now also handles one-dword entries.
agc_shader_control_flow checks the decode, the graphs and the refusals;
the new agc_driver_scalar_swappc runs all shapes on the GPU.