Repository navigation
Astro Bot (PPSA21564) on Linux/NVIDIA: NGG passthrough vertex program with s_barrier fails SPIR-V validation at its first draw on main de5e541d #2871
Description
Title: Astro Bot (PPSA21564 v01.007.000, retail, owned). Converted from the dump's eboot.bin with relinker --registry built from the same main, no --to-intel. Follow-up to #2340, closed with "open a new issue for the next stop on current main".
Host: Linux (Omarchy, kernel 7.2.5), GCC 16.2.1, RTX 3090, NVIDIA 610.57.04 (subgroup 32), selected with ANYPS5_GPU=3090. DualSense connected, no input given. Frame rates not measured.
Builds: main de5e541, Release, -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON (what build.yml's linux job uses). Then the same main with these stacked in order: (a) a one-line local probe that drops s_barrier in Vertex-stage programs (not a fix, see stop 1); (b) (a) + #2653 (gh pr diff | git apply --3way, clean); (c) (b) + the three-line plain mode from #2712. Also main + #2261 alone (stop 1 only), and main with -DANYPS5_ENABLE_SPIRV_TOOLS=OFF ("the build without validation" below).
Launch: APS5_TRACE_DISPATCH_IO=1 ANYPS5_GPU=3090 timeout 300 ./app.elf, one launch at a time, cold shader cache then warm. The first runs were plain windows under Hyprland, which stops rendering a window whose workspace is not visible; the later runs were inside gamescope --backend headless -W 2304 -H 1296 -r 60 and are marked as such.
Stop 1 — main de5e541, first draw: an NGG passthrough vertex program with s_barrier fails SPIR-V validation
Cold: 1260 shaders prepared in ~180 s, swapchain 2304x1296 created, 46 Material [...] is Replaced. lines, no dispatch, then the background preparation logs
[Shaders/ShaderRegistry.cpp:590 operator()] Shader 0x5006bff00 could not be prepared after registration: ShaderRecompiler::Recompile: SPIR-V validation before optimization failed:
level=2 word=102: OpEntryPoint Entry Point <id> '24[%24]'s callgraph contains function <id> '24[%24]', which cannot be used with the current execution model:
[VUID-StandaloneSpirv-OpControlBarrier-04682] in Vulkan environment, OpControlBarrier execution scope must be Subgroup for Fragment, Vertex, Geometry, TessellationEvaluation, RayGeneration, Intersection, AnyHit, ClosestHit, and Miss execution models
[VUID-StandaloneSpirv-None-04637] in Vulkan environment, Workgroup execution scope is only for TaskNV, MeshNV, TaskEXT, MeshEXT, TessellationControl, and GLCompute execution models
[VUID-StandaloneSpirv-None-07321] Workgroup Memory Scope is limited to MeshNV, TaskNV, MeshEXT, TaskEXT, TessellationControl, and GLCompute execution model
%main = OpFunction %void None %35
and the first draw that binds the program rethrows it (AwaitRegisteredPreparation from ShaderPreparationTransaction::Read, checked with a local print):
terminate called after throwing an instance of 'std::runtime_error'
what(): ShaderRecompiler::Recompile: SPIR-V validation before optimization failed:
level=2 word=102: OpEntryPoint Entry Point <id> '24[%24]'s callgraph contains function <id> '24[%24]', which cannot be used with the current execution model:
(followed by the same three VUID lines). Warm: the same abort ~10 s after start, after 878 cache hits. The serialized RecompileRequest is in the log.
The program, from that request:
- Registered type 2, planned as
Stage::Vertex, wave32. 104 words, entry at offset 0, no DS instruction, no lane read.s_barrierat pc 0x20;s_sendmsg 9(GS_ALLOC_REQ) at 0x38 behind a wave-id test (s_bfe_u32 s107, s3, 0x40018,m0 = prim_count << 12 | vertex_countfroms2); a primitive export and a position export;s_endpgmat 0xa0. - Header specials:
VGT_SHADER_STAGES_EN(0x2d5) =0x02402000, i.e.PRIMGEN_ENandPRIMGEN_PASSTHRU_ENwith wave32;GE_CNTL=0x8040. Shader registers:0xc8/0xc9 = 0x05006bff/0,0x8a/0x8b = 0x622c0041/0x00030000.
This is an NGG passthrough program whose barrier only orders the wave-0 GS_ALLOC_REQ, which the vertex path already drops (EmitSendmsg is void). TranslationContext::sBarrier emits IrOpcode::Barrier for every stage, and EmitBarrier emits OpControlBarrier with Workgroup scope, which is invalid in the Vertex execution model. #1825 and #2261 route NGG programs that use LDS or s_barrier to the mesh path but keep passthrough programs on the vertex path by design: #2261's registration check is (routing & 0x02002024) == 0x2000, and the DecodeShaderStages check they carry throws for passthrough with a workgroup. With #2261 applied on main the stop is unchanged: same text, same program. Same family as #2783 and the Wolverine note on #1825 (registered NGG programs on the vertex path), different symptom: no lane-63 read, so it does not depend on the host subgroup size.
Validation only runs with ANYPS5_ENABLE_SPIRV_TOOLS=ON. Built with it OFF, the same main hands the module to the driver, no driver error appears in the log (the only Error lines are gamescope's DRM-modifier notes), and the title runs to stop 2 (cold and warm, in gamescope). A build without SPIRV-Tools does not see this stop.
The local probe used to get past this stop with validation on (not proposed as a fix): sBarrier() returns without emitting when program.Resources().stage == IrShaderStage::Vertex. With it, main reaches stop 2, like the build without validation.
Stop 2 — main past stop 1, after LevelDocument Loaded: worldmap: #2712
terminate called after throwing an instance of 'std::runtime_error'
what(): compute shader 0x500599900: image descriptor is incompatible with the static runtime image interface
Reached in six runs: the build without validation, cold and warm, in gamescope; the probe build, warm twice, in gamescope; the probe build + #2653, warm twice, under Hyprland. In each: PLAY: [0:05..0:10] StartLevel ps_logo, PLAY: [0:57..1:14] StartLevel title (title_controller_ship; in the two Hyprland runs 0:05/0:42 and 0:54/1:34), PLAY: Playing: , Continue: worldmap, LevelDocument Loaded: worldmap [worldmap], about 100,000 [dispatch-io] lines, then this throw. Presentation evidence is the frame counter in the window title under Hyprland (828 and 917 frames at the stop); no capture was kept, and the gamescope runs have no frame record. #2712 reports the same text on PPSA21567 as compute shader 0x50059eb00; here it is 0x500599900. With the three-line plain mode from #2712 applied, 0x500599900 is dispatched once and the title runs about 40 s further, to stop 3, in the one run that went past it; too short to say anything about #2775's hang.
Stop 3 — main + probe + #2653 + #2712 mode, on the world map after PLAY: Playing: , Continue: worldmap: #1409 / #425
terminate called after throwing an instance of 'std::runtime_error'
what(): compute shader 0x5006cdc00: AGC graphics: storage image access to depth/stencil surface 0x53a500000 is not implemented
ShaderResources.cpp:806, warm, in gamescope. The surface has two storage descriptors, both created before the first level loads and both used throughout the run: StorageTexture address=0x53a500000 960x540 ... format=22 vk=100 (R32_SFLOAT) and ... format=36 vk=122 (B10G11R11_UFLOAT). The throw comes on the 4561st dispatch of 0x5006cdc00 (1520 of the earlier ones bound 0x53a500000, all through the vk=122 descriptor); the throwing dispatch's own bindings are not in the log. This is #1409's text on main and the surface oneandonlydean reports on #425 (where the message becomes storage image access to depth surface 0x53a500000 (960x540, vk format 126) as a 960x540 image of vk format 122 ...). #425 was not tested here.
Seen once, not reproduced: #2653
terminate called after throwing an instance of 'std::runtime_error'
what(): ShaderRecompiler::Recompile: GetImageResource dword 0 is not a valid runtime value; chain: Phi(ReadConst(GetSrtResource, Immediate), ReadConst(GetSrtResource, Immediate))
Fragment program 0x5008bc400 (2464 words), in the one warm run of the probe build under Hyprland, ~150 s in, while the title was still in its boot UI (LevelDocument Loaded: ui_pause_next was the last level line) and the window was being throttled. Four later runs of the probe build and of the build without validation, in gamescope, did not hit it and reached stop 2. It is the line #2653 names for this title; #2653 was kept in the stack for stop 3.
Deferred by #1836 (not stops), per run
| Reason | count |
|---|---|
its s_swappc_b64 at program counter 0x… calls a computed target |
96 |
its program address is not in the shader registers |
32 (33 once past stop 1) |
None of them was dispatched before stop 3. Also seen, not stops: [gpu] storage image 0x…: guest memory changed while GPU results were pending; keeping the CPU's blocks (8 in every run that got past its first dispatches; 0 and 1 in the two cold runs that hit the cap early); [gpu] host import budget: 4483 MiB live of 6144 MiB ... imports refused (count grows during a run); once, on a cold run that hit the cap, double free or corruption (out) during the timeout teardown.
Reached / not reached
Evidence level not_tested: nobody watched a frame. Past stop 1 the title's own log reaches ps_logo, title_controller_ship and the worldmap load, and the window-title frame counter advances under Hyprland, but no capture was kept. On main as the linux CI job builds it (validation on) there is no frame before stop 1. Not reached: the world map itself, any level, input, audio, save/resume, Windows, RADV on this machine.
Logs for every run are kept locally; say which one you want. AI-assisted: yes (Claude Code, Claude Fable 5.1), operating under my supervision; I am accountable for this report and will respond to review.
Activity
ak800i commentedon Oct 11, 2026
@snoozedDev would you be willing to share the eli file with me?
snoozedDev commentedon Oct 12, 2026
No, the ELF is the game itself and I don't distribute it. The conversion steps are in the issue; dump your own copy and run
relinker --registryas described