Commit Graph

38 Commits

Author SHA1 Message Date
Scott Schuster 8c06af0ad9 fix(agc): honour primitive restart in mesh-path triangle strips
A geometry (NGG) strip draw on the mesh path assigned every primitive the next index positions without looking for the restart index: a triangle spanning a restart fetched vertex 0xFFFF and was drawn to wherever that read landed, and the strip parity never reset, so after an odd-length strip every triangle's winding flipped and face culling dropped it.

For an indexed strip draw with restart enabled and the restart index present, the draw now records each position's strip start (one past the last restart at or before it) and passes the table after the mesh arguments, flagged in draw word 3. The mesh prologue reads the start of its primitive's last vertex: a primitive whose start lies past its first vertex spans a restart and is made degenerate, and the others take their parity from their strip's start (or the draw's first index). The restart vertex itself fetches vertex 0. The table is built from the index bytes the draw already reads (its copy, or the guest memory an in-place import reads), next to #1968's restart-aware highest-index scan.

The new Mesh.cpp test draws quads as four-index strips each followed by a restart (16- and 32-bit indices, and an indirect draw starting mid-buffer) with front faces culled: every quad must render whole and the gaps between them stay clear. Without the table the first check fails ("a triangle crossed the restart after quad 0").

AI-assisted (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 07:57:16 -05:00
Celegans12 7144bb57ee feat(shader): implement compute LDS beyond the device's shared memory limit
Route oversized compute LDS through a per-workgroup storage buffer.
Retain native shared memory for programs that fit the device limit.
Carry the spill stride through prepared shader results and dispatches.
Preserve the stride in the full-result codec and its round-trip fixture.
Keep the Linux RecompileResult size guard aligned with the added field.
Exercise interleaved workgroups and the device-limit boundary.
This topic contains only the LDS change and its reviewer-requested repair.
The base includes upstream's separately merged NaN result correction.
2026-10-10 10:56:56 +02:00
Adrià 94ebb80c34
Merge pull request #2458 from gjonn/fix/recompiler-invariant-position
fix(shader): mark the vertex position output invariant
2026-10-10 08:17:50 +02:00
Adrià e0a311f8e5
Merge pull request #2315 from karanvora2599/perf/shader-min-max-native
perf(shader): pick f32 min, max and med3 results with fewer selects
2026-10-10 08:15:40 +02:00
Adrià 6324b00b5a
Merge pull request #1979 from Deftera186/fix/shader-sin-cos-exact-reduction
fix(shader): reduce v_sin_f32 and v_cos_f32 inputs exactly
2026-10-10 08:06:46 +02:00
Celegans12 c4b7a7453e feat(agc): implement 10_11_11 unorm color targets through 32-bit uint attachments
CB_COLOR_INFO format 6 (COLOR_10_11_11) with NUMBER_UNORM threw in
DecodeColorFormat: Vulkan has only the B10G11R11 float format. Such a
target now renders into an R32_UINT attachment in the layout of
format-30 images (unorm11 R and G in bits 0-21, unorm10 B in bits
22-31), so later image loads of the surface read the same words.

The target format is draw state, so the packing is a specialization of
the registered pixel shader: every float MRT export also computes the
packed word (EmitFormatStoreComponent) behind specialization constant
ExportPackingBase + target, and the specialized module turns a packed
target's output into a uint4 that ValidateShaders expects. The new
request field targetExportPacking is in the module key, the result memo,
the draw key and the serialized request (version 14). Blending, partial
RGB writes, UNORM16/SNORM16/UINT16 exports, DCC and ROUND_MODE throw.
2026-10-10 02:09:27 +02:00
Gjon Gojcevic 21f94288ac fix(shader): mark the vertex position output invariant
Two different vertex programs that compute the same position give
bit-identical depth on the GPU, which a depth pre-pass followed by an
equal-or-greater redraw relies on. Without Invariant, a Vulkan driver may
compile the same position arithmetic differently in each shader (for
example contracting into an FMA in only one), so the redraw fails the
depth test.

Decorate the gl_PerVertex Position member and the mesh shader's Position
output Invariant, and check both in a new recompiler test.

AI-assisted: yes
2026-10-09 17:53:07 -04:00
Karan Vora 9996baaf41 perf(shader): pick f32 min, max and med3 results with fewer selects
EmitClassifyF32Bits found a NaN from the exponent and mantissa fields
separately. The magnitude settles it: a NaN is above 0x7f800000 and a
zero is 0.

min and max ended with two NaN selects. An ordered compare is false when
either operand is a NaN, so the ordered pick already returns rhs for a
NaN lhs; only the select that returns lhs for a NaN rhs, also when both
are NaNs, is needed.

med3 is the minimum of the three when any operand is a NaN and the
median otherwise. The median is used only without NaNs, so it is now
built from compares and the zero-sign rule alone, and the minimum reuses
the operands' NaN tests instead of classifying intermediate values.
2026-10-09 11:18:19 -04:00
Liav Mordouch e6eb64aa58 fix(shader): reduce v_sin_f32 and v_cos_f32 inputs exactly
v_sin_f32 and v_cos_f32 take their input in cycles, and the SPIR-V
backend reduced it with GLSL Fract before scaling by 2*pi. For x in
(-0.5, 0), fract(x) = 1 + x is rounded to a multiple of 2^-24, which
drops the low bits of x, and for |x| <= 2^-25 it rounds to 1.0, which
RADV's v_fract_f32 clamps to 0x3f7fffff. On RADV, v_sin_f32(-2^-30)
returned 0xb4c90fde (-3.7e-7) where RDNA3 returns 0xb1c90fd5 (-5.9e-9).

The cycle is now x - roundEven(x). It lies in [-0.5, 0.5] and is exact
for every finite x: it is x when roundEven(x) is 0, and Sterbenz's
lemma applies otherwise. Finite inputs of magnitude 2^23 or more still
reduce to +0, so the select that forced them to 0 is removed. Infinite
and NaN inputs are unaffected: the translation selects their results
on the input bits.

Measured on RDNA3 (gfx1101) with the hardware oracle, with the same
results in all 16 combinations of IEEE, DX10_CLAMP and the f32 and f16
denormal modes: over 544 inputs, v_sin_f32(x - v_rndne_f32(x)) returns
the bits of v_sin_f32(x) for 519 and is at most 11 ulp off, while
v_sin_f32(v_fract_f32(x)) matches 403 and is more than 16 ulp off for
80, all of them in (-0.5, 0). For v_cos_f32 the counts are 523 (at
most 7 ulp off) and 484 (6 more than 16 ulp off). On RADV the
recompiled shader returns the bits of the v_fract_f32 sequence before
this change and of the v_rndne_f32 sequence after it, for all 544.

v_sin_f16 and v_cos_f16 share the reduction. On RADV, with f16
denormals kept, v_sin_f16 now returns the RDNA3 bits for all 65536
inputs, where it was 1 ulp off for 6 small negative ones, and
v_cos_f16 matches for all of them before and after.
2026-10-09 06:34:59 +03:00
Daan Dehing daa6bee7cf feat(shader): implement v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16
Decode the three VOP3 16-bit interpolation instructions (attribute, channel
and high-half select in the src0 field, the I/J VGPR in src1, P0 or the first
step's result in src2) and translate them with a new
GetInterpolationParameterF16 IR value. It reads the selected 16-bit half of
each vertex's parameter through VK_KHR_fragment_shader_barycentric, so P10 and
P20 are vertex differences rounded to f16; both steps are a multiply and an
add, as in the f32 forms.

An input in FP16_INTERP_MODE whose low half is defaulted is still declared at
its slot when its high half is read, and the high half takes its default from
USE_DEFAULT_ATTR1 and DEFAULT_VAL_ATTR1. Without barycentrics, on an input
without FP16_INTERP_MODE or on a pass-through input the instructions throw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 23:36:04 +02: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 2a117a6e5a fix(shader): apply fragment export mappings at runtime 2026-10-08 17:28:29 +03:00
boykopovar dccfa08de9 feat(shader): add fixed runtime abi + typed descriptor heaps 2026-10-08 17:26:04 +03:00
nisbenz f025a9e506 fix(shader): read s_memtime from the device clock where RADV's subgroup clock is 20 bits
On GFX10.3 to GFX11.7, ACO lowers a subgroup-scope OpReadClockKHR to
s_getreg_b32 of SHADER_CYCLES, 20 bits zero-extended to 64, so s_memtime
wrapped every 2^20 cycles and went backwards. Detect those families from
the RADV device name and read the device clock there instead.

Fixes #985

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 14:42:40 +02:00
Celegans12 e1d4e699be feat(agc): implement primitive-ordered pixel shaders with fragment shader interlock
DecodeState and the draw precheck rejected DB_SHADER_CONTROL bit 16,
PRIMITIVE_ORDERED_PIXEL_SHADER (POPS), which runs the ordered section
of overlapping pixels' shaders in primitive order. The bit now reaches
the recompiler as a pixel stage field, and such a pixel shader declares
PixelInterlockOrderedEXT and runs its whole body between
OpBeginInvocationInterlockEXT and OpEndInvocationInterlockEXT; an
invocation that a BDA read fault kills ends the interlock first. The
POPS collision SGPRs read 0, so a title skips its POPS wait and runs
its per-collision loop once. An image_load still reads a sampled
binding, which misses an earlier overlapping primitive's image_store in
the same draw. The device enables VK_EXT_fragment_shader_interlock when
it has fragmentShaderPixelInterlock; without it the recompile throws,
as does a nonzero POPS_OVERLAP_NUM_SAMPLES. The field is part of the
recompile cache key, the draw key and the serialized request (version 10).
2026-10-08 11:18:46 +02:00
Dean 71f248ad54
Merge pull request #568 from adriadam10/fix/f32-minmax-nan
fix(shader): return src0 when both f32 min/max sources are NaN
2026-10-05 18:09:38 +13:00
Chad d78eea1f5f feat(shader): implement the 64-bit LDS atomics
ds_add/sub/rsub/inc/dec_u64, ds_min/max_i64/u64, ds_and/or/xor/mskor_b64,
ds_cmpst_b64/f64, ds_min/max_f64, their _rtn forms and ds_wrxchg_rtn_b64
threw in the decoder.

LDS is a 32-bit workgroup array, so the two dwords are read and written
under a lock dword placed after the guest LDS, which is cleared with a
workgroup barrier at entry. The lock is acquired and released in the same
loop iteration, so lanes of one subgroup cannot deadlock on it. Stages
whose LDS is a function variable update it directly. 64-bit GDS atomics
throw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 18:14:24 -05:00
Adrià Franch 1f5d84a619 fix(shader): return src0 when both f32 min/max sources are NaN
Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-05 00:00:40 +02:00
Dean Galvin fb55cb2c0e perf(agc): draw mesh-stage indirect draws on the GPU
A mesh program took its draw's index count from push constants, so an
indirect mesh-path (NGG) draw needed the CPU to read its record first,
waiting for the GPU to finish everything recorded before. In PPSA21564
that was about 170 waits per 10 s at the title, menu and level (about
1 s), and one per frame in the intro (2.2 s per 10 s).

The two free mesh push dwords now carry the device address of an argument
record (a VkDrawMeshTasksIndirectCommandEXT followed by the index count
and first index). When the address is set, the prologue takes the index
count and the first index from the record (the new MeshArgument IR value,
a load through a physical storage buffer pointer) and offsets its index
reads by the first index; a direct draw leaves the address 0 and draws as
before. Mesh modules declare the physical storage buffer capability for
it, and the driver's SPIR-V validation accepts the extension in mesh
modules.

An indexed mesh draw with one record that patches no user SGPR now stays
on the GPU. A one-thread compute pass (Graphics/shaders/MeshArguments.comp)
turns the record, read in place from its host import, into the draw's
argument record: the groups and instances of vkCmdDrawMeshTasksIndirectEXT
and the index count and first index the mesh program reads, with the CPU
path's rules (count clamped to the index buffer, nothing for an empty
record or past the device's group limits). The index buffer descriptor
covers the whole buffer and the program offsets by the first index. When
the records cannot be read in place, the CPU reads them and fills the
same argument record (ResolveMeshArguments).
2026-10-04 15:45:56 +13:00
Dean dfac4ab1ec
Merge pull request #412 from arreina/pr/pixel-position-export
fix(shader): reject position exports without vertex input info
2026-10-04 14:02:31 +13:00
Alexey 1043f90d8a
Merge pull request #324 from adriadam10/feat/shader-memtime
feat(shader): implement s_memtime and s_memrealtime
2026-10-03 10:33:57 +03:00
Alexey 926906e257
Merge pull request #320 from adriadam10/feat/lds-atomics
feat(shader): implement the missing 32-bit LDS atomics and fix ds_min/max_f32
2026-10-03 10:13:33 +03:00
Alberto Ruiz 379f192978 fix(shader): reject position exports without vertex input info
EmitSetAttribute read state.inputInfo.vertex->clipSpace for every
position export. A pixel shader has no vertex input info, so a pixel
program that exports a position dereferenced a null pointer and crashed
the process; three words (0xf80008cf, 0, s_endpgm) are enough.
EmitSetAttribute now throws "vertex input info is missing for a position
export", as EmitAuxPositionExport already does for auxiliary positions.

Found by fuzzing the recompiler with mutated pixel shaders.
agc_pixel_position_export recompiles that program and expects the error;
it segfaults without the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R5WJS4ZdQynAJesCZKH5hb
2026-10-02 23:45:19 +02:00
Dean Galvin 7ed138d13f fix(recompiler): start pixel waves without their helper invocations in EXEC
A pixel wave starts with EXEC holding its covered pixels. The other lanes of
a partly covered quad run as helpers outside EXEC until the shader asks for
whole quads with s_wqm. Pixel shaders started with every invocation in EXEC,
Vulkan's helper invocations included. Those take part in subgroup
operations, so they counted in ballots (v_mbcnt, s_bcnt1 of EXEC,
v_readfirstlane), and a wave's single ds_append or atomic could be performed
by one of them: an atomic in a helper invocation does nothing and returns an
undefined value.

The entry prologue now starts a pixel shader's EXEC as !gl_HelperInvocation,
through a new StageInputKind::HelperInvocation builtin input. s_wqm still
adds the helpers of every quad with a covered pixel, so derivative code keeps
them.

The agc_driver_pixel_helper_lanes execution test draws triangles that leave
partly covered quads across the target with a pixel shader that stores each
covered pixel's v_mbcnt rank in EXEC, the s_bcnt1 count of EXEC and the
v_readfirstlane of a pixel id. Every wave's covered pixels must be lanes
0..n-1 of an n-lane EXEC whose first lane is one of them, in wave64 and
wave32. It skips on a subgroup narrower than 32, such as lavapipe's.
2026-10-02 21:08:32 +13:00
Adrià Franch 7d90b1dcec feat(shader): implement s_memtime and s_memrealtime
Both SMEM opcodes read a 64-bit counter into an SGPR pair. They now
lower to OpReadClockKHR: s_memtime with subgroup scope (the shader
clock) and s_memrealtime with device scope (the constant real-time
clock). The device enables VK_KHR_shader_clock when it has both clocks
and the backend refuses the opcodes with a clear reason otherwise.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 07:58:41 +02:00
Adrià Franch c8af88921a feat(shader): implement the missing 32-bit LDS atomics and fix ds_min/max_f32
ds_rsub(_rtn)_u32, ds_inc_u32, ds_dec_u32, ds_mskor(_rtn)_b32,
ds_cmpst(_rtn)_b32/f32, ds_add(_rtn)_f32, ds_min/max_rtn_f32,
ds_wrap_rtn_b32 and ds_nop threw in the decoder. The new ones run as a
compare-exchange loop over the old value, with the semantics measured on an
RDNA2 GPU.

ds_min_f32 and ds_max_f32 take one data operand on gfx10, but compared the
memory with a second register (the unused data1 field, usually v0) and
stored data0 when that compare held. They now keep the minimum/maximum of
memory and data like the hardware: a NaN loses to a number, -0 < +0. Both
IR ops return the old value, so the _rtn forms share them.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-02 07:11:48 +02:00
Dean Galvin 6a33a3d371 fix(recompiler): declare pixel shader inputs at their parameter slots
SPI_PS_INPUT_CNTL_n.OFFSET (bits 0-5) names the parameter slot pixel
input n reads, so several inputs may read one slot and an input may read
a slot the vertex stage never exports. With OFFSET bit 5 set and
FLAT_SHADE clear the input is the DEFAULT_VAL constant instead (bits 8-9:
0000, 0001, 1110, 1111). With OFFSET bit 5 and FLAT_SHADE both set the
slot in the low bits is passed through with its three vertices unchanged:
v_interp_mov P0, P10 and P20 read vertices 0, 1 and 2 rather than P0,
P1-P0 and P2-P0. The PS5 compiler writes such inputs as 0x422 and 0x423.

The old mapping masked OFFSET to five bits, so a defaulted input landed
on a real slot, and gave the second of two inputs reading one slot either
the same Location (two variables, "duplicate shader interface location")
or, when one was flat, a free Location the vertex stage never wrote
("graphics interfaces disagree at location N").

Now one input variable is declared per slot actually read, at Location =
slot, and inputs reading that slot share it. A slot read both flat and
interpolated (or through the perspective and linear pairs) is read per
vertex: flat readers take the provoking vertex, the others interpolate
with the barycentrics of their own pair. Pass-through inputs are read per
vertex at their slot without subtracting vertex 0, and reading one with
v_interp_p1/p2 is refused. Defaulted inputs read their DEFAULT_VAL
constant and declare nothing; inputs the program never reads are no
longer declared.
2026-10-01 08:38:47 +13:00
Dean Galvin 33f9109999 feat(shader): translate NGG geometry programs to mesh shaders
The geometry path's merged ES/GS programs (VGT_SHADER_STAGES_EN.GS_EN)
were compiled as mesh-stage programs whose input metadata was never
filled in, so every such draw failed with "mesh shader translation
configuration is not supported".

Recompiler:
- BuildShaderStageInputInfo takes the draw's MeshConfiguration; the mesh
  input helpers (primitive size, step and counts) are implemented.
- The prologue emulates one NGG subgroup per workgroup: s2 GS_TG_INFO,
  s3 the merged wave info per wave, the GS vertex offsets (ES thread
  index times VGT_ESGS_RING_ITEMSIZE, odd strip triangles swapped), the
  primitive id, and the ES vertex and instance ids. An indexed draw's
  vertex id is read from the index buffer through a raw V# the driver
  puts in hidden user words 4..7.
- s_sendmsg GS_ALLOC_REQ becomes MeshAllocate. The epilogue zeroes the
  allocation first, clamps it to the output limits, culls primitives no
  thread exported and decodes the 9-bit vertex indices.
- The draw parameters live in the last six push dwords. A BDA fault in
  a mesh program no longer stops the invocation (as for other programs
  with barriers, the fault is recorded and the read returns zero), so
  every invocation reaches the subgroup barriers and the epilogue.
- The cache keys cover the mesh configuration; requests serialize its
  new esgsItemSize field (version 4).

Driver:
- DecodeShaderStages reads VGT_ESGS_RING_ITEMSIZE (now in the draw key)
  and sizes the subgroup for its ES vertices, GS primitives and outputs.
- Stages compile below the mesh draw parameters, which every mesh draw
  pushes; the mesh stage always takes part in the push range.
- The index-buffer V# is set per draw (per record on the CPU-resolved
  indirect path); the index-buffer alias check spares read-only
  elements.
- The workgroup and LocalSize checks count two guest lanes per
  invocation on 32-wide subgroups; mesh draws may carry a first vertex
  or instance.

agc_driver_mesh_tests (EXCLUDE_FROM_ALL, needs VK_EXT_mesh_shader)
recompiles a hand-assembled merged ES/GS program in the PS5 compiler's
layout and draws indexed and non-indexed triangle lists in one- and
two-wave subgroups and a triangle strip across odd-starting subgroups,
reading the pixels back. agc_shader_replay prints a request's draw and
mesh configuration.
2026-09-30 23:57:47 +13:00
STL1te 6b36c050fe style: drop the branch's code comments per CONVENTIONS.md
Only the lines this branch added lose their comments; the commits before
keep them, with the reasoning behind each change, for whoever digs into
that code later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:25:29 +03:00
STL1te 000d0bf4f0 feat(shader): retire threads past a partial thread-dimension dispatch
A dispatch sized in threads (USE_THREAD_DIMENSIONS) launches a partial last
workgroup on RDNA2, but the host rounds it up to whole groups, so the extra
threads ran: past the end of a node list they read past it and wrote over
live records.

Such dispatches now compile a separate variant (ShaderComputeStageInfo::
partialThreads, keyed only on whether it is set) whose entry EXEC excludes
threads at or past the requested size. The size reaches the shader as three
shader data dwords after the memory offsets (StageInputKind::
DispatchThreadLimit), is mixed into the dispatch cache key and the result
memo hash, and serialized in recompile requests (version 3). Whole-group
dispatches keep their variant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:22:19 +03:00
STL1te 8e6614500a feat(shader): MIMG UNORM, _a sample aliases, LOD clamp, VOP3 carry-in
- MIMG word0 bit 12 (UNORM) is accepted on every op without a sampler
  (compute image stores set it, as the ISA asks); sample and gather still
  reject it, and rejections name the instruction words.
- The image_sample_*_a opcodes keep the plain address layout, as in Kyty.
- The _cl LOD clamp becomes the MinLod image operand (or clamps an
  explicit level) on devices with shaderResourceMinLod; a gather with a
  clamp fails loudly.
- v_add_co_ci_u32, v_sub_co_ci_u32 and v_subrev_co_ci_u32 in VOP3 form
  take their carry-in from the SGPR pair of their third source, not VCC:
  a glyph upload's reciprocal division corrected its quotient on the
  wrong lanes and UI text came out stippled.
- Pixel exports follow each MRT slot's component mapping and write only
  the components of their export format (32_R, 32_GR, 32_AR).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 21:22:12 +03:00
boykopovar f38c6cd7af feat(shader): implement checked BDA reads and GPU fault reporting 2026-09-21 19:26:09 +03:00
boykopovar 48d08397d9 fix(shader): fix build break from bda cache page bits field 2026-09-21 18:08:03 +03:00
boykopovar cf507a3b89 feat(shader): implement spirv tessellation emission 2026-09-21 06:49:35 +03:00
boykopovar c4ace2027e feat(shader): add spirv emit funcs impl from spirv module emitter (wip) 2026-09-20 21:04:28 +03:00
boykopovar 342fd6f052 fix(shader): remove already impl funcs from spirv module emitter 2026-09-20 19:59:28 +03:00
boykopovar d084eed394 feat(shader): rdna memory op decoder impl 2026-09-15 07:07:50 +03:00
Renamed from core/shader/recompilier/SpirvBackend/src/SpirvModuleEmitter.cpp (Browse further)