Skip to content

fix(agc): wait for a shader's registered preparation before taking the transaction lock - #11873

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
JJenkx:fix/agc-await-preparation-outside-transaction-lock
Nov 1, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
JJenkx:fix/agc-await-preparation-outside-transaction-lock

Conversation

@JJenkx

@JJenkx JJenkx commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What

ShaderPreparationTransaction serializes its writers with one static mutex (State::writers). The first Read, Edit or Hold of a transaction takes it, and the transaction keeps it until it ends. Read and Edit then call AwaitRegisteredPreparation for the shader. That call waits, possibly for seconds, for the shader's background registration preparation when it is still pending.

A thread whose shader is still queued for preparation therefore holds the global writers mutex while it waits. Every other thread that opens a transaction queues behind it, even for shaders that are already prepared. Such threads include sceAgcCreateShader → Driver::RegisterShader → Hold, and AgcDriverResolveShaderAbi → Read.

With this change, Read and Edit first wait for the shader's registered preparation under that shader's own mutex, when the transaction does not hold the writers mutex yet. Only then do they take the writers mutex. The existing wait inside the locked section stays. It now finds the preparation settled, except when a preparation was queued again in between, in which case it waits as before. A transaction that already holds the writers mutex, at its second and later shaders, is unchanged.

Why

Echoes of the End (PPSA25636) from empty shader and pipeline caches. During loading, the registration preparation pool holds a backlog of roughly 250-300 tasks (APS5_TRACE_PREPARATION_WAIT=1).

  • An all-thread stack sample at 26 s showed 12 game threads blocked on this mutex: 4 in RegisterShader → Hold and 8 in ResolveShaderAbi → Read.

Tested

  • PreparedShaders gains PendingPreparationOutsideTransactionLock:

    • One thread opens a transaction and reads a shader whose preparation is pending.
    • A second transaction on another thread reads a prepared shader.
    • The second must finish within 2 s while the first is still waiting.
    • The pending preparation is then settled, and both transactions commit.
  • Focused Vulkan gate. Machine: Linux 7.2.9, RX 6650 XT, RADV (Mesa 26.2.4). Command: ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation VK_LOADER_DEBUG=layer ctest -V -j1 -R '^agc_driver_prepared_shaders'.

    arm head result validation layer inserted VUIDs
    main 3fea2a937ba 22/22 49 0
    this branch with ShaderRegistry.cpp restored to main (new test only) bcaac162594 18/22: agc_driver_prepared_shaders, _indirect, _uncached and _debug fail with "a transaction on a prepared shader waited for another transaction's pending registration preparation" 49 0
    this branch bcaac162594 22/22 49 0

    Logs: https://github.com/JJenkx/AnyPS5/releases/tag/transaction-lock-evidence

  • This PR makes no performance claim. The Echoes observations above (the stack sample) explain why the convoy was found; they are not a measured speed result. Echoes still crashes about 30 s into a first launch from empty caches with this change alone.

Checklist

  • Based on current main; no other open PR implements the same functions. perf(agc): wait at submit only for the preparations of the programs a submission binds #6867 also changes ShaderRegistry.cpp and PreparedShaders.cpp, in different functions: it narrows the submit-time preparation wait in Driver::Submit. The two changes are independent.
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file (no unimplemented path added)
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source (not applicable)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none claimed
  • GPUs tested: AMD Radeon RX 6650 XT, RADV Mesa 26.2.4, Linux 7.2.9
  • AI-assisted: yes

🤖 Generated with Claude Code

…e transaction lock

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The synchronization change and the new concurrency regression test look internally consistent on source review; the reported focused Vulkan gate also includes main, test-only and full-change arms with 49 layer insertions and zero VUIDs. I have not yet completed the local exact-head build/test, which is still running.\n\nOne repository requirement is not met by the report as written: CONTRIBUTING.md says a PR claiming a speed change must include valid aps5-summary.jsonl lines for main and the change from the same machine and scene, with both traces attached as described by the Performance report form. The description gives a quantified before/after compute-queue duration (1,420,772 µs versus 19,158/21,088 µs) and a dramatic per-submission improvement, but the linked release contains Vulkan test logs only; it has no summary lines or trace captures. Please add the paired performance-report artifacts, or remove the before/after speed claim and keep the report to the correctness/concurrency evidence.

