Skip to content

AGC: since #6291, Stray (PPSA02101) stops on Intel Arc with "a pixel shader that reads barycentrics … needs a vertex shader before it" on a Geometry-path draw #7693

Open

Description

@ThatsLeo

What happens

On main a373e85e5, Stray (PPSA02101) stops about 15 s after start, at its first frames, on Intel Arc (no VK_KHR_fragment_shader_barycentric):

[gpu] driver failure: AGC driver: a pixel shader that reads barycentrics without VK_KHR_fragment_shader_barycentric needs a vertex shader before it
terminate called after throwing an instance of 'std::runtime_error'

It starts with #6291. A bisect over main's first-parent history from 2da2284de^1 to fea306dff gives 18bd70080 (#6291's merge) as the first bad commit:

  • c820102bd, its parent, reaches 64 presents in 75 s with no error;
  • on a373e85e5 with git revert -m 1 18bd70080 alone, the title runs normally again (63 presents in 90 s).

#7681's fix was applied at every bisect step from #6270 on, because the indirect register sentinel abort (#6270) hid this stop on the same range.

Why, as far as I can tell

  • fix(shader): preserve pixel helper lanes in EXEC copies #6291 makes hostExecWord in pixel shaders AND the EXEC copy with WqmU64 of the active lanes.
  • After that, HostInterpolationChecker::Lower no longer accepts some of Stray's pixel shaders. Recompiler.cpp then translates them again with barycentric emulation (barycentricEmulation = true).
  • In Draw.cpp, that emulation needs ShaderPath::Vertex with two programs. A local log just before the require shows Stray's draws:
    • rectList=0 path=0 programs=2 stages=2: a Vertex-path draw, which goes through;
    • rectList=0 path=1 programs=2 stages=2: a Geometry-path draw, which throws.

I haven't checked which part of the interpolation pattern the WQM mask breaks in the checker, so that step is inferred from the bisect and the revert, not shown.

Setup

