Skip to content

fix(agc): prepare the null pixel program at draw when its variant was not prepared - #1902

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
fisherjoey:fix/agc-null-pixel-on-demand
Oct 10, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
fisherjoey:fix/agc-null-pixel-on-demand

Conversation

@fisherjoey

@fisherjoey fisherjoey commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1901

What

The driver's null pixel program is prepared only in its registration variants (0 user SGPRs). A draw without a pixel program uses the queue's SPI_SHADER_PGM_RSRC2_PS user count and SPI_PS_IN_CONTROL wave bit for it. When those differ from the registration variant, SourceHandleFor and InvocationFor throw prepared shader artifact is missing for the requested static ABI.

The root cause, found by @Raghav314: since #1178 the two emplace calls at registration did nothing, so the null program was registered with the wrong inputs, and a null pixel draw took the last bound pixel shader's user SGPR count.

Tested

Linux (Nobara 44, GCC), RTX 5070 Ti, NVIDIA 595.91.07.

d83e608 adds NullPixelMatchesRegistration to agc_driver_prepared_graphics: a null pixel draw with a previous shader's leftover state (4 user SGPRs, other inputs, a bound export mapping) must use the artifact prepared at registration. It fails without the fix and still fails with only part of it; it passes with all of it. ctest -R agc_: 248/248 on the RTX 5070 Ti and on RADV (Mesa 26.2.1). tools/check_conventions.py passes.

