Skip to content

fix(agc): build a pipeline with a fragment shader interlock whole, not from libraries - #9185

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
janzezno:fix/pixel-interlock-monolithic
Oct 24, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
janzezno:fix/pixel-interlock-monolithic

Conversation

@janzezno

@janzezno janzezno commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

What

agc_driver_pixel_interlock fails now and then when the test suite runs in parallel. With 12 copies at once on an RX 7900 GRE (RADV, Mesa 26.2.5), 14 of 240 runs failed. Every failure was the first draw of the process: wave64 pass 0: pixel N counted 63 of 64 overlapping read-modify-writes, with N always on an 8x8 tile corner. So one whole wave64 tile lost one increment.

The first draw is the one that uses the fast-linked graphics-pipeline-library pipeline. After it, the background LINK_TIME_OPTIMIZATION relink takes over. The pipeline modes give:

Mode Failed runs
APS5_NO_PIPELINE_LTO=1 (every draw fast-linked) 108 of 120, every pass and both wave sizes
APS5_NO_GPL=1 (whole pipelines) 0 of 240

RADV_DEBUG=shaders,epilogs shows the difference. In the whole pipeline, ACO puts s_waitcnt_vscnt null, 0x0 between the counter buffer_store_b32 and the final exp mrt0 ... done. On GFX11 that done export ends the primitive-ordered section, so the store lands before the next overlapping wave is let in. In the fragment-shader library, ACO moves the export into a separate PS epilog. The main part stores and then jumps to the epilog (s_setpc_b64) without the wait, and the epilog is only exp mrt0 ... done and s_endpgm. The next wave covering the same pixels then reads the count before the store lands. This is a driver bug: a pipeline linked from libraries must behave like the whole pipeline. It is reported upstream as mesa#16653. The workaround below can be removed once the fix has shipped. The fix is in review as mesa!45546: with its revision d71ecb1, ACO waits for the store before the jump to the epilog, and the loaded runs of main's test (without this change) fail 0 of 120 fast-linked and 0 of 240 by default.

This change builds a pipeline whole when its fragment shader declares a fragment shader interlock capability. FragmentInterlock in Pipeline.cpp reads the module's leading OpCapability words, and libraryKeys declines such pipelines, as it already does for rect lists, per-face depth bias and barycentric patches. These pipelines lose the fast link on every driver and are compiled whole at the draw. Primitive-ordered pixel shaders are rare, so this costs one whole compile per such pipeline. A new debt entry, "Fragment shader interlock pipelines", records the reason, the cost and which drivers' fast links are measured.

The test now also requires that none of its pipelines was linked from libraries when the device uses them. Without the Pipeline.cpp change, that check fails on every run, loaded or not, so the regression no longer depends on load.

Overlap: on main 24c6cc33f0, pr_overlap paired this change with #7888, and on b7d1d47a5f also with #9489. #7888 has since merged; the rebase onto e13027a1f3 keeps its functionLdsBuffer condition and this change's condition on the same libraryKeys line. The remaining overlaps are only textual:

Neither depends on this change or this change on them, so the one that merges second rebases, as @mazen320 agreed for #7888 (comment). @jotitadev agreed to the same for #10046 (comment); it is proposed in #9489, without an answer yet. If this change merges first, #9489's rebase keeps both includes. If #9489 merges first, I rebase this change onto it and rerun the loaded agc_driver_pixel_interlock runs and the validation-layer comparison before it merges. Of the pull requests pr_overlap could not compare:

Tested

