Commit Graph

36 Commits

Author SHA1 Message Date
Gjon Gojcevic e8b24afecd feat(agc): fold samples of views whose channels all select constants
When every dst_sel of a sampled view selects constant 0 or 1, a sample
or gather returns that constant whatever the texel, filter, LOD or
coordinates. Such placeholder views can have a dimension the static
runtime image interface has no mode for (a 3D view under a 2D sample,
a 1D view under a 2D gather), and a 3D one paired with an unnormalized
sampler fails the unnormalized proof.

A direct sampled image whose accesses are all non-compare samples or
gathers gets three constant runtime modes (float, uint, sint). The mode
is selected when all four dst_sel are 0 or 1, unless the view is FMASK,
depth bits, converted or sRGB-decoded. In it the emitter returns each
channel's constant (the dmask channel's, in all four texels, for a
gather) from the image's swizzle specialization constants, and the
image is left unbound. ProveUnnormalized skips the mode after the
run-time-selected check, so table roots still throw.
2026-10-10 01:22:21 +02:00
Celegans12 56f8a62428 feat(agc): implement deferred faults for reads through null SRT pointers
Capturing a shader threw "null or misaligned address" when the flat
SRT walk read through a pointer loaded from memory that is null or
unmapped, even when the program skips that s_load. Such a read now
yields zero and a record with its pc and address, as does a slot whose
address needs it. The registered artifact checks one flag word per
guarded slot at each ReadConst; the capture appends the flags and the
records to the flattened SRT, and a set flag raises an Unmapped BDA
fault through the fault buffer and stops the invocation as a BDA read
does. Nothing per capture enters a key: poisoned and clean captures
share one variant and specialization. Descriptors, branch conditions,
uniform fills, table bases and user-data pointers that need such a
value still throw, and poisoned results stay out of the dispatch and
draw caches. The shader disk cache format becomes 21.
2026-10-09 00:20:59 +02:00
Adrià Franch d5a3a4321c fix(shader): follow measured hardware for MIMG BY2/BY4 loads
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-08 21:28:22 +02:00
Petre 88f7a5e0e4 feat(shader): lower full-width MIMG BY2 and BY4 transfers 2026-10-08 21:10:51 +02:00
boykopovar e4c604d64e fix(shader): update linux shader cache structure size assertions 2026-10-08 18:43:06 +03:00
boykopovar eb68ad3b02 fix(shader): preserve main buffer alignment and image formats in prepared shaders 2026-10-08 17:51:05 +03:00
boykopovar 2d93308780 fix(shader): preserve main behavior in cached specialization 2026-10-08 17:37:25 +03:00
boykopovar ca40b70b60 fix(shader): correct descriptor size check in disk cache decoder 2026-10-08 17:29:46 +03:00
boykopovar 05fa426112 perf(shader): eliminate prepared-path overhead in data, descriptors, dispatch, bda 2026-10-08 17:29:17 +03:00
boykopovar 7431e5a6e1 perf(shader): restore prepared vertex inputs + harden buffer specialization 2026-10-08 17:28:55 +03:00
boykopovar 7f56e04157 fix(shader): validate and serialize pipeline specialization constants (wip) 2026-10-08 17:28:35 +03:00
boykopovar dc62c432b4 perf(shader): eliminate redundant prepared invocation work 2026-10-08 17:27:26 +03:00
boykopovar b2a4813307 refactor(shader): remove resource specialization from artifact key 2026-10-08 17:26:38 +03:00
boykopovar 1174069239 feat(shader): select image and sampler resources at runtime 2026-10-08 17:26:34 +03:00
boykopovar 27439c43cc feat(shader): version runtime abi + freeze device capabilities 2026-10-08 17:26:01 +03:00
boykopovar b08c410b97 feat(shader): move buffer accesses to runtime descriptors via bda 2026-10-08 17:25:14 +03:00
boykopovar f26c8b6112 refactor(shader): separate compiled artifacts from invocation data 2026-10-08 17:21:17 +03:00
Gjon Gojcevic 71851d66a3 fix(recompiler): take point-filtered sampler copies' descriptors from their source sampler
When one guest sampler is paired both with an image that needs point
filtering (sint, depth-bits or converted format) and with an ordinary
image, ResourceMaterializer::Apply appends a point-filtered copy of the
sampler after the original samplers. The resource snapshot only holds
the original samplers, so DescriptorBindingBuilder::Populate threw
"guest sampler index is out of range" for the copy, both in the sampler
binding and in the unnormalized-coordinate check.

