Skip to content

fix(agc): refuse last-vertex strips only for reads of a vertex slot - #5734

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
YossiYad:fix/strip-last-provoking
Oct 15, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
YossiYad:fix/strip-last-provoking

Conversation

@YossiYad

Copy link
Copy Markdown
Contributor

What

With fragmentShaderBarycentric (RADV, NVIDIA) the translator writes v_interp_p1_f32 and v_interp_p2_f32 as P0 + I * P10 + J * P20 over pervertexEXT values, so every smooth pixel input is a per-vertex input. ValidateProvokingVertex refuses a triangle strip drawn with PROVOKING_VTX_LAST when any input is per-vertex, so with GL's last-vertex convention every interpolating shader on a strip threw:

AGC graphics: last provoking vertex with explicit per-vertex triangle-strip interpolation is unsupported

That order question only matters to a shader that reads a slot itself. I and J are loaded from gl_BaryCoordKHR[1] and [2] (InstructionTranslator.cpp, component + 1 in SpirvModuleEmitter.cpp), in the same vertex order as the pervertexEXT array that P0, P10 and P20 come from (vertex 0, vertex 1 minus vertex 0, vertex 2 minus vertex 0). So a v_interp_p1/p2 pair gives b0 * v0 + b1 * v1 + b2 * v2 whichever order the device puts an odd strip triangle in.

  • The translator records the pixel inputs read through v_interp_mov_f32 or 16-bit interpolation in pixelVertexOrderInputs. FragmentParameter carries that as vertexOrder (cache encoder and its size assertion updated), and the strip check refuses only those.
  • 16-bit interpolation would cancel out the same way, but I haven't checked it, so it stays refused, as does every v_interp_mov read. The flat-input check is unchanged.
  • The debt entry now names the slot reads instead of every per-vertex input.

Titles. Both are ps5-opengl (Mesa) titles, and the shaders dumped from ES-DE's run use only v_interp_p1_f32/v_interp_p2_f32:

  • OliSe Player (PPSA01153): stopped about 1 s after start-up in 2 of 5 runs on main. With this PR: 5 of 5 runs of 40 s without a stop, menu and scroller drawn at 59.94 fps.
  • RetroArch's ES-DE frontend (PPSA99169, with fix(libSceSystemService): pass LoadExec arguments as the whole guest argv #5083, which it needs to start): aborted on its first presentation after a game returned to it. With this PR ES-DE draws its UI and ran until the 120 s timeout.

Fixes #5245.

Tested

Linux (Bazzite 44, GCC 16.2.1, Ninja, Release, -DBUILD_TESTING=ON), based on main 315260903:

  • Full build and ctest: 1105 of 1106 pass. agc_driver_tessellation segfaults on unchanged main here too.
  • agc_driver_graphics recompiles a pixel shader that interpolates one input with v_interp_p1/p2 (per-vertex, not vertexOrder, accepted on a last-vertex strip) and one that reads it through v_interp_mov P10 (vertexOrder, refused on a strip, accepted on a list). The first check fails with the strip guard on perVertex, and the second fails without the v_interp_mov record.
  • Title runs (distrobox, RX 7700 XT, RADV): as above.
  • tools/check_conventions.py --base upstream/main passes. tools/pr_overlap.py reports feat(agc): replay proven finite stencil reference exports #5466 on the neighbouring static_assert line of ShaderDiskCache.cpp (CompiledShaderInfo); the two changes don't depend on each other, so whichever lands second keeps both values. Windows was not built here.

Checklist

@babyhuey

Copy link
Copy Markdown
Contributor

Windows run, adding a title this unblocks. Windows 11 Pro 25H2, RTX 3080 (NVIDIA 591.86), WinLibs GCC 15.2.0, Ninja Release. Head 5ea14b9a0 merged onto main 23e93c054 with no conflicts.

  • agc_driver_graphics and agc_shader_disk_cache pass 5 of 5. The build has 0 warnings.
  • Régie5 (PPSA99520, catalog release, relinked with --windows, 60 s):
    • On main it exits 0xC0000409 about 5 s after start-up with AGC graphics: last provoking vertex with explicit per-vertex triangle-strip interpolation is unsupported. That stop is new on main since 13 Oct.
    • With this PR the throw is gone and it runs the full 60 s with its window open. It is slow (no frames for about 45 s, then about 0.4 fps), and I haven't looked into why.
  • OliSe Player (PPSA01153) doesn't relink on Windows (Forbidden syscall instruction), so I couldn't run it here.

AI-assisted: yes

@orishamir

Copy link
Copy Markdown
Contributor

Adding to babyhuey's run: the revert check and the rest of the homebrew catalog. Windows 11, RTX 4080 (NVIDIA 616.56), WinLibs GCC 15.2.0, Release, this head merged onto main 31bfc4e.

  • Full ctest: 1140 of 1140 pass (7 display-only skips). With only Pipeline.cpp taken back to main and the new tests kept, agc_driver_graphics fails on last provoking vertex with explicit per-vertex triangle-strip interpolation is unsupported, so the new strip case pins the change.
  • Whole catalog, 67 titles, 45 s each (--trap-sdk-syscall-stubs, original eboot.bin in app0), main against this PR with the same run settings: 65 titles end the same way (same stop line, or a full run with frames). Régie5 stops 17-19 s in on main in 4 of 4 runs. With this PR it runs the full 45 s in 4 of 4 and draws its Home screen ("No configuration: see Settings"); it presents only when the screen changes. Ruffle (PPSA68091) ran 45 s once with this PR but crashed at 3 s in 3 more PR runs, as it does on main. So that difference is its existing Ruffle, VLC and XPSemu read their GOT at a fixed 0x400000 base from app0/imports.txt and fault once all imports load #4013 crash, not this change.
  • OliSe Player relinks on Windows with --trap-sdk-syscall-stubs. It ran 45 s without the stop on main (2 runs) and with this PR (1 run), so this machine doesn't show the intermittent stop.

AI-assisted: yes (Claude Code Opus 5.5)

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
With fragmentShaderBarycentric (RADV, NVIDIA), v_interp_p1_f32 and
v_interp_p2_f32 are translated as P0 + I * P10 + J * P20 over the
per-vertex values, so every smooth input became per-vertex. A triangle
strip drawn with PROVOKING_VTX_LAST then threw "last provoking vertex
with explicit per-vertex triangle-strip interpolation is unsupported"
for any shader that interpolates, which stops OliSe Player (PPSA01153)
and RetroArch's ES-DE frontend (PPSA99169), both ps5-opengl titles
whose shaders use only v_interp_p1/p2.

I and J come from gl_BaryCoordKHR[1] and [2], in the same vertex order
as the pervertexEXT values, so the result is b0 * v0 + b1 * v1 + b2 * v2
whatever order the device gives an odd strip triangle. Only a shader
that reads a slot itself depends on that order. The translator now
records the inputs read through v_interp_mov_f32 or 16-bit
interpolation, FragmentParameter carries that as vertexOrder, and the
strip check refuses those alone. 16-bit interpolation would cancel out
the same way but has not been checked, so it stays refused.
A pixel shader that interpolates its input with v_interp_p1/p2 compiles
to a per-vertex input without vertexOrder and passes a last-vertex
strip; the same input read through v_interp_mov P10 is vertexOrder and
still throws on a strip, not on a list. The synthetic matrix gets
vertexOrder as a third axis. The first check fails with the guard on
perVertex, the second without the v_interp_mov record.
@YossiYad
YossiYad force-pushed the fix/strip-last-provoking branch from 5ea14b9 to e4d0e2d Compare October 14, 2026 21:19
@YossiYad

Copy link
Copy Markdown
Contributor Author

Rebased onto main 0ae1deb85 (new head e4d0e2dc7). The only conflict was the static_assert block in ShaderDiskCache.cpp, where #5466 changed CompiledShaderInfo to 352 next to this PR's FragmentParameter 16; both are kept, and the PR's changed lines are the same as before. Full build and ctest here: 1181 of 1204 pass and 20 are skipped (display tests). agc_driver_tessellation and agc_host_import_sparse fail as on main, and agc_driver_pixel_interlock failed once under -j8 and passed 3 of 3 alone. agc_driver_graphics and agc_shader_disk_cache pass.

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

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
#1395 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
#1747 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/shader/recompiler/Recompiler.hpp
#1896 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/shader/recompiler/Recompiler.hpp
#4040 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
#4048 core/shader/recompiler/Recompiler.hpp
#4448 docs/dev/debt/libSceAgcDriver.md
#4650 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/Recompiler.hpp
docs/dev/debt/libSceAgcDriver.md
#4943 core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
#5372 docs/dev/debt/libSceAgcDriver.md
#5390 docs/dev/debt/libSceAgcDriver.md
#5499 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
#5500 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
#5609 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
docs/dev/debt/libSceAgcDriver.md
#5656 core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
#5761 docs/dev/debt/libSceAgcDriver.md
#5804 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
#5875 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
docs/dev/debt/libSceAgcDriver.md
#5881 core/shader/recompiler/Recompiler.hpp
#5884 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/Recompiler.hpp
docs/dev/debt/libSceAgcDriver.md
#5905 docs/dev/debt/libSceAgcDriver.md
#5925 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/Recompiler.hpp
core/shader/recompiler/ShaderDiskCache.cpp
docs/dev/debt/libSceAgcDriver.md
#6146 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
docs/dev/debt/libSceAgcDriver.md
#6177 core/shader/recompiler/Recompiler.hpp
#6188 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
#6222 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrMetadata/ProgramMetadata.hpp
core/shader/recompiler/Recompiler.hpp
docs/dev/debt/libSceAgcDriver.md
#6235 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
core/shader/recompiler/Recompiler.hpp
#6292 core/libs/prx/libSceAgcDriver/Graphics/src/Pipeline.cpp
#6300 docs/dev/debt/libSceAgcDriver.md

@adriadam10

Copy link
Copy Markdown
Collaborator

Checked on Linux with the PR merged onto current main: agc_driver_graphics passes, and the new strip cases pin the change (confirmed by @orishamir's revert of Pipeline.cpp on Windows). The v_interp_p1/p2 argument holds: I/J and the pervertexEXT values share one vertex order, so P0 + I*P10 + J*P20 is order-independent. Slot reads (v_interp_mov, 16-bit) stay refused, the debt entry matches, and the cache encoder/size assertion are updated. agc_driver_tessellation fails on main too. Third-party Windows runs by @babyhuey and @orishamir cover what Linux can't; merging.

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

Labels

agc libSceAgc* docs shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AGC: OliSe Player stops on 'last provoking vertex with explicit per-vertex triangle-strip interpolation is unsupported'

4 participants