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
AMD Windows: draws whose pixel shader reads the packed ancillary VGPR leave pixels unwritten (agc_driver_relative_ancillary fails on main) #4800
On an AMD GPU on Windows, agc_driver_relative_ancillary (added by #4150) fails on main. The draw leaves its pixels unwritten. #4031's new agc_driver_pixel_ancillary_word fails the same way on this GPU. Both pass on lavapipe in CI.
wave 32 packed read
AGC graphics: relative ancillary draw produced an incorrect pixel: 64
The test fills the target with 0x40 (64) before drawing, so the first draw (wave 32 packed read, expected 255) wrote nothing to that pixel. It fails 3 runs of 3, and the same way with each of these set: APS5_NO_GPL=1, APS5_NO_PIPELINE_LTO=1, APS5_SPIRV_OPT=none.
The full ctest -R '^agc_|pixel_ancillary' -j 1 run is 445/446; this is the only failure. pixel_ancillary, which doesn't draw, passes.
#4031 head dd8403112 merged onto that main: its new agc_driver_pixel_ancillary_word fails 5 runs of 5 with ANCILLARY alone, triangle: pixel (0, 0) holds (nan, nan, nan, nan). That test fills its target with 0xff, so NaN is again an unwritten pixel. With APS5_NO_PIPELINE_LTO=1 the failing case moves between "ANCILLARY alone, triangle" and "ANCILLARY after the perspective center, rectangle", but each time the pixel is unwritten. Details: #4031 (comment)
In common: both tests draw with a pixel shader that reads the packed ancillary VGPR, and on this GPU those draws leave the target untouched where the test expects a write. Other AGC draw tests pass on the same machine.
Titles: this is the input behind #2548 (Stray, Kena, Lies of P and other Unreal titles), so ancillary-reading draws on AMD hardware may be affected beyond these tests. I have no such title to check that.
I'm looking into it next: the generated SPIR-V for these draws and what differs on AMD. I'll post what I find here.
First finding: the read of gl_Layer is what loses the pixels.
I dumped the SPIR-V that agc_driver_relative_ancillary's first draw (wave 32 packed read) passes to vkCreateShaderModule on main584cc4c57. The fragment shader composes the ancillary word from gl_SampleID (bits 8+) and gl_Layer (bits 16+), both Flat inputs, with SampleRateShading and Geometry declared. It writes out_mrt_0 only in non-helper lanes and kills nothing else. The vertex shader writes Position, ClipDistance, and reads VertexIndex and InstanceIndex. It does not write Layer.
Then, with a local hook that swaps in an edited fragment module (one OpLoad replaced by an OpBitcast of the constant 0, nothing else changed):
Fragment module
First draw
as emitted
pixel unwritten (incorrect pixel: 64, the fill)
gl_SampleID load replaced by 0
pixel unwritten (64)
gl_Layer load replaced by 0
written; the test moves on and fails later only because the edited module is reused for its other cases
both replaced by 0
written
So on this AMD driver, a fragment shader that reads Layer, in a pipeline whose vertex stage doesn't write it, leaves the color target untouched. lavapipe returns 0 there. I haven't confirmed whether the Vulkan spec defines the value when no pre-rasterization stage writes Layer. Either way, the draw shouldn't lose its output on AMD.
Next I'll check the other direction: the same pipeline with a vertex shader that writes Layer = 0.
One more case narrows it down. With the gl_Layer load left in place but its result unused, the first draw writes its pixel. I changed only the OpBitcast that consumed the loaded value, so the value no longer reaches the ancillary word.
In the emitted module, the layer lands only in bits 16+ of the word, and the output takes bits 8–11 (OpBitFieldUExtract … 8 4). So the layer value never reaches the colour, yet a live use of it loses the write. That looks like the AMD driver taking the fragment's render target layer from a vertex-stage export that is never written, when the fragment shader consumes Layer. The write then goes to a layer the one-layer target doesn't have. I haven't confirmed that mechanism on the hardware side. The SPIR-V results are what's established.
Two ways the emulator could avoid it, for whoever picks this up:
Fold Layer to 0 in the fragment shader when the pipeline's pre-rasterization stage doesn't write Layer, which is the common case. That is also the value the RT index has when the guest vertex shader doesn't export one. The pipeline knows both stages, so it could set it with a specialization constant, like the other per-pipeline values.
Make the vertex stage write Layer = 0 whenever the fragment shader reads it and the guest vertex shader exports none. That needs shaderOutputLayer, and a vertex variant keyed on the fragment shader's needs.
Option 1 keeps the vertex shaders as they are. I can test either one on this GPU.
Fix proposed in #4842: the packed ancillary lowering uses 0 for the render target index field instead of reading the unwritten Layer. On the Radeon 860M agc_driver_relative_ancillary passes with it (445/445 agc_), and with #4031 merged on top (plus the same 0 at its whole-word read) #4031's agc_driver_pixel_ancillary_word passes too.
Activity
LeandroLP23 commentedon Oct 12, 2026
First finding: the read of
gl_Layeris what loses the pixels.I dumped the SPIR-V that
agc_driver_relative_ancillary's first draw (wave 32 packed read) passes tovkCreateShaderModuleonmain584cc4c57. The fragment shader composes the ancillary word fromgl_SampleID(bits 8+) andgl_Layer(bits 16+), bothFlatinputs, withSampleRateShadingandGeometrydeclared. It writesout_mrt_0only in non-helper lanes and kills nothing else. The vertex shader writesPosition,ClipDistance, and readsVertexIndexandInstanceIndex. It does not writeLayer.Then, with a local hook that swaps in an edited fragment module (one
OpLoadreplaced by anOpBitcastof the constant 0, nothing else changed):incorrect pixel: 64, the fill)gl_SampleIDload replaced by 0gl_Layerload replaced by 0So on this AMD driver, a fragment shader that reads
Layer, in a pipeline whose vertex stage doesn't write it, leaves the color target untouched. lavapipe returns 0 there. I haven't confirmed whether the Vulkan spec defines the value when no pre-rasterization stage writesLayer. Either way, the draw shouldn't lose its output on AMD.Next I'll check the other direction: the same pipeline with a vertex shader that writes
Layer = 0.AI-assisted: yes (Claude Code)
LeandroLP23 commentedon Oct 12, 2026
One more case narrows it down. With the
gl_Layerload left in place but its result unused, the first draw writes its pixel. I changed only theOpBitcastthat consumed the loaded value, so the value no longer reaches the ancillary word.In the emitted module, the layer lands only in bits 16+ of the word, and the output takes bits 8–11 (
OpBitFieldUExtract … 8 4). So the layer value never reaches the colour, yet a live use of it loses the write. That looks like the AMD driver taking the fragment's render target layer from a vertex-stage export that is never written, when the fragment shader consumesLayer. The write then goes to a layer the one-layer target doesn't have. I haven't confirmed that mechanism on the hardware side. The SPIR-V results are what's established.Two ways the emulator could avoid it, for whoever picks this up:
Layerto 0 in the fragment shader when the pipeline's pre-rasterization stage doesn't writeLayer, which is the common case. That is also the value the RT index has when the guest vertex shader doesn't export one. The pipeline knows both stages, so it could set it with a specialization constant, like the other per-pipeline values.Layer = 0whenever the fragment shader reads it and the guest vertex shader exports none. That needsshaderOutputLayer, and a vertex variant keyed on the fragment shader's needs.Option 1 keeps the vertex shaders as they are. I can test either one on this GPU.
AI-assisted: yes (Claude Code)
LeandroLP23 commentedon Oct 12, 2026
Fix proposed in #4842: the packed ancillary lowering uses 0 for the render target index field instead of reading the unwritten
Layer. On the Radeon 860Magc_driver_relative_ancillarypasses with it (445/445agc_), and with #4031 merged on top (plus the same 0 at its whole-word read) #4031'sagc_driver_pixel_ancillary_wordpasses too.fix(shader): move the primitive class to request version 19 and compo…
fix(shader): read the ancillary render target index as 0
Upstream sync 6: shader recompiler to upstream/main (slice B) (#448)