Build Result
c820102bd (#6291's parent) + #7681 fix 64 presents in 75 s
18bd70080 (#6291) + #7681 fix stops with the error above
a373e85e5 (main) stops with the error above
a373e85e5 + revert of 18bd70080 63 presents in 90 s

@santhoshkumar-dev

AI-assisted: yes

Activity

  1. MarcZX10 commented on Oct 19, 2026

    @MarcZX10
    Contributor

    The bisect and single-commit revert make the regression attribution to #6291 strong. Before choosing a fix, could you capture the specific reason HostInterpolationChecker::Lower rejects the affected pixel shader (for example, its existing diagnostic/trace at the rejection point) and, if possible, a sanitized replay request? That would distinguish a checker false negative caused by the WQM EXEC mask from an interpolation pattern that actually needs geometry-path emulation. The current report establishes the failing draw path, but not which of those code paths is safe to change, so I don't have a validated standalone patch yet.

  2. milescoviello commented on Oct 19, 2026

    @milescoviello
    Contributor

    @ThatsLeo a data point that narrows this down, without the Stray shader. I checked whether #6291's WQM mask on EXEC copies stops host interpolation from lowering for the usual pixel prologues. I recompiled three variants through agc_driver_pixel_barycentric_emulation's draw with fragmentShaderBarycentricEnabled = false (RTX 4070 Ti, Linux), on #6291's parent c820102 and on main 8a526d4:

    • s_mov_b64 s[0:1], exec, s_wqm_b64 exec, exec, v_interp_p1/p2, s_and_b64 exec, exec, s[0:1];
    • s_wqm_b64 exec, exec and the interpolation only;
    • the first one, restoring EXEC with s_mov_b64 exec, s[0:1].

    All three lower to host interpolation on both commits (barycentricEmulation.active 0), and the interpolated pixels match, in both the geometry and the tessellation emulation. So the shader that fails in Stray needs some other pattern; the plain save, WQM and restore around v_interp isn't enough. A dumped request of the failing pixel shader (APS5_DUMP_SHADERS=1, the shader_<address>.req of the program that turns on the emulation) would let someone replay it and see which part of HostInterpolationChecker refuses it.

    AI-assisted: yes (Claude Code)

  3. santhoshkumar-dev commented on Oct 19, 2026

    @santhoshkumar-dev
    Contributor

    Follow-up: opened #7811, currently at 9b45e784b.

    I reproduced a checker false negative with EXEC save/restore between v_interp_p1_f32 and v_interp_p2_f32. The P1 result retains a select guarded by the pre-copy EXEC predicate, while P2 uses a reconstructed predicate. partialOf in HostInterpolationChecker.cpp cannot peel that select because the predicates are different IR values. The PR extends the proof for nested/intersected entry WQM masks, allowing this pair to use host interpolation while preserving #6291's helper-lane behavior. A counterexample that actually widens EXEC after P1 still requires emulation.

    Verified on Windows 11 / AMD RX 6700 XT: all nine focused tests pass with Vulkan required and the Khronos validation layer enabled. On main with the regression tests added, the two new regression checks fail; with the fix, both pass. Neither validation log contains a VUID- error. The original 12 mip-selection cases also pass with zero incorrect pixels.

    Stray recovery on Intel Arc remains unverified. Testing #7811 on the affected setup would help confirm whether this is the same pattern; a sanitized failing pixel-shader .req capture (APS5_DUMP_SHADERS=1) would let us check it directly.

    AI-assisted: yes (Codex).

  4. ThatsLeo commented on Oct 19, 2026

    @ThatsLeo
    ContributorAuthor

    @santhoshkumar-dev #7811 fixes it on the Arc: Stray runs again on main c74aeb2f4 + 9b45e784b, with 62 presents in 90 s and no error, the same as with #6291 reverted.

    Checker rejections. I logged every HostInterpolationChecker::Lower rejection in one 90 s Stray run with no shader cache, using a local log at each return false:

    Build Pixel shaders rejected
    main 122
    main without #6291's ExecMask.cpp change 68
    main + #7811 49, all of them also in the 68

    The 54 that #6291 adds all fail at the same place: exact accepts every interpolation, but a barycentric GetBuiltin is still live after lowering. They are not rejected at partialOf.

    What keeps the barycentric live. On the smallest of them (0x2011160000, APS5_DUMP_IR, after the first fold):

    %30 = GetBuiltin 0x0b, 0     ; barycentric i
    %31 = GetBuiltin 0x0b, 1     ; barycentric j
    ...
    %65 = WqmU64 (CompositeConstructU64 (%28 & WqmU64(Ballot true)), (%29 & WqmU64(Ballot true)))
    %74 = INotEqual32 (bit LaneId of %65), 0                 ; the EXEC predicate, 15 uses
    %78 = InterpolateHostP1 0, 0, %30
    %80 = SelectU32 %74, %78, %30
          InterpolateHostP2 0, 0, %31, %80, %74
    %86 = GetAttribute 0, 0
    %87 = SelectU32 %74, %86, %30                            ; v_interp_p2's destination keeps i in inactive lanes
    

    P1 and P2 share %74 here, so the pair itself is accepted. What keeps %30 alive is the select on the v_interp_p2 result (%87). Its condition is now the WQM of the entry EXEC (%28/%29, from Ballot(GetBuiltin 0x12 == 0)) ANDed with the WQM of every lane. MaskedSelectEliminator no longer reduces that to "every lane", so the inactive-lane arm, the raw barycentric, survives the lowering. Before #6291 the same copy was entry & Ballot(true), which it removed.

    Which guest instructions produce that EXEC is my reading of the IR, not of a disassembly. #7811's nested/intersected entry WQM masks match it, and with it this shader lowers.

    The dumped requests are on ThatsLeo:evidence/7693-stray-requests. They were written by a local RequestSerializer dump at the rejection, from main c74aeb2f4, and replay with agc_shader_replay:

    AI-assisted: yes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions