Repository navigation
PPSA20612: missing static shader ABI register 201 #2341
Description
Game
Dragon Quest III HD-2D Remake, title ID PPSA20612 (retail dump, Unreal Nicola project). Linux host. This is the next stop after #1789 on the same title.
What failed
The process aborts with exit 134:
terminate called after throwing an instance of 'std::runtime_error'
what(): AGC driver: missing static shader ABI register 201
Register 201 is 0xC9. It is not a missing shader and not a system call.
PrepareRegistered reads the high half of the program address from sh_registers before it recompiles that shader. For registered shader type 2 (vertex) and type 4 (mesh) the low half is register 0xC8 and the high half is 0xC9:
AnyPS5/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
Lines 294 to 329 in d70b899
| std::uint32_t RegisterValue(const Registers& registers, std::uint32_t offset) { | |
| const auto found = registers.find(offset); | |
| if (found == registers.end()) throw std::runtime_error("AGC driver: missing static shader ABI register " + std::to_string(offset)); | |
| return found->second; | |
| } | |
| void BuildRegisteredAbiKey(const QueueState& state, const VulkanDevice& device, std::vector<std::uint64_t>& key) { | |
| key.clear(); | |
| key.reserve(7u + state.shader.size() + state.context.size() + state.userConfig.size()); | |
| key.insert(key.end(), {device.Serial(), ShaderRecompiler::DebugProbeActive(), ShaderRecompiler::RayTracingStrict(), ShaderRecompiler::RayTracingMiss()}); | |
| for (const auto* registers : {&state.shader, &state.context, &state.userConfig}) { | |
| key.push_back(registers->size()); | |
| for (const auto& [offset, value] : *registers) key.push_back((static_cast<std::uint64_t>(offset) << 32u) | value); | |
| } | |
| } | |
| std::vector<PreparedShaders::Entry> PrepareRegistered(const ShaderSnapshot& snapshot, const VulkanDevice& device, const QueueState& state, bool registration) { | |
| using Stage = ShaderRecompiler::ShaderStage; | |
| const auto header = ReadHeader(snapshot); | |
| std::uint32_t programRegister; | |
| std::uint32_t resourceRegister; | |
| std::uint32_t firstUser = 0; | |
| Stage stage; | |
| switch (snapshot.type) { | |
| case 0: stage = Stage::Compute; programRegister = 0x20c; resourceRegister = 0x213; break; | |
| case 1: stage = Stage::Fragment; programRegister = 0x008; resourceRegister = 0x00b; break; | |
| case 2: stage = Stage::Vertex; programRegister = 0x0c8; resourceRegister = 0x08b; firstUser = 8; break; | |
| case 4: stage = Stage::Mesh; programRegister = 0x0c8; resourceRegister = 0x08b; break; | |
| case 5: stage = Stage::Local; programRegister = 0x148; resourceRegister = 0x10b; firstUser = 8; break; | |
| case 6: stage = Stage::Mesh; programRegister = 0x088; resourceRegister = 0x08b; break; | |
| case 7: stage = Stage::TessellationControl; programRegister = 0x108; resourceRegister = 0x10b; break; | |
| default: throw std::runtime_error("AGC driver: unsupported registered shader type"); | |
| } | |
| const auto high = RegisterValue(state.shader, programRegister + 1); | |
| if ((high & ~0xffu) != 0) throw std::runtime_error("AGC driver: invalid registered program address"); | |
| const auto address = (static_cast<std::uint64_t>(RegisterValue(state.shader, programRegister)) << 8u) | (static_cast<std::uint64_t>(high) << 40u); |
const auto high = RegisterValue(state.shader, programRegister + 1);RegisterValue throws when that offset is absent. The log does not print snapshot.type, so this is either the vertex program (0xC8) or the mesh program (same base). The throw is the first lookup, so the log also does not say whether 0xC8 itself was present.
The draw decoder treats a missing high half as zero instead of throwing:
AnyPS5/core/libs/prx/libSceAgcDriver/Execution/src/Driver/Draw/DrawDecode.cpp
Lines 10 to 25 in d70b899
| std::uint32_t ReadGraphicsRegister(const Registers& registers, std::uint32_t offset) { | |
| const auto found = registers.find(offset); | |
| return found == registers.end() ? 0u : found->second; | |
| } | |
| } | |
| void DecodeGraphicsPrograms(DrawDecode& decoded, const QueueState& queue, const ShaderRegistry& registry, bool staticAbi, bool includeFragment) { | |
| using Stage = ShaderRecompiler::ShaderStage; | |
| using Role = ShaderRecompiler::ProgramRole; | |
| const auto programAddress = [&](std::uint32_t base) { | |
| Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, base); | |
| Graphics::NoteRegisterRead(Graphics::RegisterBank::Shader, base + 1); | |
| const auto high = ReadGraphicsRegister(queue.shader, base + 1); | |
| require((high & ~0xffu) == 0, "reserved graphics program address bits are set"); | |
| return (static_cast<std::uint64_t>(ReadGraphicsRegister(queue.shader, base)) << 8u) | (static_cast<std::uint64_t>(high) << 40u); |
return found == registers.end() ? 0u : found->second;The high value is shifted by 40, so an omitted zero high dword still forms the same address from the low dword. This header's static sh_registers list does not contain 0xC9.
How far it got
On this machine an older libSceAgcDriver stopped earlier with packed pixel ancillary input has an unsupported live use (the shape in #1903 / #1933). After rebuilding libSceAgcDriver from d70b8998 with the still-open #1904 and #1934 applied locally, that message did not return. The shader disk cache then recorded 1413 misses and 1412 writes (format 20, source version 13b6ff1b8c6f0c98, 0 hits, 0 load failures). The abort happened on the shader that was in flight, before a RecompileRequest was printed. No window was created. Vulkan device setup had already succeeded.
Non-fatal lines earlier in the same run, not this abort: Project file not found: ../../../Nicola/Nicola.uproject (it then opened /app0/nicola/content/paks), and sceSysmoduleLoadModule unknown ids 272 and 271.
Host
- Nobara 44, kernel
7.1.3-200.nobara.fc44.x86_64, glibc 2.43 - AMD Ryzen 5 7640U, AMD Radeon 760M (RADV PHOENIX, PCI
1002:15bf), subgroup 64 - Mesa 26.2.3, Vulkan api
0x404162(1.4.354) - Device memory heaps reported by the log: 2717.5 MiB and 5435.0 MiB
- Launch:
DISPLAY=:0,SDL_VIDEODRIVER=x11
Tree the library was built from
main at d70b89989473ba1f6ae13e44e079e67f1f8a44b0. ShaderRegistry.cpp was not locally modified. Local edits on top of that commit:
- fix(shader): lower the packed pixel ancillary when its extract operands fold after it #1904 and fix(shader): carry a packed pixel ancillary through phis when every reader extracts a modelled field #1934 applied to
ConstantFolder.cpp(packed pixel ancillary). Those PRs are still open. RuntimeAbi.hppheap sizes raised for this title (upstream values in parentheses):Version11 (10),ImageCapacity65536 (256),SampledHeapCapacity65536 (16),StorageHeapCapacity16 (4),SamplerHeapCapacity1024 (16). This is the same local change discussed on Sampled image heap of 16 rejects a retail shader before the metadata limit #1789. The register lookup does not use those capacities.
Log excerpt
[src/VulkanDevice.cpp:729 VulkanDevice] Selected GPU name=AMD Radeon 760M Graphics (RADV PHOENIX) vendor=0x1002 device=0x15bf subgroup=64
[src/VulkanDevice.cpp:731 VulkanDevice] Memory heap 0 size=2717.5 MiB flags=0x0
[src/VulkanDevice.cpp:731 VulkanDevice] Memory heap 1 size=5435.0 MiB flags=0x1
[src/VulkanDevice.cpp:1133 VulkanDevice] Vulkan device ready device=0x5583e16390f0 queue=0x5583dd324e30 family=0
[write-watch] host imports resolve write protection: yes (4 of 4 scratch pages written after a GPU read, 4 after the import); imported ranges are compared
[write-watch] dma-buf imports keep write protection: yes (0 of 4 scratch pages written after a GPU read, 0 after the import, 1 seen after a CPU store); imported ranges stay watched
[shader-disk-cache] .../shader_cache/13b6ff1b8c6f0c98 (source version 13b6ff1b8c6f0c98, format 20)
[shader-disk-cache] (10 s): 0 hits, 501 misses, 501 writes (886.2 MiB) ... (totals: 0 hits, 501 misses, 501 writes)
[shader-disk-cache] (10 s): 0 hits, 254 misses, 254 writes (288.8 MiB) ... (totals: 0 hits, 755 misses, 755 writes)
[shader-disk-cache] (10 s): 0 hits, 155 misses, 155 writes (77.3 MiB) ... (totals: 0 hits, 910 misses, 910 writes)
[shader-disk-cache] (10 s): 0 hits, 177 misses, 177 writes (79.6 MiB) ... (totals: 0 hits, 1087 misses, 1087 writes)
[shader-disk-cache] (10 s): 0 hits, 70 misses, 68 writes (142.7 MiB) ... (totals: 0 hits, 1157 misses, 1155 writes)
[shader-disk-cache] (10 s): 0 hits, 143 misses, 145 writes (255.5 MiB) ... (totals: 0 hits, 1300 misses, 1300 writes)
[shader-disk-cache] (10 s): 0 hits, 113 misses, 112 writes (392.0 MiB) ... (totals: 0 hits, 1413 misses, 1412 writes)
[libc] unhandled exception 'St13runtime_error' thrown from 0x7f31856cd693
terminate called after throwing an instance of 'std::runtime_error'
what(): AGC driver: missing static shader ABI register 201
Activity
darklinkworker commentedon Oct 9, 2026
Update after trying the closed pull request that matches this report.
#2413 is closed under the intake pause. Applied locally on top of
d70b8998(with the earlier ancillary edits and the local runtime-ABI heap sizes from #1789), it does what it says. The register 201 abort is gone. The same launch loaded 1,360 shaders from the format 20 disk cache (all hits, no misses) and then stopped one register earlier:Register 200 is
0xC8, the low half of the same vertex/mesh program address. #2413 still requires that low half, and this header does not have it either. Exit 134 again, no window.#1836 looks like the next step. It defers a registered shader whose program-address registers are missing from the header and prepares it on first use, instead of aborting in
PrepareRegistered. I am going to try that next and will update this issue with the result.darklinkworker commentedon Oct 9, 2026
Update after trying #1836, and then the video-out library that change depends on.
#1836, applied locally on
d70b8998with the earlier ancillary edits and the local runtime-ABI heap sizes, got past register 200. Three shaders logged that the program address was not in the shader registers and that preparation was deferred to first use:0x7f358bbe00000x7f358c5200000x7f358d570000The format 20 disk cache then recorded 1,448 hits and 1,387 misses (1,386 writes). The log has no
missing static shader ABI register, and the packed-pixel ancillary abort did not return.That process still aborted, exit 134, with no window:
The only
VulkanDeviceconstructor in that log waswindow=(nil), from shader registration.libSceVideoOut.prxin that run was still the 8 October build, from before #1367. #1367 is what callsAgcDriverAttachWindowwhen VideoOut starts, andPresentin the rebuilt driver requires that attach. I rebuiltlibSceVideoOutfrom current main and launched again.The second launch opened a window. The title read
FPS: 0.00and the image stayed black. That title is only written after a present returns, and it stays at 0.00 until two seconds of presents have been counted, so at least one present completed and the picture did not start updating. About two minutes after the process started, the host kernel faulted and the machine rebooted (previous boot ended 2026-10-09 15:43:34 CDT). Ten NULL dereferences atamdgpu_hmm_invalidate_gfx+0x38inamdgpu, kernel7.1.3-200.nobara.fc44. The first waskcompactd0. The next waseboot.elfinsideamdgpu_gem_userptr_ioctl, throughamdgpu_ttm_tt_get_user_pagesandamdgpu_hmm_range_get_pages.kswapd0andplasmashellfaulted in the same function before the reboot. GPU is the Radeon 760M (RADV PHOENIX), Mesa 26.2.3. The game log for that run was on tmpfs and did not survive the reboot.Register 200 and the missing video-out attach are behind this title. The black window and the amdgpu userptr fault are the current stop. I have not launched it again.
darklinkworker commentedon Oct 9, 2026
Follow-up from a second launch, with the game log and a host memory trace kept on disk.
The window did open. At 135 seconds the title was
DRAGON QUEST III HD-2D Remake | FPS: 0.00 (1), so one present returned. The process then aborted, exit 134:That throw is
ResolveGraphicsAbiinShaderRegistry.cpp: the rectangle draw's shader was registered under its code address, and the bound header pointer is not the registered header's address. The same three shaders as before were deferred at registration because the program address was missing (0x7f62c65e0000,0x7f62c6f20000,0x7f62c7f70000). No kernel message was recorded. Available RAM stayed at or above 4.9 GB, swap stayed at 0, and the game peaked at 5.3 GB RSS. Compaction did run as the process aborted (compact_stall0 to 295) and memory was back to about 10 GB available once it exited. This run did not reproduce the earlier amdgpu userptr fault.#1837 is open and describes this exact throw: a bound copy of a registered header whose fixed fields match except
user_data. I have not applied it yet.darklinkworker commentedon Oct 9, 2026
Follow-up after applying open #1837 locally, on top of the earlier #1836 build.
The replaced-header abort is gone. The window opened again at 135 seconds with
FPS: 0.00 (1), one present, and the process then aborted:DecodeColorBufferrequires the color view's first slice to equal its last slice. This draw's view covers more than one slice, and the vertex stage is exporting a render-target index (PA_CL_VS_OUT_CNTL=0x01240000). That is the same stop as #2102. Available RAM stayed around 4.2 GB, swap stayed at 0, and there was no kernel fault.#1077 is the open change for rendering those layered draws into a 3D color target. It does not apply cleanly onto current main. I have not applied it.
darklinkworker commentedon Oct 9, 2026
Follow-up after applying #1077 locally, including the clip/cull commits it is stacked on. The recompile-request version here was already 13, so the new
paClVsOutCntlfield is version 14.The array-slice abort is gone. The window opened at 170 seconds with
FPS: 0.00 (1), one present, and the process then aborted, exit 134:Stage 7 is
ShaderStage::Mesh. The prepared entry for that code has push layout[0,104], the same offset and size as the draw, andMatchesPreparedShaderstill rejected it, so the mismatch is another field of the prepared key. The shader was not deferred, so the driver did not prepare a new variant at use. There was nocolor views of several array slicesline. Available RAM stayed around 4.1 GB, swap peaked near 77 MB, and there was no kernel fault.danzaio commentedon Oct 10, 2026
Same fix direction as the draw decoder: the static register list has the program low half (0xC8) but no high half (0xC9), and registration throws instead of assuming zero. Branch with the tolerant lookup: danzaio/AnyPS5 fix/shader-missing-high-half. Intake is paused so I cannot open a PR; leaving this here for a collaborator to carry. One caveat: if a title omits the high half for an address above 2^40, zero is the wrong value. The entry-point bounds check still rejects that loudly, but I have no such title to verify against. AI-assisted: yes.
oneandonlydean commentedon Oct 10, 2026
Closing: #1836 defers a shader whose program address isn't in the registered registers to its first use, so registration no longer stops on register 201. If the Mesh-stage
prepared shader artifact is missingstop still happens on current main, please open it as its own issue.danzaio commentedon Oct 10, 2026
Update: current main defers preparation when the program address registers are absent (PlanRegistered, deferred path), and the draw path already reads a missing high half as zero. That covers this issue by design, so I dropped my branch instead of rebasing it against the new flow. Leaving the earlier validation notes above for reference. AI-assisted: yes.