Commit Graph

1122 Commits

Author SHA1 Message Date
Adrià ff8fe4dbc1
Merge pull request #3988 from karanvora2599/perf/shader-f16-unpack-half
perf(shader): convert f16 operands with UnpackHalf2x16 where f16 denormals are kept
2026-10-13 13:48:59 +02:00
Dean 768cd93977
Merge pull request #4082 from Celegans12/feat/agc-quad-tessellation
feat(agc): implement quad tessellation with counter-clockwise output and level clamps
2026-10-13 23:42:25 +13:00
Dean 387b8a0faa
Merge pull request #3763 from eljackjoestar-cmd/codex/dynamic-rendering-layers
fix(agc): render all attachment layers with dynamic rendering
2026-10-13 23:32:53 +13:00
Karan Vora e4f212b2e2 perf(shader): convert f16 operands with UnpackHalf2x16 where f16 denormals are kept
Since #1463, every f16 operand read decodes sign, exponent and mantissa
with integer operations, 8 to 12 instructions more per read than
UnpackHalf2x16 on NVIDIA. The decode works around an AMD Windows driver
that folds the mask of a byte-selected half into UnpackHalf2x16.

A new target flag, float16DenormPreserve, is set when the device reports
shaderDenormPreserveFloat16 and denormBehaviorIndependence ALL, the same
rule as float64DenormPreserve. With it, EmitF16BitsToF32 uses
UnpackHalf2x16 and declares DenormPreserve for 16-bit floats, under
which SPV_KHR_float_controls keeps unpacked subnormal halves. NVIDIA
returns the default NaN for every NaN half, so the NaN bits are rebuilt
from the half: its sign at bit 31, its payload at bits 22:13. The
DenormPreserve 16 mode is declared once, shared with the native f32 to
f16 conversion. Without the flag (lavapipe; RADV reports 32_BIT_ONLY)
the integer decode stays. Halves of fields narrower than 16 bits, such
as SDWA byte selects, keep the integer decode on every device.

The flag is serialized (request version 17) and part of the cache key.

New test agc_driver_f16_to_f32_unpack converts all 65,536 halves, with
nonzero high bits, through v_cvt_f32_f16, and byte-selected halves
through v_add_f16 SDWA, with each decode the device supports, and
compares the bits with an exact decode. agc_driver_f16_to_f32_unpack_spirv
checks which decode the SPIR-V uses with and without the flag.
2026-10-13 12:15:00 +02:00
Adrià 8bcd97cd5b
Merge pull request #1418 from gjonn/feat/agc-unnormalized-implicit-lod
feat(agc): sample the base level through unnormalized samplers
2026-10-13 11:49:07 +02:00
eljackjoestar-cmd 2ae227ef85 fix(agc): integrate merged position exports and request version 21 2026-10-13 13:03:12 +04:00
alfalfa-star 1055617751 feat(agc): render layered draws into 3D color targets
A color view of several depth slices of a 3D target (CB_COLOR_VIEW
SLICE_START < SLICE_MAX) was rejected as "color views of several array
slices are unsupported". Titles render such volumes with one draw: an
instanced draw whose vertex stage picks each primitive's slice through
the misc position export (PA_CL_VS_OUT_CNTL USE_VTX_RENDER_TARGET_INDX
with VS_OUT_MISC_VEC_ENA).

Depends on #423, which carries PA_CL_VS_OUT_CNTL to the recompiler.

- DecodeColorBuffer takes the slice range of a 3D target
  (ColorTarget::layers, clamped to the surface's last slice), allows DCC
  on 3D targets, and sizes a tiled 3D target by its whole surface
  (SurfaceForTarget moves to State.cpp for that). DecodeState sets
  State::renderLayers and, for a layered draw, USE_VTX_RENDER_TARGET_INDX
  (State::layerExports); color targets of different slice counts and
  layered draws with a depth target throw.
- The driver adds State::layerExports to the PA_CL_VS_OUT_CNTL bits #423
  passes (it already passes VS_OUT_MISC_VEC_ENA), so the render target
  index reaches gl_Layer only for layered draws and stays ignored
  elsewhere as before.
- A vertex stage writing gl_Layer declares ShaderViewportIndexLayerEXT
  and SPV_EXT_shader_viewport_index_layer when the device offers them
  (the device enables VK_EXT_shader_viewport_index_layer when present),
  instead of SPIR-V 1.5 ShaderLayer, which a Vulkan 1.1 device cannot
  take. Graphics validation accepts the capability, the extension and a
  vertex Layer output when it is enabled, and a per-primitive Layer
  output of a mesh stage.
- The resident target's attachment view is a 2D array view of the
  slices, and the framebuffer has that many layers (Framebuffer and
  AcquireFramebuffer take the layer count).

The execution test agc_driver_layered_volume_draw draws three bands,
one per layer, into slices 1..3 of a 4-slice 64 KiB-tiled volume and
reads every slice back: slice 0 stays untouched and slice 1 + L holds
only band L. It also recompiles a mesh program with the layer export and
validates it against the layered state. The mesh case does not draw:
lavapipe's LLVM backend aborts compiling that mesh pipeline ("Cannot
emit physreg copy instruction"), which a test cannot catch. On RADV the
same mesh draw renders the bands into the right slices.

PPSA14632 (LEGO Horizon Adventures) renders its 64x64x64 RGBA16F
translucency lighting volumes (two DCC targets, 64 instances) this way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:00:04 +04:00
Celegans12 23342340cc feat(agc): implement quad tessellation with counter-clockwise output and level clamps
DecodeShaderStages accepted only clockwise triangle patches and no HOS
level clamp narrower than [1, 63], so terrain drawn as TRI_CCW quad
patches with VGT_HOS_MAX_TESS_LEVEL 9 aborted. Quad domains and TRI_CCW
now decode and emit Quads and VertexOrderCcw; isolines, other
partitionings and point or line output still throw.

VGT_HOS_MIN/MAX_TESS_LEVEL are read per draw and pushed in the last 8
bytes of the pre-rasterization push constant slot, where the hull reads
them to clamp its factors; they stay out of every key and the recompile
request, so one hull serves every level. Zero, negative and NaN outer
factors still cull; clamps outside 0 <= min <= 63, max >= 1,
min <= max throw. Quad hulls write four outer and two inner factors at
a 24-byte stride, the layout Mesa's TF ring store uses.
2026-10-13 10:56:16 +02:00
Dean 4bb2c7f394
Merge pull request #423 from MichonGoddijn231849/feat/clip-cull-distances
feat(agc): vertex clip and cull distances
2026-10-13 21:47:43 +13:00
Dean Galvin 8c04efcfbc fix(shader): load a BDA span in place from its own function
read_bda_span chooses between the span's in-place dword loads and its
per-dword byte fallback, which calls read_bda_dword_bytes. Each shape of
that choice so far hangs a group of NVIDIA drivers: a dispatch never
finishes and the device is lost.

As the two arms of one selection, with the loads emitted in the arm,
it hung drivers 591.86 and 590.48.01 when lanes took both arms (#3291).
As two selections, the loads under the span's condition and the fallback
under its negation (#4759), it hangs 595.97, 596.36, 610.62, 610.74 and
610.57.04 (#4975).

The loads now live in load_bda_span, a DontInline function that calls
nothing, and read_bda_span is one selection again: one arm calls
load_bda_span, the other is the fallback as before. Probe, loads,
lookups, faults and stops are unchanged for every lane.

On an RTX 4090 (Linux, driver 610.57.04) the two-selection shape hangs
agc_driver_scalar_wide_loads and agc_driver_flat_apertures_0..13 in a
build with SPIRV-Tools, and agc_driver_bda_span_reads and
agc_driver_pixel_interlock in a build without. All pass with this
change in both builds. Drivers 591.86, 590.48.01, 595.97, 596.36,
610.62 and 610.74 are not tested; a shape with the loads in their own
DontInline function was reported passing on 591.86 in #3291.

agc_shader_memory now requires that a span read function holds no load
and calls its load function once, and that the load function holds the
four loads and no call.

Refs #4975
2026-10-13 21:40:18 +13:00
Michon 04138f50ad feat(agc): vertex clip and cull distances
PA_CL_VS_OUT_CNTL's clip and cull enables and its misc and distance vector bits reach the recompiler's vertex info (request version 20), the draw key, the vertex-info comparison and the recompile cache key. Registration and draw-time preparation set them the same way the draw does; a program without pos1..pos3 exports gets none, so its artifact does not depend on the register. Guest clip distances share main's gl_ClipDistance array with the all-zero position distance; cull distances need shaderCullDistance. Up to eight clip and cull distances are accepted.

Co-Authored-By: john.maia <hi@johnmaia.dev>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 21:37:49 +13:00
Dean d71423a575
Merge pull request #4911 from oneandonlydean/up4/atomic-buffer-fault-only
perf(shader): record a specialized buffer atomic's fault without the address tables
2026-10-13 19:52:01 +13:00
Dean ea2bbefb2b
Merge pull request #4402 from jakezwang/fix/spirv-redundancy-memory
fix(shader): release bundled optimizer traversal maps
2026-10-13 18:49:45 +13:00
Dean Galvin dbe16fb28c perf(shader): record a specialized buffer atomic's fault without the address tables
A program with an atomic on a V# it specializes was compiled as
address-based (usesDma) so that the atomic's unaligned-address check could
call the fault function. The atomic itself goes through the element's
storage buffer and nothing in such a program reads the address tables. In
the driver an address-based build leases all registered guest memory and
binds every V# of the program in place inside a host import, so none of
its elements is staged in device memory, the atomic one included.

The atomic now asks for the fault buffer alone (usesFaultBuffer) and
records the fault through RecordBdaFaultWords, which has both forms. The
fault record, the stop of the invocation and the atomic are unchanged. The
64-bit fault address needs shaderInt64, as the scalar buffer load does. A
tessellation-control program with such an atomic is refused as before.

Test: agc_shader_memory compiles a buffer_atomic_inc on a specialized V#
and requires no address table binding, one fault buffer, one call of the
fault function, logical addressing and the atomic element flag; without
shaderInt64 the program is refused.
2026-10-13 18:29:59 +13:00
Dean f9821b76ed
Merge pull request #4097 from kr4y4/fix/shader-f16-ternary-output-modifier
fix(shader): keep the output modifier of the f16 min3, max3, med3 and div_fixup ops
2026-10-13 17:52:50 +13:00
Dean 1268ba8cd4
Merge pull request #4727 from gjonn/feat/hull-dynamic-control-point-index
feat(shader): read hull inputs at a control-point index computed from data
2026-10-13 17:50:35 +13:00
Dean 432675d919
Merge pull request #4252 from karanvora2599/perf/shader-wave32-exec-lane-bits
perf(shader): keep wave32 EXEC and VCC lane bits without a ballot round trip
2026-10-13 17:47:39 +13:00
Dean 02f5c0bcb0
Merge pull request #4195 from rugbedbugg/fix/shader-narrowing-fold
fix(shader): preserve integer narrowing in constant folding
2026-10-13 17:47:18 +13:00
Dean e51428bf23
Merge pull request #4759 from fdfawef324/fix/bda-span-read-split
fix(shader): split a BDA span read's in-place loads from its fallback
2026-10-13 17:00:47 +13:00
Dean 675c52ca98
Merge pull request #4580 from coltken95/fix/shader-same-target-branch
fix(shader): never branch conditionally to one label twice
2026-10-13 16:52:48 +13:00
Dean a675333d5a
Merge pull request #4600 from coltken95/fix/shader-folded-continue
fix(shader): keep the back-edge of a loop whose continue block is dead
2026-10-13 16:52:24 +13:00
Dean 3a0642e131
Merge pull request #4019 from ahir061/fix/macos-shader-cache-directory
fix(shader): find the shader cache directory on macOS
2026-10-13 16:52:01 +13:00
Gjon Gojcevic 63ed0ffeaf feat(agc): sample the base level through unnormalized samplers
The hardware ignores the LOD for unnormalized coordinates and samples the
base level. A sampler used only by explicit-LOD, implicit-LOD, bias and
gradient samples gets a pipeline specialization constant set from its
unnormalized bit when the binding plan is prepared. When the constant is
set, each of its samples is emitted as OpImageSampleExplicitLod with LOD 0,
which is what Vulkan requires of unnormalized samplers. Otherwise the
sample keeps its own form. Such shaders are no longer refused.
2026-10-12 22:58:16 -04:00
Dean c3625c4789
Merge pull request #4120 from tibzejoker/fix/vector-window-direct-ssa
fix(shader): keep direct VGPR accesses in SSA when the window has only relative reads
2026-10-13 14:47:39 +13:00
thibaut 2ecbbe40a3 fix(shader): keep direct VGPR accesses in SSA when the window has only relative reads
A program that uses relative VGPR addressing placed every register from the
lowest relatively addressed one upward in the window array, so even direct
accesses became array loads and stores. A descriptor spilled with v_writelane
and restored with v_readlane then lost its definition, and resource analysis
refused GetSamplerResource with ReadLane(LoadVectorWindow(Immediate)) (GTA V,
PPSA04264, shader 0x25a889400).

When the program has no indexed writes (v_movreld, v_movrelsd, v_swaprel),
direct accesses stay SSA registers and each direct write is mirrored into the
window for the relative reads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1W4jdNSKTV7xwuS6xQPJV
2026-10-13 13:59:35 +13:00
LeandroLP23 3334e41e45 fix(shader): read the ancillary render target index as 0
No pre-rasterization stage writes Layer: vertex layer exports are
ignored, so the first layer is used and the render target index field
of the packed ancillary word is 0. The lowering read the Layer builtin
for it instead. AMD's Windows driver drops the color writes of a
fragment shader that consumes an unwritten Layer, which made
agc_driver_relative_ancillary fail on AMD hardware (#4800); lavapipe
returns 0 there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:23:56 +13:00
Dean Galvin c52321fab5 fix(shader): move the primitive class to request version 19 and compose the ancillary word with render target index 0
main reads target.vertexSubgroups at request version 18 since #4470, so
reading the pixel primitive class at 18 as well read a version 18
request written by main one byte short. The class is now written and
read at version 19. agc_shader_memory loads a version 18 request as
main writes it, and it fails with "truncated data" when the class is
read at 18.

The composed word no longer reads the Layer builtin for bits 16-28.
Draws render to a single layer and guest layer exports are ignored, so
no stage writes Layer, and AMD's Windows driver drops the writes of a
fragment shader that reads it unwritten (#4800). The field is the
constant 0 and pixel_ancillary checks that the composed program has no
Layer read. The debt entry names the bits that have no source and are
assumed zero (2-7, 12-15, 29-31), and says that a change which makes
draws export a layer must restore the read.
2026-10-13 13:18:51 +13:00
AlxcBTW 18d9874a06 feat(shader): give reads the masked-select pass keeps the whole pixel ancillary word
A packed ancillary word that still reaches an export, or any other read
the masked-select pass cannot drop, no longer stops the recompile. After
that pass every remaining use gets the composed word: the primitive class
at bits 0-1, SampleId at bits 8-11 and Layer at bits 16-28, with every
other bit zero. Those lanes do export the ancillary VGPR on the console,
so the word has to be right, not just present.

The primitive class (0 point, 1 line, 2 triangle, 3 rectangle) comes from
the draw. RasterPrimitiveOf maps rect lists to 3, line lists on the vertex
path to 1 and everything else the driver accepts to 2, because its
geometry and tessellation paths only output triangles. It reaches the
shader as pipeline specialization constant 8233, the way dual-source
blending does: a request field that is folded into the specialized
module and is part of the result memo, the draw key and the serialized
request (version 17).

Only shaders that read the class pay for it. ShaderInfo records the read
(disk cache format 28), and only those shaders put the class in their
module key, so a shader that reads just the sample or layer field keeps
one module for every primitive class. The draw sets the class only when
the pixel shader loads the ancillary VGPR, so other draws keep the draw
cache entries they have today.

The VRS rates (bits 2-5) and the eye id (bit 7) are 0 because the driver
supports neither VRS nor multiview. SampleId is 0 because the driver
rejects MSAA and per-sample shading, and Layer is 0 while it ignores
vertex layer exports. TechnicalDebt.md records the layout sources and
these assumptions.

Tests:
- pixel_ancillary builds an export to target 0 whose two channels select
  between a packed value and a phi carrying the word. It checks the word
  for each primitive class, sample and layer, checks that the three
  shapes the masked-select pass keeps now compile with the composed word,
  and that a program whose select the pass drops reads no class.
- agc_driver_pixel_ancillary_word exports the word from a draw and reads
  back 2, 3 and 1 for triangle, rectangle and line. A shader that reads
  only the sample field shares one module across the classes, and the
  whole-word shader gets one per class. It also checks RasterPrimitiveOf.
- agc_shader_memory covers the version 17 round trip, and
  agc_shader_disk_cache the new flag.

Both ancillary tests fail with the old refusal when the composition is
disabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:06:21 +13:00
Dean e6ecece94e
Merge pull request #4267 from EagleBearerYS/fix/shader-color-compare-8bit-unorm-zero
fix(shader): return 0 for 8-bit UNORM color comparison
2026-10-13 12:38:49 +13:00
Dean 7ac3f460e5
Merge pull request #3952 from xXPBSXx/fix/recompiler-resource-control-flow
fix(recompiler): leave out descriptor sources behind a branch the capture decides
2026-10-13 10:00:59 +13:00
fdfawef324 f1b2e605e7 fix(shader): split a BDA span read's in-place loads from its fallback
On an RTX 3060 (Windows, driver 591.86) a dispatch never finishes when
the lanes of read_bda_span split between its in-place dword loads and
its per-dword byte fallback, which calls read_bda_dword_bytes: Windows
resets the GPU and the device is lost. The two were the arms of one
selection in the DontInline function. They are now two selections, the
loads under the span's condition and the fallback under its negation,
joined after both. Loads, lookups, faults and stops are unchanged for
every lane; only the SPIR-V control-flow shape changes (SPIR-V
specification 2.11, structured control flow). Every stage except mesh
shaders is emitted as SPIR-V 1.3, and OpSelect chooses between
composites with a scalar condition only from 1.4 on, so the join stays
a selection.

The same hang and fix were reported in #3291 on an RTX 4060 Laptop GPU
(Windows, driver 591.86) and an RTX 5060 (Linux, driver 590.48.01). An
RTX 4070 Laptop GPU on driver 616.92 does not hang with either shape.
The driver-side cause is unknown, so the shape is recorded in
docs/dev/debt/shader.md.

On main the hang fails nine tests on the RTX 3060, in two ways.
agc_driver_{flat,global}_loads{,_x2_x3,_x4} and
agc_driver_flat_apertures_{2,5} abort after 64 to 71 s: the lost batch
keeps its lease, so teardown waits 60 s for the pinned guest allocation.
agc_driver_pixel_interlock fails after about 4 to 7 s with
vkDeviceWaitIdle returning VK_ERROR_DEVICE_LOST (-4) and never reaches
the pin wait. All nine pass with the change.

Fixes #3291

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:37:43 -04:00
Gjon Gojcevic 0b7de0e737 feat(shader): read hull inputs at a control-point index computed from data
A hull program may read its input control points from LDS at an index it
computes from data, such as an index loaded from memory or min(invocation, n).
The tessellation lowering refused such a read because its address is not
affine in the invocation and relative patch.

The address evaluator now carries a dynamic term for a hull LDS read: a value
that is not affine but does not depend on the relative patch index, with the
multiple it is scaled by. Terms of the same value add up ((t << 1) + t of
t = index << 3 is 24 * index); terms of different values keep the greatest
common divisor of their multiples. The read is lowered from the program's own
address arithmetic, and the relative index may reach it only with the stride
of a patch's inputs. A computed offset that is not a whole number of control
points is refused.

The SPIR-V emitter clamps a hull input control-point index to the patch's last
input, recorded as technical debt: on the console an index past the patch
reads LDS of a neighbouring patch.
2026-10-12 15:47:59 -04:00
Dean 2f4cc8983b
Merge pull request #4431 from AlxcBTW/refactor/spirv-specializer-debris
refactor(shader): drop unused folded constants and unreachable blocks after specialization
2026-10-13 08:30:39 +13:00
xXPBSXx 7d508cf779 fix(recompiler): evaluate pruning conditions without throwing on null roots
A branch condition that reads through a null user-data pointer is now
treated as not evaluable instead of being caught, so every successor
stays active.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 21:14:18 +02:00
xXPBSXx 37003b7a38 fix(recompiler): track which branch uses each image, not only each descriptor source
Several image instructions of different dimensions on the same T# address share
one descriptor source, so a source stays active as soon as one of them is taken
and the others are still checked against the T#. Record the images a block uses
as well, deactivate the images used only behind a branch the capture does not
take, and bind them as null descriptors.

PPSA15246 (Teardown) then passes the "incompatible with the static runtime
image interface" check of the three image_store variants that are not taken.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 21:14:18 +02:00
xXPBSXx 0accdb487e style(recompiler): drop the comment above the resource control flow planner
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 21:14:18 +02:00
xXPBSXx 7b9c7ca582 fix(recompiler): leave out descriptor sources behind a branch the capture decides
A compute kernel that picks a buffer or an image path from an SRT word reads
the same address as a V# in one path and as a T# in another. The resource
tracker now records which block uses each descriptor source and builds the
resource control flow from the structured block terminators, so a source whose
only uses sit behind a conditional branch with a clean, evaluable condition is
left inactive (bound as zero words, not validated) when the capture takes the
other path. A condition that reads inaccessible memory is not evaluable instead
of throwing.

PPSA15246 (Teardown) stopped on "runtime image descriptor is invalid" for the
untaken path of such a kernel.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 21:14:18 +02:00
Yunus Shaikh a9c625acaf fix(shader): return 0 for 8-bit UNORM color comparison
image_sample_c on an 8-bit UNORM T# returns 0 for every compare
function on gfx1035, as on 8-bit sRGB (measured on the hardware oracle
in the review of #3438), but the emulated comparison still compared the
red channel. 8, 8_8 and 8_8_8_8 UNORM now return 0 like 8-bit sRGB.

Every texel comparison of those formats is 0, so a bilinear cube
without DISABLE_CUBE_WRAP and an anisotropic S# cannot change the
result; both are accepted for them, as the cube already was for 8-bit
sRGB. Other formats keep both refusals.

The test rows that check real comparisons move to 16_16 UNORM.
2026-10-12 12:00:21 -07:00
Dean 9d3629cc91
Merge pull request #4268 from AlxcBTW/fix/vop3p-int-neg
fix(shader): apply VOP3P neg_lo/neg_hi to packed 16-bit integer sources
2026-10-13 07:35:10 +13:00
Dean c90404f97a
Merge pull request #4518 from AlxcBTW/fix/bda-unaligned-dword-write
fix(shader): store unaligned 32-bit BDA writes byte by byte
2026-10-13 07:34:34 +13:00
Dean 0c75934f5f
Merge pull request #4470 from overlordxrz-source/fix/vertex-one-lane-waves
fix(recompiler): run vertex programs as one-lane waves without vertex subgroups
2026-10-13 07:34:13 +13:00
Dean 9f7c40e36f
Merge pull request #4389 from ivica00/fix/rect-list-invariant-position
fix(shader): decorate rect-list gl_Position as Invariant
2026-10-13 07:33:51 +13:00
Dean 5d933db02e
Merge pull request #4364 from SleglJan/fix/structurizer-return-join
fix(recompiler): give an early-return selection a merge when its parent's other arm reaches the same join
2026-10-13 07:33:30 +13:00
Claude 1285ee06f5
fix(shader): keep a folded selection's branch when SPIR-V 1.6 forbids one label
The previous commit pointed the untaken side of a folded branch that
keeps its selection merge at the merge block. That adds an edge from the
header to the merge block: the merge block's phis have no operand for it,
and values from the taken arm no longer dominate the merge block, as
fuzzing with validation on found once the repeated label stopped hiding
it.

For SPIR-V 1.6 the branch is now left as it is, so the control flow and
the untaken arm stay; earlier versions keep naming the taken label twice,
which they allow, and drop the untaken arm as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
2026-10-12 16:40:43 +00:00
Claude 52b679c552
fix(shader): keep the back-edge of a loop whose continue block is dead
Pipeline specialization replaced every unreachable block with
OpUnreachable. When a loop's continue target became unreachable, either
because the latch exited on a constant condition or because the emitter
had already folded it, that removed the header's only back-edge, and
validation rejects a loop header targeted by no back-edge block.

An unreachable continue target of a live loop header now keeps an
OpBranch to its header, as SPIRV-Tools' dead branch elimination does.
The header's phis keep an operand for it, naming the phi itself, since
the edge is never taken and the continue block's values are gone.

Found by recompiling mutated compute shaders with SPIR-V validation on,
for loops with a literal bound such as s_cmp_lt_u32 with two constants.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
2026-10-12 16:25:22 +00:00
Claude 7fd423dfe9
fix(shader): never branch conditionally to one label twice
SPIR-V 1.6 requires the true and false labels of OpBranchConditional to
differ. Two places produced the same label for both:

- The structured terminator emitter, for a conditional branch whose
  targets are the same block, such as s_cbranch_scc0 to the next
  instruction. It now emits OpBranch, keeping OpLoopMerge on a loop
  header.
- Pipeline specialization, when it folded a branch on a constant and
  kept the selection merge because the taken path is not linear. It
  wrote the taken label twice; the untaken side now names the
  selection's merge block, which the constant condition never takes.

Found by recompiling mutated compute shaders with SPIR-V validation on;
before this, 169 of 20000 were rejected for this rule, and builds
without SPIRV-Tools handed the module to the driver.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZWLdTjf1S57iLgn2rcaFb
2026-10-12 16:04:13 +00:00
AlxcBTW 676622fd78 fix(shader): count only id operands as uses when dropping specialization debris
referencedIds() took every operand word of every instruction as a
possible id, so a literal that happened to equal the id of a dead folded
constant or an unreachable block kept it: the OpTypeInt width 32 kept a
dead constant %32, LocalSize literals kept dead labels.

Visit only the <id> operands of each instruction. Literal operands are
skipped for the instructions that carry them: type widths, counts and
image parameters, constant values, storage classes, memory access and
image operand masks (their trailing ids still count), composite and
shuffle indices, OpArrayLength members, extended instruction numbers,
entry point names, execution modes, merge and function controls, branch
weights, OpSwitch case values (two words for a 64-bit selector) and
group operations. An instruction without a listed layout still counts
every word, so an unknown instruction can only keep debris, never drop
something in use.

The SpirvModule case now also folds a switch whose dead constant has id
32 and whose dead blocks' ids appear as LocalSize literals; it fails
without this change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:03:08 +02:00
Dean 8751a29407
Merge pull request #4150 from eljackjoestar-cmd/codex/ancillary-relative-ssa
fix(shader): preserve ancillary proofs across relative VGPR accesses
2026-10-13 03:42:06 +13:00
Dean acb13d1b39
Merge pull request #4030 from AlxcBTW/fix/recompiler-flat-p10-provoking-v2-2
fix(recompiler): read P10 of a flat input as the provoking vertex
2026-10-13 03:41:23 +13:00
Dean cee0290c2c
Merge pull request #4331 from oneandonlydean/up4/fix-3504-shader-prep-time
fix(shader): prove masked lanes unread for every select in one backward pass
2026-10-13 03:07:16 +13:00