@github-actions

github-actions Bot commented Oct 27, 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
PR 6188 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 6867 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 7642 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp
PR 7682 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 8592 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 9432 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 9508 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp
PR 10193 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp
PR 11442 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
PR 13928 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp
PR 13929 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp
core/libs/prx/libSceAgcDriver/tests/execution/PreparedShaders.cpp

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks, the change looks correct: no new lock-order edge, and the original regression distinguishes main from the PR. One request about the performance claim: the speed comparison comes from a private integration build, so we can't check it as it stands. Could you attach a paired same-machine, same-scene comparison (summary JSONL plus both traces) with immutable refs for the two builds? Alternatively, drop the speed comparison from the description and keep the correctness evidence, which stands on its own.

@MarcZX10

Copy link
Copy Markdown
Contributor

I downloaded and checked the three linked gate logs. The main and full-change arms both report 22/22 passing; the test-only arm reports 18/22 and fails exactly the four modes that include the new assertion (prepared_shaders, _indirect, _uncached, _debug). Each log shows the Khronos validation layer was added 294 times, no “layer not found” message, and zero VUID- matches. This is a useful negative control for the regression test.\n\nI also built agc_driver_prepared_shaders_tests from the exact PR head locally. Local CTest cannot execute it here: all 22 cases abort immediately at the project host-CPU guard (E5-1620 v2 lacks AVX2/FMA). A QEMU/llvmpipe attempt reached the test but exited on a separate guest-memory-readability failure, so I am not counting that as a pass. The reported RX 6650 XT run remains the applicable successful hardware evidence. The only requested change from my review is the repository-required paired performance report for the quantified speed claim.

@JJenkx

JJenkx commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

@oneandonlydean @MarcZX10 thanks for checking. I took the second option: the description no longer quantifies a speed change. The per-submission compute-queue timings came from a private integration tree, so they are removed, and the PR now states that it makes no performance claim. What remains is the correctness change, the concurrency regression test and its negative control (the gate MarcZX10 checked), and the stack sample that explains why the convoy was found. If I later report a measured effect, it will be a paired performance report with summary lines and traces from immutable refs.

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The requested change is addressed: the description no longer claims a measured speedup, and it now explicitly says there is no performance claim. The synchronization code and regression remain unchanged from the reviewed head. The new test checks that a transaction on a prepared shader completes while another waits on a pending registration; the linked RX 6650 XT gate logs provide main, test-only negative-control, and full-change arms. I checked those logs previously: main and full-change pass 22/22, the test-only arm fails exactly the four modes containing the regression assertion, the validation layer is inserted, and no VUID is reported.

I found no blocking source issue. My local exact-head test could not provide a GPU execution result: native CTest stops at this host's AVX2/FMA guard, and the QEMU/llvmpipe attempt failed on guest-memory readability, so I do not count either as a pass. The author-reported hardware gate is the successful runtime evidence. Hosted Windows, conventions, intake, labels, and report checks pass; the Linux product job is skipped by path policy.

@maskym

maskym commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Windows + NVIDIA result for head bcaac16259, merged into main 2ee8e9751a (the PR's GPUs tested line names no NVIDIA run on Windows).

This PR's own tests: the 26 agc_driver_prepared_shaders* tests (0.5-2.4 s each), all passed.

Host: Windows 11 26300, Intel i7-12700K, NVIDIA GeForce RTX 2070 SUPER (vendor 0x10de, device 0x1e84; Vulkan loader 1.4.357 from the NVIDIA driver), Khronos Validation Layers nightly 1.4.366 (b2b44c0d37), WinLibs GCC 15.2.0 (x86_64-ucrt-posix-seh, r6), Release, BUILD_TESTING=ON, Ninja.

Same focused run on both: VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ANYPS5_NO_SHADER_CACHE=1, ANYPS5_REQUIRE_VULKAN=1, ctest --test-dir build -R '^(agc|guest_video_out|video_out)' --parallel 4 -V, run on main 2ee8e9751a and on this tree from the same machine and GPU.

  • main: 1125/1125 passed (9 skipped, as without the layer), 146 s.
  • This tree: the same 1125 passed (6 further tests from the other PRs in the tree were not run because their executables failed to build; see those PRs), 136 s.
  • Neither log contains was not found but was requested by env var VK_INSTANCE_LAYERS.
  • Validation: both logs report exactly one VUID, VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (18 occurrences each, from agc_driver_prepared_exports and agc_driver_srgb8_color_target), which is pre-existing on main; this PR adds no new VUID- error.

The tree also carried the open PRs #11705, #11943, #11721, #11708, #11703, #11702, #11701, #11948, all merged cleanly, so the run is a joint one. Not run on a PS5.

AI-assisted: yes (Claude Code), the runs and the numbers above are from this machine.

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head bcaac16 merged onto main f6fddd6 · tested 2026-10-30 08:26 UTC · 23 min

GPU NVIDIA RTX 2000 Ada Generation, driver 595.99.02, Vulkan 1.4.329
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-38-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,261 passed · 4 failed (0 new, 2 also fail on main, 2 unstable on main) · 41 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,306 of 2,950 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (862 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_bda_device 39.5 s
Unstable on main (2, these also failed in recent main runs on this machine; not counted against this PR)
agc_submit_progress: failed in 5 of the last 7 main runs
agc_submit_progress_no_timeline: failed in 6 of the last 7 main runs
Failing on main too (2, not counted against this PR)
guest_mmap_executable
guest_socket_options_multicast
Passed on retry (1, failed in the parallel run, passed when re-run with a 360 s timeout; not counted against this PR)
agc_driver_job_manager_compute_capacity: Failed in the parallel run; also passed only on retry in 90 other runs in the last 7 days
Validation IDs already present on main (4 IDs in 391 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (391 tests): agc_compute_pipeline_replay, agc_driver, agc_driver_address_passes, agc_driver_address_passes_draw_transitions, agc_driver_address_passes_targets_split, agc_driver_alias_copy_label, agc_driver_array_layer_target_cpu, agc_driver_bda_span_reads_draw_pipeline, and 383 more
VUID-vkCmdCopyImage-srcImage-01548 (11 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_cpu_write_after_memo, agc_driver_depth_plane_cpu_write_after_page_unwatch, agc_driver_depth_plane_cpu_write_after_partial_unwatch, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_partial_store_unimported, agc_driver_depth_storage_dispatch, and 3 more
VUID-vkCmdCopyImage-srcImage-01551 (11 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_cpu_write_after_memo, agc_driver_depth_plane_cpu_write_after_page_unwatch, agc_driver_depth_plane_cpu_write_after_partial_unwatch, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_partial_store_unimported, agc_driver_depth_storage_dispatch, and 3 more
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (2 tests): agc_driver_prepared_exports, agc_driver_srgb8_color_target
Skipped (41)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_msaa_dcc_compared_import_2: skipped, the device reports host imports that keep write protection, so the write-watch probe compares none of them
agc_driver_msaa_dcc_compared_import_4: skipped, the device reports host imports that keep write protection, so the write-watch probe compares none of them
agc_driver_msaa_dcc_compared_import_8: skipped, the device reports host imports that keep write protection, so the write-watch probe compares none of them
agc_driver_sample_order_grid_reference: skipped, the device does not keep depth drawn at custom sample locations at those locations, even after a transition that names them: the composite of uniform renders holds the guest's depth in 0 of 3005 sloped samples, so no path can be compared with it bit for bit
agc_driver_sparse_dma_buf_import: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_driver_stencil_ref_export: skipped, the device lacks VK_EXT_shader_stencil_export
agc_driver_vertex_elected_atomic_max: skipped, the device runs vertex programs in subgroups, so they keep their cross-lane reductions
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_controls: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip_in_order: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop_main: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_fresh: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or plat
Reproduce
git fetch origin bcaac16259475f6eea804429fc94837df5ed8b37 f6fddd60d826831f70f7c62e25423610ee742bcc
git checkout f6fddd60d826831f70f7c62e25423610ee742bcc && git merge --no-ff bcaac16259475f6eea804429fc94837df5ed8b37
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 32 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 4 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's NVIDIA machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@oneandonlydean
oneandonlydean merged commit c090585 into boykopovar:main Nov 1, 2026
14 of 15 checks passed
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.

5 participants