SamplerResource::copyOf records the sampler a copy was made from, and
the guest sampler descriptor of a copy is read from that sampler; the
point-filter rewrite of word 2 still applies to the copy. The field is
part of the disk cache encoding: cache format version 8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:55:18 +02:00
Celegans12 38e89df1a5 fix(agc): bind guest buffer views that start off a DWORD boundary
A V# whose base is not a multiple of 4 bytes from the start of its GPU
owner threw in GuestBufferMemory::Descriptor, because the shader added
the offset below the view only in whole dwords.

Descriptor now binds from the offset alignment below the view and
passes the whole remainder. Such a view's range covers the dwords
around it, and its descriptor region starts and ends on dword
boundaries, so a lone view copied to its own buffer binds the same way.

The buffer specialization records the V# base's low two bits. Raw
vector loads through such a V# join two dwords, glc dwordx2 loads
included, stores merge their bytes into both with compare-exchange
loops, and bounds checks include the second dword. s_buffer_load
ignores the low two bits, as SrtEvaluator does. Formatted accesses
whose element alignment (min(element bytes, 4)) the base breaks throw,
as they fault the wave on RDNA2, and so do atomics.
2026-10-08 13:01:27 +02:00
Celegans12 91852719bf feat(agc): implement 8 and 8_8 srgb image loads through unorm views
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.
2026-10-07 11:52:08 +02:00
alfalfa-star 9df592926b feat(recompiler): implement 64-bit image atomics
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>
2026-10-07 10:21:32 +02:00
Celegans12 1afcd20281 feat(agc): implement min and max sampler reduction modes
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.
2026-10-07 09:04:26 +02:00
Celegans12 76ddd9b6d4 feat(agc): implement unnormalized sampler coordinates on single-level 1D and 2D views
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.
2026-10-06 07:36:30 +02:00
Dean 021e4573ce
Merge pull request #668 from ThisIsAkill/fix/agc-emulated-color-compare-main
fix(agc): emulate comparison sampling of color textures in the shader
2026-10-06 13:27:39 +13:00
Celegans12 73c68c04bd feat(agc): implement image atomics on 32_SINT and 32_FLOAT surfaces
The resource materializer refused every atomic image descriptor whose
format was not 32_UINT. RDNA2 image atomics act on the raw dword and
the numeric format only affects addressing, so 32_SINT and 32_FLOAT
surfaces are now accepted with the Uint numeric class and run through
the existing integer atomics and float compare-exchange loops. Other
formats still throw, and the message now names the format.

DescriptorBinding gains imageAtomic, carried through the shader disk
cache (format version 4) and the resource content key. The driver
binds those elements through a cached R32_UINT view of R32_SINT and
R32_SFLOAT storage images.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 17:51:06 +02:00
Akhil Moola 5202199684 fix(agc): emulate comparison sampling of color textures in the shader
AMD hardware compares a sample_c reference against a color texture's red
channel; Vulkan defines comparison sampling only for depth formats, so the
driver refused any compare-sampled texture that is not R32 float or R16
unorm. PPSA21567 compares the same LESSEQUAL, clamp-to-border (opaque
white) sampler against a shadow map and against a 1x1 RGBA8 texture.

For a compare-sampled image whose format is not natively comparable, the
specialization records the paired sampler's compare function, filter,
addressing and border, and the view's reference clamp kind; the image then
binds as an ordinary color texture. The backend fetches the red channel,
applies wrap, clamp-to-edge or clamp-to-border addressing with the
sampler's texel-centre convention (border taps take the border's red),
compares, and blends four comparisons for bilinear filtering. Real depth
formats keep the native path.

Only sample_c / sample_c_lz on 2D and 2D array views, float/unorm/snorm
formats, wrap, clamp-to-edge or clamp-to-border, point or bilinear at one
level are implemented; half-border, the border color table and everything
else on this path throw. The reference clamp for unorm/snorm color formats
is not documented by AMD and is recorded in the technical debt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 06:55:03 -06:00
Chad e017894bb4 fix(agc): free per-thread driver state at thread exit without emutls
On MinGW, thread_local storage is emutls: libgcc frees a thread's blocks
from a winpthread key destructor when the thread function returns, and
the CRT runs the thread_local destructors later, from the module's TLS
callback at thread detach. The destructors of the driver's non-trivial
thread_locals (the deferred label queue, packet profile, capture and
revalidation scratch vectors, build profile, memory counters, disk cache
buffers) therefore ran on freed storage. Under the debug heap the flip
test failed on every run at the queue worker's exit after the injected
flip failure; without it the use-after-free showed up as intermittent
heap corruption or a stack overflow on the Windows CI job.

ShaderRecompiler::ThreadOwned keeps such state behind a trivially
destructible thread_local pointer and deletes it from its own pthread key
destructor, which receives the objects directly instead of reading
emutls. The driver no longer registers any thread_local destructor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 19:08:54 -05:00
Adrià Franch e2ac1a6690 feat(shader): implement image_load_pck and image_load_mip_pck with signed variants
Return the raw texel bits: dmask bit i selects texel dword i, dwords past the
texel read 0 and the _sgn forms sign-extend texels narrower than 32 bits
(measured on RDNA2 hardware). Only formats whose bits are exactly recoverable
from the sampled view are accepted (unsigned/signed integer, unorm up to 16
bits, 32-bit float components), on a direct sampled image with identity
swizzle; anything else throws.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-04 22:32:13 +02:00
Dean a75b2c9550
Merge pull request #316 from adriadam10/fix/wave32-subgroup-size
fix(agc): run wave32 compute programs on 32-wide subgroups
2026-10-04 20:09:16 +13:00
Adrià Franch ecda77d51d fix(agc): run wave32 compute programs on 32-wide subgroups
On devices whose default subgroup is 64 lanes (RADV, AMD on Windows) a
wave32 compute program was compiled for a 64-wide host subgroup, so the two
guest waves of a 64-thread workgroup shared one subgroup: ballots, EXEC,
v_mbcnt and lane reads of the second wave saw the first one's lanes.

With VK_EXT_subgroup_size_control, when compute can require 32 lanes, the
dispatch recompiles wave32 programs for a 32-wide host subgroup
(VulkanDevice::ComputeTarget) and the pipeline requires that size. The
result records the width it was compiled for (hostSubgroupSize, also in the
disk cache).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 06:28:08 +02:00
Michon f5ae0b8106 fix(shader): rect lists take explicitly interpolated fragment parameters and refuse only custom ones
Explicit v_interp_p1/p2 interpolation (fragment-shader barycentrics) reads every interpolated
parameter per vertex, and the rect-list shaders refused every per-vertex parameter. Their
tessellation stage emits each corner's parameter value, which is what a per-vertex read of the
generated triangles needs. Only custom parameters (custom interpolation or passthrough), whose
fourth vertex has no value of its own, stay refused; FragmentParameter carries that flag.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-01 22:13:36 +02:00
Alberto Ruiz cc3d2d21ef fix(shader): skip the push constant copy when there is nothing to copy
decodeResult and decodeAllocation called memcpy with the data() of
empty vectors, which may be null. Passing a null pointer to memcpy is
undefined even for a zero size, and UBSan reports it on every entry
without push constants, including the existing shader disk cache test.
The copy now runs only for a non-empty table, as Reader::Values does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5WJS4ZdQynAJesCZKH5hb
2026-10-01 13:28:36 +02:00
SamG-Coder dfdbeae901 fix(shader): preserve comparison image flags in disk cache 2026-10-01 20:36:28 +10:00
Michon bbf430e22d fix(agc): read a 16-bit integer view of a depth plane as its stored values
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:54:01 +02:00
boykopovar bb60336a1d fix(shader): preserve current shader metadata in disk cache keys and serialization 2026-09-30 12:01:57 +03:00
Dean Galvin cf721a2794 feat(agc): persistent shader disk cache and Vulkan pipeline cache
Every run recompiled every shader: the recompiler's variant cache and
the device's VkPipelineCache only lived in memory, and the first frames
of a title spent hundreds of milliseconds per large program in the
front end, the emitter and the optimizer.

Compiled variants (the SPIR-V plus the compiled shader info and binding
allocation that per-snapshot binding population needs) are now stored
on disk, one file per key, written atomically by a background thread,
and a later run loads them instead of compiling. The key holds every
input the in-memory variant cache matches on: the in-memory recompile
key (stage, code hash, stage registers, user data count, target), every
code word, the host subgroup size, the binding layout, the resource
specialization, and the values of the APS5_* switches the recompiler
sources read (found at build time; switches that only report are left
out). Entries live under a directory per source version, a build-time
hash of the recompiler and AGC driver sources and the build
configuration, and carry their key and checksums: an entry whose
format, version, key, size or checksum does not match, or whose payload
does not decode exactly, is ignored and recompiled. Directories no
build used for two weeks and stale temporary files are removed. Requests
under the debug probe bypass the disk cache.

The device's VkPipelineCache is loaded at creation when the file's
checksum and Vulkan header match the device (vendor, device and
pipelineCacheUUID; the driver version is in the file name), saved every
10 s while it grew, and saved at teardown.

Location: a shader_cache folder beside the running executable (not the
working directory), so the relinked game stays a self-contained folder;
ANYPS5_SHADER_CACHE_DIR overrides it. ANYPS5_NO_SHADER_CACHE=1 turns both caches off; the AGC
tests run with it set so they never write to the user's cache.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ButHENCbXXt8h4EZuWUabJ
2026-09-30 12:01:25 +03:00