After the rebase onto main 0011248 (with #1836), built with ANYPS5_ENABLE_SPIRV_TOOLS=ON: agc_driver_prepared_graphics and agc_driver_prepared_shaders pass, and ctest -R agc_ -j1 passes 278 of 279 on the RTX 5070 Ti. The one failure, agc_driver_dcc_keys_first_write, also fails on plain main 0011248 on this GPU.

The earlier at-draw check and its results:

New check NullPixelAtDraw() in tests/execution/PreparedShaders.cpp. It prepares the registration variant of the null pixel program, then requests a wave32, 4 user SGPR fragment variant at push offset 20 / capacity 108.

ctest -R agc_driver_prepared_shaders
main 1be6f9f + the test only 4 of 5 fail with prepared shader artifact is missing for the requested static ABI: ... stage=5 wave=32 pushOffset=20 pushCapacity=108 preparedLayouts= [0,128]. _debug_failure passes, as it expects a failure.
this branch 5/5 pass, and agc_driver_prepared_graphics passes too

Stray: without this change it aborts on the first such draw (above). With it, the log shows The null pixel program has no artifact for this draw (wave 64, 4 user SGPRs); preparing it at draw and the run continues to a later, unrelated error.

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
  • No notes, investigation .md files or images added to the repository
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

@Raghav314

Copy link
Copy Markdown
Contributor

Hit this exact crash in Hades II (PPSA36082) and dug into why the null program misses in the first place. Two things:

  • Since fix(agc): SPI_PS_INPUT_ENA/ADDR start at their reset value #1178, SPI_PS_INPUT_ENA/ADDR are already in InitialContextRegisters, so the emplace(0x1b3u, 0x2u) / emplace(0x1b4u, 0x2u) in the null program's registration don't actually do anything. It gets prepared with inputs 0, but the draw asks for defaultPixelInputs (2). So every null pixel draw misses, not just the ones with odd leftover state.
  • The draw also gives the null program the user SGPR count of whatever pixel shader was bound last. The null program is just s_endpgm, so it shouldn't take any.

With insert_or_assign there and a user count of 0, the draw matches the variant from registration and nothing needs compiling at draw. That's enough for Hades. The leftover wave32 bit would still need handling the same way, which your PR already covers.

I've got this on a branch with a test that goes through the real registration path. Happy to share it if you want to use any of it here. Your call how you want to handle it.

@EdouardSence

Copy link
Copy Markdown
Contributor

Astro Bot (PPSA21567) hits the case this PR fixes, and the next stop is a rect-list draw with the same null pixel program.

Setup: RX 9070 XT (RADV / Mesa 26.2.4), Linux. main a8b032b + #759 + #1836 + #1837 + CouchRiv's #1789 branch + this PR (fb6aefc). The only conflict was with #1836 in ShaderRegistry.cpp. I kept your PreparedAtUse and made it return snapshot.prepared->deferred instead of false for registered shaders.

Without this PR: the run stops about 3 min in on prepared shader artifact is missing for the requested static ABI ... stage=5 wave=64 pushOffset=0 pushCapacity=128 preparedLayouts= [0,128] [0,0].

With it: the log prints The null pixel program has no artifact for this draw (wave 64, 0 user SGPRs); preparing it at draw. The same frame then stops on:

AGC driver: prepared rectangle artifacts are missing

I added a local log in PreparedRectangle:

front=<vertex> deferred=0 entries=1 rects=2 fragments=1 vertexId=30a fragmentId=66d
  registered fragment: deferred=0 entries=2
  rectangles: (30a, 30b) (30a, 30c)

So the draw is a rect list that uses the null pixel program variant prepared at draw (66d). ResolvePreparedGraphics only builds rectangle pairs from the variants prepared at registration, so no pair exists for 66d. PreparedRectangle would need to build the missing pair the same way when a stage was prepared at use. I'm leaving that to you, since it extends this PR. Happy to run a candidate on this title.

Tested and written by Claude (AI assistant) on a contributor's machine.

@XairRrR

XairRrR commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Uncharted: Legacy of Thieves Collection (PPSA05684) hits the same miss for the null pixel program on main (a host address ending in 2700, stage 5, pushOffset=20 pushCapacity=108, prepared layouts [0,128] [0,0]). Like @Raghav314 found, the root cause was the emplace of SPI_PS_INPUT_ENA/ADDR doing nothing; locally I prepare the null program with insert_or_assign, no user SGPRs and an empty export mapping, so its registration variant matches the state its draws decode (a test that fails without it is in the branch below).

The title also misses for its own registered shaders, which this PR does not cover:

AGC driver: prepared shader artifact is missing for the requested static ABI: address=<0x1400010700, 0x1400011600, 0x1400026000> stage=5 wave=64 pushOffset=20 pushCapacity=108 preparedLayouts= [0,128] [0,0]

It links libSceAgc statically, so the driver's ABI-resolution hooks never see its state and registration cannot predict the variant (here the push-constant layout). Locally I prepare a registered shader's missing variant at the draw and keep it with the snapshot (with an error log line), which gets the title to its main menu. In terms of this PR, that is PreparedAtUse accepting registered snapshots too. Would you accept that generalization, here or as a follow-up on top of this PR? The branch is XairRrR:feat/agc-prepare-unpredicted-shaders.

@orbz22

orbz22 commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Astro's Playroom (PPSA01325) hits this on Windows with NVIDIA.

Setup: main 745b775 + #1836 + #1837 + #1395 (rebased locally), plus a local workaround for vertex fetch exceeds descriptor record count (no PR yet).

Windows 11, RTX 3060 Laptop (NVIDIA 616.92), WinLibs GCC 15.2.

🤖 Generated with Claude Code

@NahuelGenchi

Copy link
Copy Markdown
Contributor

Tested with Sonic Origins (PPSA05324) on Linux, R9 380 (RADV). Its depth/stencil clear draws have no pixel program, and the title uses three null pixel variants: wave 64 with 0, 4 and 16 user SGPRs. Without this PR the first one stops at prepared shader artifact is missing for the requested static ABI ... stage=5 wave=64 pushOffset=0 pushCapacity=128. With it, all three are prepared at draw. Then, with #1928 for their rect-list pairing, the clears run and the title presents its boot frames.

Bring-up thread: #2021

@fisherjoey

Copy link
Copy Markdown
Contributor Author

Thanks, all. @Raghav314, that was the root cause. Since #1178 the two emplace calls did nothing, so the null program was registered with inputs 0 while every draw asked for 2. The user SGPR count was wrong too: it came from the last bound pixel shader, which matches orbz22's 4 and NahuelGenchi's 0/4/16 variants.

Fixed in d83e608. Registration now uses insert_or_assign for the program address, SPI_PS_INPUT_ENA/ADDR and RSRC2 (no user SGPRs). A null pixel draw gets 0 user SGPRs and an empty export mapping, so it matches the registered variant. The at-draw path is now only for wave32, which registration doesn't prepare. The new check in agc_driver_prepared_graphics decodes a null pixel draw with a previous shader's leftover state (4 user SGPRs, other inputs, a bound export mapping) and requires the artifact prepared at registration. It fails on the previous head. ctest -R agc_ passes on an RTX 5070 Ti and on RADV.

I couldn't find your branch, so the test is mine. If you push yours, I'm happy to add it here with a Co-authored-by line.

@XairRrR, preparing registered shaders at draw is a bigger change than this PR. It decides when registered shaders compile, which oneandonlydean has asked boykopovar to rule on in #1836. I'd keep it as its own PR on top of this one.

@EdouardSence, once #1836 is in I'll rebase and return snapshot.prepared->deferred from PreparedAtUse, as you did.

AI-assisted: yes

@JAB-dev

JAB-dev commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

Another title hitting #1901: Seeker: My Shadow (PPSA12500, Unity) stops with prepared shader artifact is missing for the requested static ABI ... pushOffset=68 pushCapacity=60 on the null pixel program. With this PR on top of #2169 it gets past it and keeps rendering.

@tv7

tv7 commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Another title hitting #1901: Marvel's Wolverine (PPSA03671) on Windows.

Setup: Windows 11 Pro (10.0.26200), RTX 3070, GCC 15.2.0 UCRT (WinLibs), Release. This PR (d83e608) merged onto main 54f28f2 with #1836, #1893, #1709, #1837, #1940, #2022, #2219, and a local change that lets sceKernelSignalSema accept a count of 0.

Without this PR: the first presentation fails with prepared shader artifact is missing for the requested static ABI: ... stage=5 wave=64 pushOffset=0 pushCapacity=128 preparedLayouts= [0,128] [0,0].

With this PR: the null pixel draws pass. The next stop is AGC driver: prepared rectangle artifacts are missing (#1928).

Merge note: with #1836 also applied, SourceHandleFor and InvocationFor conflict. I resolved both as if (snapshot.prepared->deferred || PreparedAtUse(snapshot, request)), which keeps #1836's deferred shaders and this PR's at-draw path.

Limits: one title, one run each way.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

#1836 is merged, so this now conflicts in ShaderRegistry.cpp; could you rebase as planned (snapshot.prepared->deferred from PreparedAtUse)? It passed testing at d83e608.

@fisherjoey

Copy link
Copy Markdown
Contributor Author

Rebased onto main 0011248 as planned. New head: 1140f63.

The only conflict was SourceHandleFor and InvocationFor in ShaderRegistry.cpp. Both now call if (PreparedAtUse(snapshot, request)), and PreparedAtUse returns snapshot.prepared->deferred for a registered shader, so #1836's deferred shaders are still prepared at use. An unregistered program still goes through the compute check as before. Nothing else changed.

Tested on an RTX 5070 Ti (NVIDIA 595.91.07), built with ANYPS5_ENABLE_SPIRV_TOOLS=ON: agc_driver_prepared_graphics and agc_driver_prepared_shaders pass, and ctest -R agc_ -j1 passes 278 of 279. The one failure, agc_driver_dcc_keys_first_write, also fails on plain main 0011248 on this GPU.

AI-assisted: yes

…PRs its draws use

Since boykopovar#1178 the SPI_PS_INPUT_ENA/ADDR emplace calls did nothing, so the registered variant had no pixel inputs, and null pixel draws took the last pixel shader's user SGPR count and the bound targets' export mapping. Registration now overwrites those registers, the draw gives the null program no user SGPRs and an empty export mapping, and the at-draw preparation is left for wave32 only. Root cause found by @Raghav314.
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main (conflicts with #2257 and #1836 in ShaderRegistry.cpp/.hpp, as Dean asked): SourceHandleFor and InvocationFor keep your PreparedAtUse and now call PrepareShaderWithDiagnostics; the header declares both ShaderPreparationContext and VulkanDevice. No other change.

Checked on the rebased head: build + ctest of libSceAgcDriver/libkernel scope, 380/380 passed (agc_driver_prepared_graphics, agc_driver_prepared_shaders included); merging on green CI.

@adriadam10
adriadam10 force-pushed the fix/agc-null-pixel-on-demand branch from 1140f63 to e2664be Compare October 10, 2026 07:10
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
@github-actions

github-actions Bot commented Oct 10, 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
#1732 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp L37-39
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp L203-207, L257-261

@adriadam10

Copy link
Copy Markdown
Collaborator

CI re-run: the only red job was write-watch (macos-15-intel), write_watch_threads timing out at 30 s. This PR doesn't touch write-watch code, the other four write-watch tests and the macos-15 job pass, so it's a runner flake. Merging once the re-run is green.

@adriadam10
adriadam10 merged commit 17d69d1 into boykopovar:main Oct 10, 2026
25 of 26 checks passed
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
…-on-demand

fix(agc): prepare the null pixel program at draw when its variant was not prepared
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Draw with no pixel program fails: "prepared shader artifact is missing for the requested static ABI" (null pixel program)

10 participants