Arch Linux, AMD Radeon RX 7900 GRE (RADV NAVI31, Mesa 26.2.5), Release, on main caa089fd73.

  • agc_driver_pixel_interlock, 12 copies at once, with MESA_SHADER_CACHE_DISABLE=true so every process compiles its pipelines:

    Mode main this change
    default 98 of 240 failed 0 of 240
    APS5_NO_PIPELINE_LTO=1 (every draw fast-linked) 104 of 120 failed 0 of 120

    With the driver's shader cache disabled the background relink lands later, so more draws use the fast-linked pipeline than the 14 of 240 failures first measured with the cache on.

  • With only the test change (no Pipeline.cpp change), the test failed 3 of 3 runs alone, each at the new check.

  • ctest --test-dir build -R 'agc|shader|recompiler' -V -j6 with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, on main and on this change: both 884 of 885, with the same failure, the agc_driver_tessellation segfault (agc_driver_tessellation segfaults on RADV with Mesa 26.2+: nir_opt_varyings corrupts the TCS input offset's use list (Mesa bug) #5444). Both logs have the same four VUIDs with the same counts, so the change adds no validation errors, and neither log reports the validation layer missing.

  • NVIDIA GeForce RTX 4090 (driver 617.42, Windows 11), tested by @MaxLikesCode in this comment: agc_driver_pixel_interlock passed on main and on this change, with and without the validation layer and with no VUID-; 12 copies at once failed 0 of 240 runs on both, by default and with APS5_NO_PIPELINE_LTO=1. NVIDIA's fast link keeps the ordering, and the debt entry now says so. The workaround still applies on every driver: NVIDIA never relinks with link-time optimization, so a whole pipeline also avoids the fast link's extra vertex-stage VGPRs there, and AMDVLK, AMD on Windows and ANV are not measured.

  • python3 tools/check_conventions.py --base upstream/main passes.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: AMD Radeon RX 7900 GRE (RADV NAVI31, Mesa 26.2.5), Arch Linux; NVIDIA GeForce RTX 4090 (driver 617.42), Windows 11, by @MaxLikesCode
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 9489 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp L22
PR 10046 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp L283

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 2434 docs/dev/debt/libSceAgcDriver.md
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 5925 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6186 docs/dev/debt/libSceAgcDriver.md
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 docs/dev/debt/libSceAgcDriver.md
PR 6300 docs/dev/debt/libSceAgcDriver.md
PR 6412 docs/dev/debt/libSceAgcDriver.md
PR 6481 docs/dev/debt/libSceAgcDriver.md
PR 6539 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6612 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6808 docs/dev/debt/libSceAgcDriver.md
PR 6867 docs/dev/debt/libSceAgcDriver.md
PR 6977 docs/dev/debt/libSceAgcDriver.md
PR 7085 docs/dev/debt/libSceAgcDriver.md
PR 7201 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 7256 docs/dev/debt/libSceAgcDriver.md
PR 7275 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
PR 7449 docs/dev/debt/libSceAgcDriver.md
PR 7604 docs/dev/debt/libSceAgcDriver.md
PR 7642 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7668 docs/dev/debt/libSceAgcDriver.md
PR 7910 docs/dev/debt/libSceAgcDriver.md
PR 7978 docs/dev/debt/libSceAgcDriver.md
PR 8014 core/libs/prx/libSceAgcDriver/tests/execution/PixelInterlock.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8223 docs/dev/debt/libSceAgcDriver.md
PR 8319 docs/dev/debt/libSceAgcDriver.md
PR 8365 docs/dev/debt/libSceAgcDriver.md
PR 8542 docs/dev/debt/libSceAgcDriver.md
PR 8592 docs/dev/debt/libSceAgcDriver.md
PR 9356 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9387 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9393 docs/dev/debt/libSceAgcDriver.md
PR 9401 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9431 docs/dev/debt/libSceAgcDriver.md
PR 9432 docs/dev/debt/libSceAgcDriver.md
PR 9450 docs/dev/debt/libSceAgcDriver.md
PR 9592 docs/dev/debt/libSceAgcDriver.md
PR 9672 docs/dev/debt/libSceAgcDriver.md
PR 9673 docs/dev/debt/libSceAgcDriver.md
PR 9674 docs/dev/debt/libSceAgcDriver.md
PR 9675 docs/dev/debt/libSceAgcDriver.md
PR 9676 docs/dev/debt/libSceAgcDriver.md
PR 9700 docs/dev/debt/libSceAgcDriver.md
PR 9842 docs/dev/debt/libSceAgcDriver.md
PR 9891 docs/dev/debt/libSceAgcDriver.md
PR 9892 docs/dev/debt/libSceAgcDriver.md
PR 9893 docs/dev/debt/libSceAgcDriver.md
PR 9895 docs/dev/debt/libSceAgcDriver.md
PR 9898 docs/dev/debt/libSceAgcDriver.md
PR 9904 docs/dev/debt/libSceAgcDriver.md
PR 9905 docs/dev/debt/libSceAgcDriver.md
PR 9906 docs/dev/debt/libSceAgcDriver.md
PR 10049 docs/dev/debt/libSceAgcDriver.md
PR 10193 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10233 docs/dev/debt/libSceAgcDriver.md
PR 10351 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10420 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10472 docs/dev/debt/libSceAgcDriver.md
PR 10515 docs/dev/debt/libSceAgcDriver.md
PR 10581 docs/dev/debt/libSceAgcDriver.md

@MaxLikesCode

Copy link
Copy Markdown
Contributor

RTX 4090 (Ada) on Windows, head fc8af7938 merged onto main cecb540a6 (clean):

  • agc_driver_pixel_interlock: main and this PR each pass 3 of 3 without and 3 of 3 with the validation layer, no VUID-.
  • 12 copies of agc_driver_pixel_interlock_tests.exe at once, each in its own working directory, 240 runs per row:
Mode main this PR
default 0 failed 0 failed
APS5_NO_PIPELINE_LTO=1 (every draw fast-linked) 0 failed 0 failed

So on NVIDIA's driver the ordering held in pipelines linked from libraries: with every draw fast-linked, main passed 240 of 240 here, where RADV failed 108 of 120. The workaround is harmless on NVIDIA, but it is only needed on RADV; if you want whole pipelines only where they are needed, it could be limited to VK_DRIVER_ID_MESA_RADV, the way #7888 limits its buffer to VK_DRIVER_ID_NVIDIA_PROPRIETARY.

NVIDIA GeForce RTX 4090, driver 617.42, Windows 11 Pro 26200, WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON, ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1, ctest --test-dir build -R <tests> -V. Validation runs add VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation (Vulkan-ValidationLayers vulkan-sdk-1.4.363.0, implicit layers disabled); the loader log shows the layer inserted in every Vulkan test process, and no log has was not found but was requested by env var VK_INSTANCE_LAYERS.

AI-assisted: yes (Claude Code)

@janzezno

Copy link
Copy Markdown
Contributor Author

Thanks for the NVIDIA runs, especially the APS5_NO_PIPELINE_LTO=1 row; that confirms the ordering bug is RADV's fast link, not something every driver does. I'd still like to keep whole pipelines for interlock shaders on every driver. On VK_DRIVER_ID_NVIDIA_PROPRIETARY, PipelineLinkTimeOptimizationUsable is false, so the fast link is never replaced by an optimized relink, and these pipelines would keep the fast-linked vertex stage's extra VGPRs for the whole run. Building them whole costs one compile at the first draw, and these shaders are rare. It also keeps the drivers nobody has measured yet (AMDVLK, AMD on Windows, ANV) safe without a driver check that needs a version limit once mesa#16653 ships. I've added your result to the debt entry so the unmeasured list is accurate. If the first-draw compile ever shows up in a trace, limiting it to RADV is an easy follow-up.

@github-actions github-actions Bot added docs agc libSceAgc* labels Oct 21, 2026

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Metal / MoltenVK data point for the "drivers nobody has measured yet" list in the debt entry.

Machine: Apple M2 Max, macOS 27.2, x86_64 under Rosetta 2. MoltenVK 1.4.2 (Vulkan SDK 1.4.363.0). Release, BUILD_TESTING=ON.

Base/method: PR #8658 feat/macos-port @ 4ebe71f2e5 (current main 0c6a5bd593 + the macOS port), not main. This PR applied on top, clean.

On MoltenVK the device reports pipelineLibrary=0, so graphicsPipelineLibrary is off and libraryKeys() already returns false for every pipeline. The new FragmentInterlock() branch therefore changes nothing here (no pipeline is ever linked from libraries), and the new Require(!device->GraphicsPipelineLibraries() || ...) is satisfied by the left operand. So the change is a no-op on this host and the RADV fast-link ordering bug this PR fixes cannot occur on MoltenVK — Metal is not a vendor that can exercise the regression.

It is also worth noting that agc_driver_pixel_interlock itself does not pass on MoltenVK, with or without this PR, for an unrelated reason: SPIRV-Cross emits MSL that Metal rejects at pipeline creation —

error: type 'const constant spvDescriptorSetBuffer0 &' is not valid for attribute 'buffer'
note: field with illegal attribute 'raster_order_group' declared here
    coherent device _17* buffers_coherent [[id(2), raster_order_group(0)]][1];

so the interlock test is blocked on Metal before this PR's path is reached. No regression from this PR on Apple.

GPUs tested: Apple M2 Max (MoltenVK 1.4.2).

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@janzezno

Copy link
Copy Markdown
Contributor Author

Thanks for checking on Apple. You're right that it's a no-op there: with graphicsPipelineLibrary off, libraryKeys returns false before it reaches FragmentInterlock, so no pipeline is fast-linked and the RADV ordering bug can't happen. I'll leave the debt entry as it is. "Other drivers are not measured" only covers drivers that actually fast-link from libraries, and MoltenVK doesn't.

The Metal compile failure (raster_order_group on an argument buffer field) is already tracked for the macOS port: it's in the #8658 body and in the issue #1 thread, and @shanto462 took it there. So I'll keep it out of this PR.

@MarcZX10 MarcZX10 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reproduction and evidence are unusually useful here: the report isolates the failure to the fast-linked library path, the regression check fails without the production change, and the described main/change validation-layer runs show matching VUID counts. I could not independently run the GPU test in this environment because it has no Vulkan device or tools; the hosted product jobs are skipped, so I’m treating the GPU results as author-reported. Before this is merge-ready, please address the repository process items: - The branch is 599 commits behind current upstream main (2 commits ahead). CONTRIBUTING requires keeping PR branches current. Please update to current main, rerun the focused validation-layer A/B suite on that base, and refresh the overlap report. - The overlap report identifies open PR #7888 changing the same libraryKeys condition and #9489 touching the same Pipeline.cpp include area. Your description explains how to preserve both #7888 conditions, but the PR discussion does not record an agreed order; CONTRIBUTING asks authors to coordinate overlapping work and declare the dependency/order in the checklist. Please coordinate with both authors and update Depends on to match whichever must land first. My local current-main convention check and git diff --check pass, and the merge-tree is clean. Hosted conventions, eligibility, labels and report pass; Linux, Windows and product-test jobs are skipped. I’m requesting changes for the stale base and overlap coordination only; I did not find a separate code correctness defect in this diff.

I’ve noticed that holding this PR because of #5239 isn’t necessary. It’s fine for PRs to keep coming in; they’ll be handled in due course. Please disregard my earlier request to hold or defer it.

@janzezno

Copy link
Copy Markdown
Contributor Author

@MarcZX10 rebased onto b7d1d47a5f (no conflicts). The validation-layer comparison, the loaded agc_driver_pixel_interlock runs and the check without the Pipeline.cpp change were redone on that base, and check_conventions and pr_overlap too; the description has the results. The overlaps with #7888 and #9489 are textual only (the same libraryKeys line, and neighbouring includes). I've proposed in both threads that neither declares a dependency and the one that merges second rebases, and I'll record their answers in the description.

On mesa#16653: the first fix in the issue thread doesn't cover the fast-linked path yet (92 of 120 fast-linked runs still failed with it), so the workaround is still needed.

@MarcZX10

Copy link
Copy Markdown
Contributor

I’ve noticed that my earlier request to hold this PR based on #5239 was unnecessary. It’s fine to keep submitting PRs; maintainers will handle them in due course. Please disregard that request.

@janzezno
janzezno requested a review from MarcZX10 October 22, 2026 22:07
@MarcZX10

Copy link
Copy Markdown
Contributor

Thanks for refreshing the A/B runs and overlap report. I checked head 9eeac401096b872007a9919953084f6d68a7379a against current upstream/main 853baf3bb95e3a6fb8f1b58d0f7b5bd4cba2d306: the branch is still 242 commits behind (merge base b7d1d47a5f9f32c9c388ad8d47a0055a90ae6e33). The current-main diff check and conventions pass, but the repository asks PR branches to stay current. Please update the branch to current main and rerun the focused A/B and overlap checks on that final head. The report also says the proposed second-merges-first/rebase order for #9489 has no answer yet; please record the author’s agreement or describe a concrete merge/rebase plan before merge. I found no new code correctness issue in this update. The requested follow-up is about current-base validation and the still-open coordination point, not PR volume.

@janzezno
janzezno force-pushed the fix/pixel-interlock-monolithic branch from 9eeac40 to 00042e1 Compare October 22, 2026 23:02
@janzezno

Copy link
Copy Markdown
Contributor Author

@MarcZX10 rebased onto caa089fd73 (no conflicts) and reran everything on that head: the validation-layer comparison (884 of 885 on both, the same agc_driver_tessellation segfault, identical VUIDs), the loaded agc_driver_pixel_interlock runs (main 98 of 240 failed by default and 104 of 120 fast-linked; this change 0 of 240 and 0 of 120), the test-only check (3 of 3 fail at the new assertion), check_conventions and pr_overlap. The description has the results.

Gooshy hasn't answered on #9489 yet, so the description now has a concrete plan. #9489 currently conflicts with main and needs a rebase anyway. If this merges first, that rebase keeps both includes. If #9489 merges first, I rebase this onto it and rerun the loaded runs and the validation-layer comparison before merge.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@adriadam10 adriadam10 self-assigned this Oct 23, 2026
@adriadam10
adriadam10 force-pushed the fix/pixel-interlock-monolithic branch from 00042e1 to 5ce1b55 Compare October 23, 2026 20:27
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@adriadam10
adriadam10 dismissed MarcZX10’s stale review October 24, 2026 10:21

Dismissing a stale review: the requested changes were addressed after it (new head and/or updated description with the asked-for runs) and the merge queue blocks on it. Please request changes again if anything is still open.

@adriadam10
adriadam10 merged commit d098eca into boykopovar:main Oct 24, 2026
12 checks passed
@janzezno
janzezno deleted the fix/pixel-interlock-monolithic branch October 24, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants