You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
AGC: on an AMD RDNA 2 iGPU (Windows) every pixel of a killed BDA fault still lands one read-modify-write (agc_driver_pixel_interlock) #2009
With the checks in DrawFault turned into a log of every mismatch:
Run
Mismatches
Pixels
Counted
Expected
Stored into read range
wave64 BDA fault
256
every pixel with x % 8 == 0 (all 8 columns × 32 rows)
1
0
0
wave32 BDA fault
256
same
1
0
0
The draw does report the unmapped read (BDA access failed, reason=1), and every other pixel counts 64. The three normal passes and the pass after each fault draw pass in both wave sizes. On each faulting pixel, exactly one of the 64 overlapping fragments' increments lands.
The RX 9060 XT (RDNA 4) in the same machine passes the whole test.
ANYPS5_GPU
Device
Driver
Vulkan
Result
Radeon(TM) Graphics
Ryzen 9 7950X iGPU (RDNA 2, 0x164e)
32.0.21045.5002
1.4.315
fails
RX 9060
AMD Radeon RX 9060 XT (RDNA 4, 0x7590)
32.0.31041.1004
1.4.349
passes
The test arrived with #1130 (merged in 3069680), and CI has no AMD device, so this may be its first run on RDNA 2. Not yet known whether the leaked increment comes from the recompiled shader or from the driver's handling of the killed invocation.
Environment: main at 79d23eb, Release, MinGW-w64 GCC 15.2.0 (WinLibs r7), Windows 11 Pro 10.0.26300.
Passes on RDNA 3.5: AMD Radeon 860M (0x1114), driver 32.0.13058.8001, Windows 11 10.0.26300, mainf145139, 5/5 runs (10 draws of 64 overlapping triangles over 2048 pixels, 2 of them with a killed BDA fault in every 8th column). So far only the RDNA 2 device fails; its driver branch (32.0.21045) also differs from both passing devices.
Thanks, that is a useful data point. With it the picture is:
Device
Arch
Driver
Result
Ryzen 9 7950X iGPU (0x164e)
RDNA 2
32.0.21045.5002
fails 5/5
Radeon 860M (0x1114)
RDNA 3.5
32.0.13058.8001
passes 5/5
RX 9060 XT (0x7590)
RDNA 4
32.0.31041.1004
passes
Two things stand out:
The failing device is the only RDNA 2 one and the only one on the 21045 driver branch, so architecture and driver are still confounded.
The driver build numbers do not line up with the architecture order (13058 passes, 21045 fails, 31041 passes), so I would not read this as "older driver = broken". I do not know how to order these build numbers by release date, so please check the Adrenalin version each one belongs to rather than relying on my reading of them.
To separate the two, the most informative runs would be:
The same RDNA 2 iGPU with a different driver branch (for example the one that ships with the 860M or the 9060 XT machine's package), if AMD's package supports the 7950X iGPU. The 7950X iGPU and the 9060 XT are in one machine, so installing one package for both would also remove the "two separate driver installs" variable.
The Layers = 1 fault draw from my previous comment on the failing iGPU. If a faulting pixel still counts 1 of 0 with no overlap, the increment does not depend on the interlock ordering.
If the RDNA 2 failure follows the driver or the architecture and cannot be fixed on the user side, one thing could be tried in the generator without touching other hardware: in StopBdaInvocationIf, set the pixel valid mask to 0 and branch to the common return so that OpEndInvocationInterlockEXT is emitted once at the end and OpKill happens after the critical section (that is what EmitKillIfPixelValidMaskInactive already does for the normal return). It is valid SPIR-V on every device, so it should not regress the two passing ones, but whether it changes behavior on RDNA 2 is unknown until someone runs it there.
fails 3/3, same line: the faulting pixel still counts 1 of 0
RTX 4060
as on main
passes 5/5
With one triangle per pixel there is no overlap and nothing to order, and the faulting invocation's read-modify-write still lands once. So the increment does not come from the interlock ordering; the counter load, add and store after the stopped BDA read run once for the invocation that StopBdaInvocationIf kills. On this driver the architecture and the driver are still the same variable as before; this only rules out that the first machine was a one-off.
I will try the StopBdaInvocationIf change you described on the same device and post the result.
Follow-up with the generator change, same machine (Radeon iGPU 0x164e, driver 32.0.21045.5002; RTX 4060 as control), same tree (main0fdda7b + #1748).
What the fault draw does on the Radeon, with temporary prints after the existing reason=1 check: every killed column pixel ends with guest = 1 and Counters = 0 in both wave64 and wave32, and every live pixel counts 64. So the killed lane's buffer_store is dropped, but its later global_atomic_umax (an OpAtomicUMax through the BDA pointer) still lands with 0 + 1 + 0 = 1, which is why it is 1 of 0 even with one layer. The RTX leaves those pixels at 0. That fits the lane running on past the OpKill that StopBdaInvocationIf puts in a selection whose merge is the rest of the program; the logs show the effect, not how the driver lowers OpKill.
Two changes each pass the test 5/5 on the Radeon and 3/3 on the RTX, with the fault still reported as reason=1 and the read-range check passing:
(a) Your suggestion. Structured SPIR-V cannot branch from the nested selection to a shared return, so the stop branch repeats the return tail: store pixel_valid_mask_active = 0, OpEndInvocationInterlockEXT, the valid-mask kill, OpReturn. After the optimizer this is store 0; End; if (!helper) OpKill; OpReturn, so a lane that survives the kill only reaches OpReturn.
(b) OpTerminateInvocation in place of OpKill. The target is SPIR-V 1.3, so this also needs VK_KHR_shader_terminate_invocation enabled (both devices report it), SPV_KHR_terminate_invocation declared in the module and allowed in ShaderValidation.cpp.
agc_driver_graphics, agc_driver_bda_device and agc_driver_pixel_{helper_lanes,interlock,lds,lds_lane_slots,valid_mask,wave_reduction} pass on both GPUs with either change. Only the interlock test and the ordered-BDA check in agc_driver_graphics reach the changed code, and that check's target does not list the terminate extension, so it does not exercise (b).
Limits: (a) covers only ordered pixel shaders that have the valid-mask variable, and it relies on the kill staying in a selection whose merge is OpReturn; if the !helper test is folded away it is End + OpKill again. (b) also changes non-ordered pixel shaders, whose BDA stops still use a plain OpKill and may leak the same way on this driver; I did not test that case.
New pull requests from me are closed by the intake rule for now, so whoever picks this up is welcome to the patch; I can rerun anything on this device.
On the RDNA 2 iGPU, the faulting fragments are killed with OpKill but still carry out their guest write. With OpTerminateInvocation the test passes. All runs below are on maind70b899 with local diagnostic changes only (no PR). Both GPUs are in the same machine. Results are wave64 and wave32 alike, 256 fault pixels and 1792 others per draw:
Change
iGPU (RDNA 2, 32.0.21045.5002): fault / other pixels
RX 9060 XT (RDNA 4, 32.0.31041.1004)
none
1 / 64
0 / 64
no OpEndInvocationInterlockEXT before the fault's OpKill (critical section held through the kill)
1 / 64
0 / 64
guest global_atomic_umax (dwords 22-23 of FaultPixelCode, opcode 0x38) replaced by s_nop
0 / 0
0 / 0
OpKill in StopBdaInvocationIf replaced by OpTerminateInvocation (SPV_KHR_terminate_invocation, device extension and shaderTerminateInvocation enabled)
0 / 64
0 / 64
The 1 comes from the shader's own guest write (the global_atomic_umax): without that instruction, the fault pixels stay 0.
It doesn't depend on the interlock order. With the critical section held through the kill, the count is still 1, not 64.
A count of exactly 1 with the order held is consistent with the killed fragments carrying on as helpers whose loads return 0 while their atomic through the physical storage buffer still lands, but I haven't measured that directly. With OpTerminateInvocation the invocations end and nothing is stored.
So on this driver, OpKill looks like it behaves as a demote, and the demoted invocation's BDA atomic isn't masked. The RDNA 3.5 and RDNA 4 drivers terminate. Which part is the driver's fault (helper stores landing) depends on how strictly OpKill is defined there. SPV_KHR_terminate_invocation was added because implementations differed on it.
On the generator side, two options:
Emit OpTerminateInvocation where the device supports VK_KHR_shader_terminate_invocation (core in Vulkan 1.3). Tested above: it passes on both devices. The other OpKill sites (SpirvFlowEmitter.cpp:25, SpirvModuleEmitter.cpp:983) would have the same exposure for any store a discarded pixel makes after the kill on this driver. I haven't tested those.
The branch to the common return suggested above. It skips the remaining instructions instead of relying on OpKill. @danielradosa ran it above and it passes on the same iGPU model; I haven't run it here.
AI-assisted: yes
Edit: corrected the guest write to global_atomic_umax (it was mislabelled global_store). This agrees with @danielradosa's runs above, which were posted first and also cover both generator changes.
Third RDNA 2 device, a discrete one, and option 1/(b) from above as a commit.
AMD Radeon RX 6700 XT (Navi 22, 0x73df), driver 32.0.21045.5002 (Adrenalin 25.10.45.05), Vulkan 1.4.315, Windows 11 Pro 23H2, WinLibs GCC 15.2.0 r7, Release. Same driver branch as the two iGPU reports. On main the test faults in amdvlk64.dll first (#2944, fixed by #3173); with #3173 it fails as reported here:
VulkanDevice.cpp: when the device has VK_KHR_shader_terminate_invocation with shaderTerminateInvocation, enables both and offers SPV_KHR_terminate_invocation to the recompiler.
SpirvBdaFault.cpp: StopBdaInvocationIf in a pixel shader emits OpTerminateInvocation (and declares the extension) when it is offered, OpKill otherwise.
ShaderValidation.cpp: accepts SPV_KHR_terminate_invocation in a fragment shader.
SpirvSpecialization.cpp: lists OpTerminateInvocation next to OpKill.
Only the BDA stop changes. The pixel kill and valid-mask OpKill sites (SpirvFlowEmitter.cpp, SpirvModuleEmitter.cpp) are left as they are: nobody has measured them on this driver, and terminating there would also end helper invocations that neighbouring derivatives may need.
Measured on main379d8b4e + #3173 + #1463, with and without the change:
Build
agc_driver_pixel_interlock
Full ctest
without
fails, line above
726 of 732 passed, 5 skipped, 1 failed
with 60609c45 (same change)
passes 3/3
727 of 732 passed, 5 skipped (video_out), 0 failed
Dreaming Sarah (PPSA02929) relinked against that build runs at about 60 FPS as before.
Limits: the branch itself is a clean cherry-pick onto 06da1176 and passes tools/check_conventions.py, but I built and tested the change only on the 379d8b4e stack above. Not tested: a device without the extension (the OpKill fallback), NVIDIA, Intel, Linux.
New pull requests from me are closed by the intake rule, so anyone with intake access is welcome to carry the commit; I can rerun anything on this GPU.
Tested @poonasor's 73969879 on the RDNA 2 iGPU, cherry-picked onto main9ba3d0d (Windows 11 Pro 10.0.26300, MinGW-w64 GCC 15.2.0 r7, Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON):
ANYPS5_GPU
Device
Driver
main
main + 73969879
Radeon(TM) Graphics
Ryzen 9 7950X iGPU (RDNA 2, 0x164e)
32.0.21045.5002
pixel_interlock fails
passes 5/5
RX 9060
RX 9060 XT (RDNA 4, 0x7590)
32.0.31041.1004
passes
passes 5/5
Together with the earlier reports, three RDNA 2 devices on the 21045 branch fail on main: two iGPUs (0x164e) and the RX 6700 XT. The full ctest shows no failure the commit introduces on either device; agc_driver_flat_apertures failed once on the RX 9060 XT in a parallel run and passed 5/5 alone.
One review note on ShaderValidation.cpp: SPV_KHR_terminate_invocation is accepted in any fragment shader without checking that the device enabled shaderTerminateInvocation, while SPV_KHR_fragment_shader_barycentric next to it requires fragmentShaderBarycentric. The emitter only declares the extension when it is offered, so this doesn't change the result above.
The 7950X iGPU on main9ba3d0d also shows #2944: 21 agc_driver_* tests segfault on it in one full run (the set varies between runs), while the RX 9060 XT in the same machine has none. pixel_interlock isn't one of them, so it could be tested without #3173.
Fourth RDNA 2 device, and @poonasor's 73969879 carried forward with @tezzgod74's validation note addressed, plus tests.
AMD Radeon RX 6900 XT (Navi 21, 0x73af), driver 32.0.21045.5002, Vulkan 1.4.315, Windows 11 Pro 10.0.26220, WinLibs GCC 15.2.0, Release. Same driver branch as the other three reports; main fails as reported here:
0ca06cf2 fix(recompiler): StopBdaInvocationIf in a pixel shader emits OpTerminateInvocation (and declares SPV_KHR_terminate_invocation) when the target offers it, OpKill otherwise. Same as 73969879, without the SpirvSpecialization.cpp case: OpTerminateInvocation has no operands, so the VisitInputs default path already handles it identically.
19486efa feat(agc): ShaderValidation accepts the extension only in a fragment shader and only when the device enabled shaderTerminateInvocation, like SPV_KHR_fragment_shader_barycentric next to it (the review note above). The flag is part of the validation memo key in Draw.cpp.
81f711d8 feat(agc): VulkanDevice enables VK_KHR_shader_terminate_invocation and the feature when available; ShaderDeviceProfile rejects a target that advertises the SPIR-V extension without the enabled feature.
4f16f09f fix(agc): validation also checks OpTerminateInvocation itself (fragment stage, enabled feature, extension declared), not only the OpExtension.
a1f407b4 test(agc): recompiler and validation tests for both paths (1 valid-mask OpKill + 5 terminations with the extension, 6 OpKill without; interlock end before every exit; no termination in non-pixel stages; non-interlocked pixel shader; specialization keeps the opcode and declaration; feature struct chained with VK_FALSE is rejected).
f23c94ca docs(technical-debt): records the RDNA 2 measurements, that the OpKill fallback is still affected on a driver without the extension, and the two OpKill sites left as they are.
Measured with #3173 applied (1d7f8bd3, not part of the branch) to remove the #2944 segfaults, -DANYPS5_ENABLE_SPIRV_TOOLS=ON:
The 7 failures are the same on both builds and none is in the changed code: agc_driver_sdwa_float_selectors, agc_driver_sdwa_ldexp_f16 (#1463), agc_driver_lds_mixed_width_atomics, three tls_function_coverage timeouts that pass when run alone, and import_audit (fails only in the git worktree used for the A/B; passes in a normal checkout). No amdvlk64.dll segfault in any of these runs.
Limits: one machine and driver. Not tested: a device without the extension (the OpKill fallback runs only in the recompiler tests), NVIDIA, Intel, Linux/lavapipe. The validation memo key change has no direct test: ValidationKey and CachedFragmentOutputs are internal to Draw.cpp and only reachable through a device-backed draw.
I can't open pull requests under the intake rule either, so anyone with intake access is welcome to carry the branch; I can rerun anything on this GPU.
Fixed by #5804 (65d56a2): pixel invocations stop with OpTerminateInvocation after a BDA fault when the feature is available. agc_driver_pixel_interlock passes on the RDNA 2 iGPU with the fix branch. Closing; reopen if it still fails on current main.
Activity
LeandroLP23 commentedon Oct 9, 2026
Passes on RDNA 3.5: AMD Radeon 860M (0x1114), driver 32.0.13058.8001, Windows 11 10.0.26300,
mainf145139, 5/5 runs (10 draws of 64 overlapping triangles over 2048 pixels, 2 of them with a killed BDA fault in every 8th column). So far only the RDNA 2 device fails; its driver branch (32.0.21045) also differs from both passing devices.AcelezXYZ commentedon Oct 9, 2026
Thanks, that is a useful data point. With it the picture is:
Two things stand out:
To separate the two, the most informative runs would be:
Layers = 1fault draw from my previous comment on the failing iGPU. If a faulting pixel still counts 1 of 0 with no overlap, the increment does not depend on the interlock ordering.If the RDNA 2 failure follows the driver or the architecture and cannot be fixed on the user side, one thing could be tried in the generator without touching other hardware: in
StopBdaInvocationIf, set the pixel valid mask to 0 and branch to the common return so thatOpEndInvocationInterlockEXTis emitted once at the end andOpKillhappens after the critical section (that is whatEmitKillIfPixelValidMaskInactivealready does for the normal return). It is valid SPIR-V on every device, so it should not regress the two passing ones, but whether it changes behavior on RDNA 2 is unknown until someone runs it there.danielradosa commentedon Oct 9, 2026
Reproduced on a second machine with the same iGPU and driver, plus the
Layers = 1run.Ryzen 5 7600 iGPU (RDNA 2,
0x164e), AMD driver 32.0.21045.5002, Vulkan 1.4.315, Windows 11 Pro 10.0.26300; RTX 4060 (0x2882, driver 616.92) in the same machine. MinGW-w64 GCC 15.2.0 (WinLibs r7), Release,BUILD_TESTING=ON,ANYPS5_ENABLE_SPIRV_TOOLS=ON,ANYPS5_REQUIRE_VULKAN=1. Tree:main0fdda7b plus #1748, which only touches libkernel.ANYPS5_GPURadeon(TM) Graphicsmain(64 layers)pixel interlock wave64 BDA fault: pixel 0 counted 1 of 0 overlapping read-modify-writesRadeon(TM) GraphicsLayers = 1RTX 4060mainWith one triangle per pixel there is no overlap and nothing to order, and the faulting invocation's read-modify-write still lands once. So the increment does not come from the interlock ordering; the counter load, add and store after the stopped BDA read run once for the invocation that
StopBdaInvocationIfkills. On this driver the architecture and the driver are still the same variable as before; this only rules out that the first machine was a one-off.I will try the
StopBdaInvocationIfchange you described on the same device and post the result.AI-assisted: yes (Claude Code).
danielradosa commentedon Oct 9, 2026
Follow-up with the generator change, same machine (Radeon iGPU
0x164e, driver 32.0.21045.5002; RTX 4060 as control), same tree (main0fdda7b + #1748).What the fault draw does on the Radeon, with temporary prints after the existing reason=1 check: every killed column pixel ends with guest = 1 and Counters = 0 in both wave64 and wave32, and every live pixel counts 64. So the killed lane's
buffer_storeis dropped, but its laterglobal_atomic_umax(anOpAtomicUMaxthrough the BDA pointer) still lands with 0 + 1 + 0 = 1, which is why it is 1 of 0 even with one layer. The RTX leaves those pixels at 0. That fits the lane running on past theOpKillthatStopBdaInvocationIfputs in a selection whose merge is the rest of the program; the logs show the effect, not how the driver lowersOpKill.Two changes each pass the test 5/5 on the Radeon and 3/3 on the RTX, with the fault still reported as reason=1 and the read-range check passing:
pixel_valid_mask_active = 0,OpEndInvocationInterlockEXT, the valid-mask kill,OpReturn. After the optimizer this isstore 0; End; if (!helper) OpKill; OpReturn, so a lane that survives the kill only reachesOpReturn.OpTerminateInvocationin place ofOpKill. The target is SPIR-V 1.3, so this also needsVK_KHR_shader_terminate_invocationenabled (both devices report it),SPV_KHR_terminate_invocationdeclared in the module and allowed inShaderValidation.cpp.agc_driver_graphics,agc_driver_bda_deviceandagc_driver_pixel_{helper_lanes,interlock,lds,lds_lane_slots,valid_mask,wave_reduction}pass on both GPUs with either change. Only the interlock test and the ordered-BDA check inagc_driver_graphicsreach the changed code, and that check's target does not list the terminate extension, so it does not exercise (b).Limits: (a) covers only ordered pixel shaders that have the valid-mask variable, and it relies on the kill staying in a selection whose merge is
OpReturn; if the!helpertest is folded away it is End +OpKillagain. (b) also changes non-ordered pixel shaders, whose BDA stops still use a plainOpKilland may leak the same way on this driver; I did not test that case.(a), without the temporary prints:
New pull requests from me are closed by the intake rule for now, so whoever picks this up is welcome to the patch; I can rerun anything on this device.
AI-assisted: yes (Claude Code).
tezzgod74 commentedon Oct 10, 2026
On the RDNA 2 iGPU, the faulting fragments are killed with
OpKillbut still carry out their guest write. WithOpTerminateInvocationthe test passes. All runs below are onmaind70b899 with local diagnostic changes only (no PR). Both GPUs are in the same machine. Results are wave64 and wave32 alike, 256 fault pixels and 1792 others per draw:OpEndInvocationInterlockEXTbefore the fault'sOpKill(critical section held through the kill)global_atomic_umax(dwords 22-23 ofFaultPixelCode, opcode 0x38) replaced bys_nopOpKillinStopBdaInvocationIfreplaced byOpTerminateInvocation(SPV_KHR_terminate_invocation, device extension andshaderTerminateInvocationenabled)global_atomic_umax): without that instruction, the fault pixels stay 0.OpTerminateInvocationthe invocations end and nothing is stored.So on this driver,
OpKilllooks like it behaves as a demote, and the demoted invocation's BDA atomic isn't masked. The RDNA 3.5 and RDNA 4 drivers terminate. Which part is the driver's fault (helper stores landing) depends on how strictlyOpKillis defined there.SPV_KHR_terminate_invocationwas added because implementations differed on it.On the generator side, two options:
OpTerminateInvocationwhere the device supportsVK_KHR_shader_terminate_invocation(core in Vulkan 1.3). Tested above: it passes on both devices. The otherOpKillsites (SpirvFlowEmitter.cpp:25,SpirvModuleEmitter.cpp:983) would have the same exposure for any store a discarded pixel makes after the kill on this driver. I haven't tested those.OpKill. @danielradosa ran it above and it passes on the same iGPU model; I haven't run it here.AI-assisted: yes
Edit: corrected the guest write to
global_atomic_umax(it was mislabelledglobal_store). This agrees with @danielradosa's runs above, which were posted first and also cover both generator changes.fix(shader): end a pixel shader's stopped BDA invocation with OpTermi…
poonasor commentedon Oct 11, 2026
Third RDNA 2 device, a discrete one, and option 1/(b) from above as a commit.
AMD Radeon RX 6700 XT (Navi 22, 0x73df), driver 32.0.21045.5002 (Adrenalin 25.10.45.05), Vulkan 1.4.315, Windows 11 Pro 23H2, WinLibs GCC 15.2.0 r7, Release. Same driver branch as the two iGPU reports. On
mainthe test faults inamdvlk64.dllfirst (#2944, fixed by #3173); with #3173 it fails as reported here:Commit
73969879onpoonasor:fix/shader-bda-stop-terminate-invocation(one commit onmain06da1176, 4 files, +31/-1):VulkanDevice.cpp: when the device hasVK_KHR_shader_terminate_invocationwithshaderTerminateInvocation, enables both and offersSPV_KHR_terminate_invocationto the recompiler.SpirvBdaFault.cpp:StopBdaInvocationIfin a pixel shader emitsOpTerminateInvocation(and declares the extension) when it is offered,OpKillotherwise.ShaderValidation.cpp: acceptsSPV_KHR_terminate_invocationin a fragment shader.SpirvSpecialization.cpp: listsOpTerminateInvocationnext toOpKill.Only the BDA stop changes. The pixel kill and valid-mask
OpKillsites (SpirvFlowEmitter.cpp,SpirvModuleEmitter.cpp) are left as they are: nobody has measured them on this driver, and terminating there would also end helper invocations that neighbouring derivatives may need.Measured on
main379d8b4e+ #3173 + #1463, with and without the change:agc_driver_pixel_interlockctest60609c45(same change)video_out), 0 failedDreaming Sarah (PPSA02929) relinked against that build runs at about 60 FPS as before.
Limits: the branch itself is a clean cherry-pick onto
06da1176and passestools/check_conventions.py, but I built and tested the change only on the379d8b4estack above. Not tested: a device without the extension (theOpKillfallback), NVIDIA, Intel, Linux.New pull requests from me are closed by the intake rule, so anyone with intake access is welcome to carry the commit; I can rerun anything on this GPU.
AI-assisted (Claude Code).
tezzgod74 commentedon Oct 11, 2026
Tested @poonasor's
73969879on the RDNA 2 iGPU, cherry-picked ontomain9ba3d0d (Windows 11 Pro 10.0.26300, MinGW-w64 GCC 15.2.0 r7, Release,-DANYPS5_ENABLE_SPIRV_TOOLS=ON):ANYPS5_GPUmainmain+73969879Radeon(TM) Graphicspixel_interlockfailsRX 9060Together with the earlier reports, three RDNA 2 devices on the 21045 branch fail on
main: two iGPUs (0x164e) and the RX 6700 XT. The fullctestshows no failure the commit introduces on either device;agc_driver_flat_aperturesfailed once on the RX 9060 XT in a parallel run and passed 5/5 alone.One review note on
ShaderValidation.cpp:SPV_KHR_terminate_invocationis accepted in any fragment shader without checking that the device enabledshaderTerminateInvocation, whileSPV_KHR_fragment_shader_barycentricnext to it requiresfragmentShaderBarycentric. The emitter only declares the extension when it is offered, so this doesn't change the result above.The 7950X iGPU on
main9ba3d0d also shows #2944: 21agc_driver_*tests segfault on it in one full run (the set varies between runs), while the RX 9060 XT in the same machine has none.pixel_interlockisn't one of them, so it could be tested without #3173.AI-assisted: yes
abnergtz commentedon Oct 11, 2026
Fourth RDNA 2 device, and @poonasor's
73969879carried forward with @tezzgod74's validation note addressed, plus tests.AMD Radeon RX 6900 XT (Navi 21, 0x73af), driver 32.0.21045.5002, Vulkan 1.4.315, Windows 11 Pro 10.0.26220, WinLibs GCC 15.2.0, Release. Same driver branch as the other three reports;
mainfails as reported here:Branch
abnergtz:fix/agc-pixel-interlock-terminate-invocation(6 commits onmainaac0f1b8, 10 files, +150/-13):0ca06cf2fix(recompiler):StopBdaInvocationIfin a pixel shader emitsOpTerminateInvocation(and declaresSPV_KHR_terminate_invocation) when the target offers it,OpKillotherwise. Same as73969879, without theSpirvSpecialization.cppcase:OpTerminateInvocationhas no operands, so theVisitInputsdefault path already handles it identically.19486efafeat(agc):ShaderValidationaccepts the extension only in a fragment shader and only when the device enabledshaderTerminateInvocation, likeSPV_KHR_fragment_shader_barycentricnext to it (the review note above). The flag is part of the validation memo key inDraw.cpp.81f711d8feat(agc):VulkanDeviceenablesVK_KHR_shader_terminate_invocationand the feature when available;ShaderDeviceProfilerejects a target that advertises the SPIR-V extension without the enabled feature.4f16f09ffix(agc): validation also checksOpTerminateInvocationitself (fragment stage, enabled feature, extension declared), not only theOpExtension.a1f407b4test(agc): recompiler and validation tests for both paths (1 valid-maskOpKill+ 5 terminations with the extension, 6OpKillwithout; interlock end before every exit; no termination in non-pixel stages; non-interlocked pixel shader; specialization keeps the opcode and declaration; feature struct chained withVK_FALSEis rejected).f23c94cadocs(technical-debt): records the RDNA 2 measurements, that theOpKillfallback is still affected on a driver without the extension, and the twoOpKillsites left as they are.Measured with #3173 applied (
1d7f8bd3, not part of the branch) to remove the #2944 segfaults,-DANYPS5_ENABLE_SPIRV_TOOLS=ON:agc_driver_pixel_interlockctestmainaac0f1b8+ #3173The 7 failures are the same on both builds and none is in the changed code:
agc_driver_sdwa_float_selectors,agc_driver_sdwa_ldexp_f16(#1463),agc_driver_lds_mixed_width_atomics, threetls_function_coveragetimeouts that pass when run alone, andimport_audit(fails only in thegit worktreeused for the A/B; passes in a normal checkout). Noamdvlk64.dllsegfault in any of these runs.Limits: one machine and driver. Not tested: a device without the extension (the
OpKillfallback runs only in the recompiler tests), NVIDIA, Intel, Linux/lavapipe. The validation memo key change has no direct test:ValidationKeyandCachedFragmentOutputsare internal toDraw.cppand only reachable through a device-backed draw.I can't open pull requests under the intake rule either, so anyone with intake access is welcome to carry the branch; I can rerun anything on this GPU.
AI-assisted (Claude Code).
blawesom commentedon Oct 11, 2026
agc_driver_pixel_interlockon RDNA 4: no mismatch.AMD Radeon RX 9070 XT (Navi 48, gfx1201, device 0x7550, subgroup 64), RADV Mesa 26.2.4, CachyOS, kernel 7.2.9, GCC 16, Release,
mainabdad2a. The wave64 and wave32 BDA-fault draws count 0 overlapping read-modify-writes (the expected value) and the test passes, standalone and inside a fullctestsweep (793 tests, 0 failures, 4 display-related skips).So the one-RMW-per-killed-pixel behaviour reported on the RDNA 2 iGPU does not reproduce on gfx1201 with Mesa 26.2.4.
fix(shader): end a pixel shader's stopped BDA invocation with OpTermi…
fix(shader): end a pixel shader's stopped BDA invocation with OpTermi…
fix(shader): end a pixel shader's stopped BDA invocation with OpTermi…
fix(shader): end a pixel shader's stopped BDA invocation with OpTermi…
fix(shader): end a pixel shader's stopped BDA invocation with OpTermi…
11 remaining items
fix(shader): end a pixel shader's stopped BDA invocation with OpTermi…
fix(recompiler): terminate pixel invocation on BDA fault when supported
feat(agc): gate SPV_KHR_terminate_invocation on shaderTerminateInvoca…
fix(agc): validate OpTerminateInvocation itself, not only its extension
feat(agc): enable VK_KHR_shader_terminate_invocation on the device
fix(recompiler): terminate pixel invocation on BDA fault when supported
feat(agc): gate SPV_KHR_terminate_invocation on shaderTerminateInvoca…
fix(agc): validate OpTerminateInvocation itself, not only its extension
feat(agc): enable VK_KHR_shader_terminate_invocation on the device
fix(recompiler): terminate pixel invocation on BDA fault when supported
feat(agc): gate SPV_KHR_terminate_invocation on shaderTerminateInvoca…
fix(agc): validate OpTerminateInvocation itself, not only its extension
feat(agc): enable VK_KHR_shader_terminate_invocation on the device
oneandonlydean commentedon Oct 16, 2026
Fixed by #5804 (65d56a2): pixel invocations stop with
OpTerminateInvocationafter a BDA fault when the feature is available.agc_driver_pixel_interlockpasses on the RDNA 2 iGPU with the fix branch. Closing; reopen if it still fails on current main.