Skip to content

PPSA20612: missing static shader ABI register 201 #2341

Closed

Description

@darklinkworker

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:

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:

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:

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

  1. darklinkworker commented on Oct 9, 2026

    @darklinkworker
    Author

    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:

    terminate called after throwing an instance of 'std::runtime_error'
      what():  AGC driver: missing static shader ABI register 200
    

    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.

  2. darklinkworker commented on Oct 9, 2026

    @darklinkworker
    Author

    Update after trying #1836, and then the video-out library that change depends on.

    #1836, applied locally on d70b8998 with 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:

    • 0x7f358bbe0000
    • 0x7f358c520000
    • 0x7f358d570000

    The 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:

    terminate called after throwing an instance of 'std::runtime_error'
      what():  AGC driver: presentation window is not attached to the device
    

    The only VulkanDevice constructor in that log was window=(nil), from shader registration. libSceVideoOut.prx in that run was still the 8 October build, from before #1367. #1367 is what calls AgcDriverAttachWindow when VideoOut starts, and Present in the rebuilt driver requires that attach. I rebuilt libSceVideoOut from current main and launched again.

    The second launch opened a window. The title read FPS: 0.00 and 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 at amdgpu_hmm_invalidate_gfx+0x38 in amdgpu, kernel 7.1.3-200.nobara.fc44. The first was kcompactd0. The next was eboot.elf inside amdgpu_gem_userptr_ioctl, through amdgpu_ttm_tt_get_user_pages and amdgpu_hmm_range_get_pages. kswapd0 and plasmashell faulted 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.

  3. darklinkworker commented on Oct 9, 2026

    @darklinkworker
    Author

    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:

    terminate called after throwing an instance of 'std::runtime_error'
      what():  AGC driver: rectangle ABI refers to a replaced shader header
    

    That throw is ResolveGraphicsAbi in ShaderRegistry.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_stall 0 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.

  4. darklinkworker commented on Oct 9, 2026

    @darklinkworker
    Author

    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:

    [gpu] layer/viewport index vertex exports are ignored (PA_CL_VS_OUT_CNTL=0x01240000)
    [videoout] presentation failed: AGC graphics: color views of several array slices are unsupported
    terminate called after throwing an instance of 'std::runtime_error'
      what():  AGC graphics: color views of several array slices are unsupported
    

    DecodeColorBuffer requires 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.

  5. darklinkworker commented on Oct 9, 2026

    @darklinkworker
    Author

    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 paClVsOutCntl field 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:

    [videoout] presentation failed: AGC driver: prepared shader artifact is missing for the requested static ABI: address=139205476803584 stage=7 wave=64 pushOffset=0 pushCapacity=104 preparedLayouts= [0,104]
    terminate called after throwing an instance of 'std::runtime_error'
      what():  AGC driver: prepared shader artifact is missing for the requested static ABI: address=139205476803584 stage=7 wave=64 pushOffset=0 pushCapacity=104 preparedLayouts= [0,104]
    

    Stage 7 is ShaderStage::Mesh. The prepared entry for that code has push layout [0,104], the same offset and size as the draw, and MatchesPreparedShader still 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 no color views of several array slices line. Available RAM stayed around 4.1 GB, swap peaked near 77 MB, and there was no kernel fault.

  6. danzaio commented on Oct 10, 2026

    @danzaio
    Contributor

    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.

  7. oneandonlydean commented on Oct 10, 2026

    @oneandonlydean
    Collaborator

    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 missing stop still happens on current main, please open it as its own issue.

  8. danzaio commented on Oct 10, 2026

    @danzaio
    Contributor

    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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions