Skip to content

perf(agc): present on a queue of its own outside the GPU mutex - #9670

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
winstrex:perf/agc-present-queue
Oct 25, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
winstrex:perf/agc-present-queue

Conversation

@winstrex

@winstrex winstrex commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

What

The presenter held GuestMemory::GpuMutex across vkQueuePresentKHR, which on NVIDIA in FIFO mode blocks for about 6 ms per present. It held the mutex only because the main VkQueue needs external synchronization with the recorder's submits, and the draw committer waited behind it: about 5 ms of every frame in Hades (PPSA03355).

When the presentation family has a free queue, the device now takes it as a presentation queue with its own mutex, and the presenter presents on it after releasing the GPU mutex:

  • The presentation queue is one more field of fix(agc): bind heap mirrors only where the guest holds bytes #7647's DeviceQueuePlan. When the device has a window, PlanDeviceQueues takes the next free queue of the main family after the main and settle queues (SelectPresentQueue(queueCount, mainCount)), records it as present/presentIndex and raises mainCount to cover it. The sparse queue never takes an index of the main family, so it is unchanged. That family already passed vkGetPhysicalDeviceSurfaceSupportKHR, so the swapchain images need no ownership transfer.
  • The blit is still submitted on the main queue under the GPU mutex and signals rendered[index]. The present waits on it from the other queue, and its submit comes after the blit's on the same thread, so the image is shown only once the blit and every batch before it are done.
  • WaitIdle takes the presentation queue's mutex around vkDeviceWaitIdle. The presenter never takes the GPU mutex while holding it, so the lock order is fixed.
  • Without a free queue, the present stays on the main queue under the GPU mutex, as on main.

The presenter still blocks for the same time. That block no longer stops the draw committer, and it doesn't move into the recorder's vkQueueSubmit calls.

The header comment above FlipInFlight in VulkanDevice.hpp now says where QueuePresent runs, and docs/dev/debt/libSceAgcDriver.md records that a device without a free queue in the presentation family, such as RADV, lavapipe and Intel's Windows driver, still presents under the GPU mutex.

#7647 landed first and moved this queue creation into PlanDeviceQueues. This branch is rebased onto it (main e3d03fb) and adapted to its plan. One trap in that rebase: git's automatic merge kept this PR's old presentIndex block, but placed it before PlanDeviceQueues, where the main family's queue count was still 1. The presentation queue would have taken index 1, which is the settle queue's index on a main family with two or more queues, and the plan's mainCount would then have overwritten the count it set. It would have compiled. The index is now chosen inside the plan, after the settle queue. agc_driver_device_queue_plan checks each existing case with and without a window, where the window may only add the presentation queue, plus six window cases, among them "settle on the main family, three queues, with a window" (main 3, settle 0:1, presentation 2, sparse 1:0).

Tested

Windows 11 (10.0.26300), NVIDIA GeForce RTX 5070 Ti, driver 591.86, WinLibs GCC 15.2.0 r7, Release, with Overwolf's implicit Vulkan layers disabled.

  • Hades (PPSA03355), gameplay: 34.5–34.9 FPS on main (da54c65), 41.8–42.8 FPS with this PR (7343b18). The committer's GPU mutex waits drop from 5.4 to 0.02 ms per frame. Two APS5_TRACE_SECONDS=150:10 captures per side, alternated, no APS5_PROFILE_*; the traces are in hades-traces-main-da54c652e.zip and hades-traces-pr-7343b1875.zip.

    Measurements
    main main this PR this PR
    presents in the 10 s window 349 345 418 428
    frame p50 / p99 (ms) 28.7 / 31.9 29.0 / 31.9 24.0 / 27.9 23.3 / 26.3
    GPU busy per present (ms) 1.82 1.93 1.34 1.33
    committer GpuMutex.wait.* per present (ms) 5.37 5.38 0.02 0.02
    presenter Vulkan.QueuePresent per present (ms) 6.13 6.13 6.17 6.18
    Recorder.Submit per call, mean / max (ms) 0.103 / 0.22 0.104 / 0.23 0.087 / 0.17 0.083 / 0.36

    Scripted input in the first room, warm caches, APS5_TRACE_EVENTS=1048576 so no thread dropped events, and APS5_TRACE_SUMMARY_EVERY=10.

    A swapchain of 3 images in FIFO, instead of the surface's minimum of 2, didn't help: Vulkan.QueuePresent stayed at 6.13 ms per present, and it would add up to a frame of latency.

    aps5-summary.jsonl, main first:

    {"commit":"da54c652e30b","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"150:10","APS5_TRACE_SUMMARY_EVERY":"10"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","cpu":"Genuine Intel(R) 0000","cpu_threads":24,"memory_mib":32509,"title_id":"PPSA03355","title":"Hades","content_version":"01.003.000","resolution":"1920x1080","window":{"spec":"seconds 150:10","first_present":6598,"ms":10029.556},"presents":349,"frame_ms":{"p50":28.693,"p90":30.440,"p99":31.895,"max":32.559},"gpu_busy_ms_per_present":1.816,"worker_busy_pct":{"queue worker 0x0":89.439},"top_phases_ms_per_frame":{"Driver.Packet":25.694,"Driver.Packet.draw":17.687,"Driver.Draw":17.459,"Graphics.Draw":14.332,"Driver.Draw.shader_compile_and_link":10.438,"Shader.DrawStage":8.125,"Graphics.Draw.validate":7.092,"Graphics.Draw.draw_and_resource_release":7.086},"trace_dropped":false,"trace":"aps5-trace-da54c652e30b-28664.json.gz"}
    {"commit":"da54c652e30b","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"150:10","APS5_TRACE_SUMMARY_EVERY":"10"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","cpu":"Genuine Intel(R) 0000","cpu_threads":24,"memory_mib":32509,"title_id":"PPSA03355","title":"Hades","content_version":"01.003.000","resolution":"1920x1080","window":{"spec":"seconds 150:10","first_present":6552,"ms":10004.268},"presents":345,"frame_ms":{"p50":28.995,"p90":30.616,"p99":31.877,"max":32.817},"gpu_busy_ms_per_present":1.927,"worker_busy_pct":{"queue worker 0x0":89.534},"top_phases_ms_per_frame":{"Driver.Packet":25.954,"Driver.Packet.draw":17.843,"Driver.Draw":17.618,"Graphics.Draw":14.417,"Driver.Draw.shader_compile_and_link":10.664,"Shader.DrawStage":8.301,"Graphics.Draw.draw_and_resource_release":7.142,"Graphics.Draw.validate":7.118},"trace_dropped":false,"trace":"aps5-trace-da54c652e30b-25860.json.gz"}
    {"commit":"7343b1875e60","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"150:10","APS5_TRACE_SUMMARY_EVERY":"10"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","cpu":"Genuine Intel(R) 0000","cpu_threads":24,"memory_mib":32509,"title_id":"PPSA03355","title":"Hades","content_version":"01.003.000","resolution":"1920x1080","window":{"spec":"seconds 150:10","first_present":7162,"ms":10000.579},"presents":418,"frame_ms":{"p50":24.035,"p90":25.526,"p99":27.888,"max":29.197},"gpu_busy_ms_per_present":1.335,"worker_busy_pct":{"queue worker 0x0":91.978},"top_phases_ms_per_frame":{"Driver.Packet":21.996,"Graphics.Draw":14.794,"Driver.Packet.draw":14.140,"Driver.Draw":13.915,"Driver.Draw.shader_compile_and_link":10.548,"Shader.DrawStage":8.207,"Graphics.Draw.validate":7.365,"Graphics.Draw.draw_and_resource_release":7.268},"trace_dropped":false,"trace":"aps5-trace-7343b1875e60-34216.json.gz"}
    {"commit":"7343b1875e60","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"150:10","APS5_TRACE_SUMMARY_EVERY":"10"},"gpu":"NVIDIA GeForce RTX 5070 Ti","driver":"NVIDIA 591.86","os":"Windows 10.0.26300","cpu":"Genuine Intel(R) 0000","cpu_threads":24,"memory_mib":32509,"title_id":"PPSA03355","title":"Hades","content_version":"01.003.000","resolution":"1920x1080","window":{"spec":"seconds 150:10","first_present":7193,"ms":10005.461},"presents":428,"frame_ms":{"p50":23.349,"p90":25.111,"p99":26.346,"max":27.295},"gpu_busy_ms_per_present":1.330,"worker_busy_pct":{"queue worker 0x0":91.761},"top_phases_ms_per_frame":{"Driver.Packet":21.441,"Graphics.Draw":14.395,"Driver.Packet.draw":13.867,"Driver.Draw":13.646,"Driver.Draw.shader_compile_and_link":10.396,"Shader.DrawStage":8.051,"Graphics.Draw.validate":7.135,"Graphics.Draw.draw_and_resource_release":7.104},"trace_dropped":false,"trace":"aps5-trace-7343b1875e60-7552.json.gz"}
    

    tools/trace_digest.py, main:

    **19 intervals**, 0.0 to 191.1 s, 7958 presents
    NVIDIA GeForce RTX 5070 Ti, NVIDIA 591.86, Windows 10.0.26300, 1920x1080, commit da54c652e30b
    
    | # | seconds | presents | p50 | p90 | p99 | max | GPU busy | busiest worker | largest phase |
    |---|---|---|---|---|---|---|---|---|---|
    | 1 | 0-10 | 601 | 16.7 | 16.9 | 17.1 | 17.8 | 1.2 | 92 % | VideoOut.Flip 16.5 |
    | 2 | 10-20 | 600 | 16.7 | 17.0 | 17.4 | 17.8 | 1.6 | 97 % | VideoOut.Flip 16.6 |
    | 3 | 20-30 | 600 | 16.7 | 16.9 | 17.3 | 17.8 | 1.6 | 97 % | VideoOut.Flip 16.6 |
    | 4 | 30-40 | 586 | 16.7 | 16.9 | 17.3 | 204.7 | 1.7 | 92 % | VideoOut.Flip 16.5 |
    | 5 | 40-50 | 600 | 16.7 | 16.9 | 17.1 | 17.2 | 1.6 | 97 % | VideoOut.Flip 16.6 |
    | 6 | 50-60 | 540 | 16.7 | 17.1 | 42.0 | 146.2 | 1.6 | 97 % | Driver.Packet 17.9 |
    | 7 | 60-70 | 324 | 29.9 | 35.2 | 40.0 | 51.9 | 1.9 | 90 % | Driver.Packet 27.8 |
    | 8 | 70-80 | 363 | 27.2 | 29.6 | 31.4 | 31.7 | 1.9 | 89 % | Driver.Packet 24.6 |
    | 9 | 80-90 | 359 | 27.6 | 29.6 | 30.8 | 32.0 | 1.8 | 88 % | Driver.Packet 24.6 |
    | 10 | 90-100 | 346 | 28.9 | 30.9 | 33.5 | 36.3 | 1.7 | 91 % | Driver.Packet 26.2 |
    | 11 | 100-110 | 328 | 30.3 | 32.1 | 33.2 | 41.1 | 1.9 | 93 % | Driver.Packet 28.2 |
    | 12 | 110-120 | 358 | 27.9 | 29.4 | 31.3 | 31.5 | 1.8 | 90 % | Driver.Packet 25.0 |
    | 13 | 120-130 | 338 | 29.6 | 31.7 | 33.2 | 34.1 | 1.6 | 91 % | Driver.Packet 26.9 |
    | 14 | 130-140 | 344 | 29.0 | 30.7 | 32.1 | 33.4 | 2.0 | 90 % | Driver.Packet 26.3 |
    | 15 | 140-150 | 318 | 31.6 | 33.8 | 35.7 | 38.2 | 2.0 | 92 % | Driver.Packet 29.0 |
    | 16 | 150-161 | 343 | 28.7 | 30.5 | 32.0 | 1031.0 | 1.8 | 90 % | Driver.Packet 28.6 |
    | 17 | 161-171 | 338 | 29.6 | 31.7 | 33.6 | 38.2 | 1.8 | 90 % | Driver.Packet 26.6 |
    | 18 | 171-181 | 332 | 30.2 | 31.8 | 32.8 | 35.4 | 2.0 | 92 % | Driver.Packet 27.7 |
    | 19 | 181-191 | 340 | 29.1 | 31.9 | 33.6 | 36.4 | 1.9 | 89 % | Driver.Packet 26.3 |
    
    

    p99 ms =====@@#######%#### 0-42.0
    gpu ms +#####%%%%@%#@@%%@@ 0-2.0

    tools/trace_digest.py, this PR:

    **19 intervals**, 0.0 to 191.4 s, 8778 presents
    NVIDIA GeForce RTX 5070 Ti, NVIDIA 591.86, Windows 10.0.26300, 1920x1080, commit 7343b1875e60
    
    | # | seconds | presents | p50 | p90 | p99 | max | GPU busy | busiest worker | largest phase |
    |---|---|---|---|---|---|---|---|---|---|
    | 1 | 0-10 | 601 | 16.7 | 16.9 | 17.2 | 17.4 | 1.4 | 92 % | VideoOut.Flip 16.5 |
    | 2 | 10-20 | 600 | 16.7 | 16.9 | 17.1 | 17.7 | 2.0 | 96 % | VideoOut.Flip 16.6 |
    | 3 | 20-30 | 600 | 16.7 | 17.0 | 17.3 | 17.8 | 2.0 | 96 % | VideoOut.Flip 16.6 |
    | 4 | 30-40 | 579 | 16.7 | 16.9 | 17.2 | 200.3 | 2.0 | 91 % | VideoOut.Flip 16.5 |
    | 5 | 40-50 | 600 | 16.7 | 16.9 | 17.3 | 17.7 | 1.3 | 98 % | VideoOut.Flip 16.6 |
    | 6 | 50-60 | 541 | 16.7 | 17.4 | 38.9 | 136.2 | 1.5 | 97 % | Driver.Packet 17.9 |
    | 7 | 60-70 | 381 | 25.1 | 30.7 | 36.0 | 50.4 | 1.3 | 93 % | Driver.Packet 24.4 |
    | 8 | 70-80 | 439 | 22.8 | 24.0 | 25.5 | 26.0 | 1.3 | 92 % | Driver.Packet 21.0 |
    | 9 | 80-90 | 436 | 22.8 | 24.4 | 25.5 | 26.5 | 1.3 | 92 % | Driver.Packet 21.1 |
    | 10 | 90-100 | 411 | 24.1 | 26.3 | 28.5 | 36.1 | 1.3 | 92 % | Driver.Packet 22.4 |
    | 11 | 100-110 | 384 | 26.0 | 27.6 | 28.8 | 29.8 | 1.4 | 93 % | Driver.Packet 24.2 |
    | 12 | 110-120 | 426 | 23.4 | 25.0 | 27.2 | 28.5 | 1.3 | 92 % | Driver.Packet 21.6 |
    | 13 | 120-130 | 395 | 25.3 | 27.6 | 32.7 | 37.4 | 1.4 | 92 % | Driver.Packet 23.4 |
    | 14 | 130-140 | 405 | 24.6 | 26.1 | 27.2 | 31.5 | 1.4 | 92 % | Driver.Packet 22.8 |
    | 15 | 140-150 | 370 | 27.2 | 29.5 | 32.5 | 33.3 | 1.4 | 93 % | Driver.Packet 25.1 |
    | 16 | 150-161 | 413 | 24.0 | 25.5 | 28.2 | 1300.6 | 1.3 | 93 % | Driver.Packet 25.1 |
    | 17 | 161-171 | 398 | 25.2 | 27.0 | 28.4 | 28.9 | 1.4 | 92 % | Driver.Packet 23.2 |
    | 18 | 171-181 | 397 | 25.1 | 26.7 | 28.3 | 29.3 | 1.4 | 92 % | Driver.Packet 23.3 |
    | 19 | 181-191 | 402 | 24.4 | 27.9 | 29.6 | 31.0 | 1.4 | 92 % | Driver.Packet 22.9 |
    
    

    p99 ms =====@%**##%%#### 0-38.9
    gpu ms @@@#************* 0-2.0

  • Other titles, main against this PR: The Pathless (PPSA01825) main menu goes from 32.9–33.6 to 39.5–40.0 FPS. Balatro (PPSA21402) and ANIMAL WELL (PPSA22520) stay at 60 FPS with the same frame times, and LEGO Horizon Adventures (PPSA14632) loading goes from 5.9–6.2 to 6.8–7.3 FPS. The screenshots show no tearing or broken frames.

    Measurements

    One run per side, APS5_TRACE_SUMMARY_EVERY=10, last six intervals:

    • The Pathless, main menu: 329–336 presents per 10 s, p50 26.7–26.8 ms on main; 395–400, p50 21.5–21.7 ms with this PR.
    • Balatro and ANIMAL WELL, in play: 600 presents per 10 s and p99 17.1–17.9 ms on both sides.
    • LEGO Horizon Adventures, loading after the graphics quality screen: 59–62 presents per 10 s on main, 68–73 with this PR.
  • Validation layer: the same flip and VideoOut tests pass on main and on this PR, with no VUID- errors. They ran with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ANYPS5_NO_SHADER_CACHE=1 and ANYPS5_REQUIRE_VULKAN=1.

    Tests
    • With ctest -V: agc_driver_present_mode, agc_driver_flip_compute, agc_driver_worker_failure_log, and the video_out_flip_* tests labels, labels_main, lifetime, reopen, decode, controls, overlay, unopened, ps4, eop, eop_main, eop_aftergpu, cpuflip and cpuflip_in_order. That is 17 tests per side.
    • video_out_flip_tests pacing, 3 runs per side. The pacing failure on main mentioned in feat(libSceAgcDriver): pin draw committer thread on hybrid architectures #7246 doesn't happen here.
    • Neither log has was not found but was requested by env var VK_INSTANCE_LAYERS. The branch logs Presentation queue family=0 index=2 in each test that opens a window.
  • agc_driver_present_mode also checks SelectPresentQueue, and agc_driver_device_queue_plan checks the plan with a window.

  • After the rebase onto main e3d03fb, at this head 008e327, with NVIDIA driver 610.88:

    • ctest -V -R 'flip|present|video_out|pacing|queue_plan' with the validation layer: 35 of 35 pass on main and on this head (2 skipped on both, guest_video_out_high_frame_rate and guest_video_out_wqhd_detection_no_vulkan), with no VUID- messages and no was not found but was requested by env var VK_INSTANCE_LAYERS. The branch logs Presentation queue family=0 index=2 in the 20 runs that open a window; main logs none.
    • video_out_flip_tests pacing, 3 runs per side with the validation layer: all pass, with no VUID- messages.
    • Hades, one APS5_TRACE_SECONDS=150:10 capture per side, to confirm the gain rather than remeasure it: 374 presents in the window on main and 470 on this head. The committer's GpuMutex.wait.* goes from 5.31 to 0.015 ms per present, and Vulkan.QueuePresent stays at 6.19–6.21 ms.
    • Full ctest suite with the GPU (--parallel 4 --timeout 120): 1994 of 2003 pass and 6 are skipped. The other 3 (agc_driver_sample_order, agc_driver_sample_order_grid_reference and agc_driver_sample_order_recipe_depth_locations) fail the same way on main.
  • CI on the fork, Windows only, before the rebase (run 39732177808): all 1887 tests pass. The first attempt lost guest_raise_in_system_call to a timeout. That is libkernel, which this change doesn't touch, and it also times out on GitHub's Windows runner in other PRs (e.g. fix(agc): prepare the push-less pixel variant only without fixed push slots #9563).

  • tools/check_conventions.py passes. After the comment and debt commit (4d228cd), the same 17 flip and VideoOut tests and 3 pacing runs pass again with the validation layer, with no VUID- errors.

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 (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • 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
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none; speeds up PPSA03355 (Hades) 34.5–34.9 → 41.8–42.8 FPS and PPSA01825 (The Pathless, main menu) 32.9–33.6 → 39.5–40.0 FPS
  • GPUs tested: NVIDIA GeForce RTX 5070 Ti, drivers 591.86 and 610.88, Windows 11 (10.0.26300). @BKlaasWerkman ran the flip, present, VideoOut and pacing tests on the head before this rebase, on Windows 11 with an NVIDIA GeForce RTX 3060 Ti (617.42, which takes the presentation queue) and Intel Arc Graphics on Meteor Lake (101.9150, which has one graphics queue and takes the fallback), with the validation layer and synchronization validation (comment). Not run on AMD
  • AI-assisted: yes

hades-traces-main-da54c652e.zip
hades-traces-pr-7343b1875.zip

@github-actions github-actions Bot added the perf Performance change label Oct 22, 2026
@github-actions

github-actions Bot commented Oct 22, 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
PR 4449 docs/dev/debt/libSceAgcDriver.md L133
PR 9881 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp L3783-3785

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 1395 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 2434 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 4040 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 4472 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5021 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5372 docs/dev/debt/libSceAgcDriver.md
PR 5441 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5925 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6188 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6202 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 docs/dev/debt/libSceAgcDriver.md
PR 6300 docs/dev/debt/libSceAgcDriver.md
PR 6412 docs/dev/debt/libSceAgcDriver.md
PR 6481 docs/dev/debt/libSceAgcDriver.md
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6804 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6808 docs/dev/debt/libSceAgcDriver.md
PR 6867 docs/dev/debt/libSceAgcDriver.md
PR 6960 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7201 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7256 docs/dev/debt/libSceAgcDriver.md
PR 7275 core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7369 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 7449 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7505 docs/dev/debt/libSceAgcDriver.md
PR 7604 docs/dev/debt/libSceAgcDriver.md
PR 7642 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7844 docs/dev/debt/libSceAgcDriver.md
PR 7910 docs/dev/debt/libSceAgcDriver.md
PR 7978 core/libs/prx/libSceAgcDriver/tests/Flip.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8004 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 8014 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 8223 docs/dev/debt/libSceAgcDriver.md
PR 8319 docs/dev/debt/libSceAgcDriver.md
PR 8520 docs/dev/debt/libSceAgcDriver.md
PR 8592 docs/dev/debt/libSceAgcDriver.md
PR 8874 docs/dev/debt/libSceAgcDriver.md
PR 9020 docs/dev/debt/libSceAgcDriver.md
PR 9335 docs/dev/debt/libSceAgcDriver.md
PR 9356 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9387 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9393 docs/dev/debt/libSceAgcDriver.md
PR 9400 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 9431 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9432 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9489 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 9508 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9592 docs/dev/debt/libSceAgcDriver.md
PR 9672 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9673 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9674 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9675 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9676 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9700 docs/dev/debt/libSceAgcDriver.md
PR 9748 docs/dev/debt/libSceAgcDriver.md
PR 9842 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9891 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9892 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9893 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9895 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9898 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9904 docs/dev/debt/libSceAgcDriver.md
PR 9905 docs/dev/debt/libSceAgcDriver.md
PR 9906 docs/dev/debt/libSceAgcDriver.md
PR 10049 docs/dev/debt/libSceAgcDriver.md
PR 10193 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/Driver/Presentation.cpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10233 docs/dev/debt/libSceAgcDriver.md
PR 10435 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10442 core/libs/prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp
core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 10513 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp

@root-over

Copy link
Copy Markdown
Contributor

A Linux run on RADV and lavapipe, which take the fallback path: both expose a single queue in their graphics family, so this PR can't take a presentation queue there.

Ubuntu, kernel 7.0, Wayland. Ryzen 5 5625U iGPU (Vega/GCN 5, RADV RENOIR, Mesa 26.0.8) and lavapipe (llvmpipe, LLVM 21.1.8), GCC 15.2.0, Release. I compared main d6eafa54d with the same main with head 7343b1875 merged (a clean merge), in the same build directory. Settings: ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, one ICD per run via VK_DRIVER_FILES.

  • Queue families: vulkaninfo reports queueCount = 1 for the graphics family on both drivers. RADV's other families are compute/transfer (4 queues), video and sparse only. So SelectPresentQueue finds no free queue, and no run logs Presentation queue family=. The present stays on the main queue under the GPU mutex, as described for that case.
  • Tests: ctest -R 'flip|present|video_out' -V, 36 tests. With the default ANYPS5_VIDEO_OUT_TEST_DRIVER=offscreen, 17 of the VideoOut flip tests skip (no display or Vulkan device). So I reconfigured with -DANYPS5_VIDEO_OUT_TEST_DRIVER=wayland, which makes them open a real swapchain on the desktop and go through QueuePresent. With that, on both drivers, main and this PR each pass 36/36 (one skip, video_out_telemetry_caption, on both), with no VUID- and the layer loaded.
  • Homebrew: ten catalog titles that present on this machine ran 40 s each on RADV, on the desktop, with this PR: Syobon Action, ScreenTester5, Capy Mobile, Raptor, CD Player, DriftCheck, Surround Sound Studio, GyroAim, DOOM and Prospero Vibrate. All are still running at 40 s, with the same frame dumps as on main.

So on Mesa's single-queue graphics family, this changes nothing and breaks nothing. I didn't measure frame times, since the path is the same as main's here.

@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 implementation releases GuestMemory::GpuMutex before vkQueuePresentKHR only when the device obtained a distinct queue, and keeps presentQueueMutex around both presentation and vkDeviceWaitIdle; the no-free-queue fallback stays on the main queue. The reported paired validation-layer tests, Windows GPU measurements, full fork CI result, and current hosted checks are useful. The PR is mergeable with current main.

Please resolve the remaining repository review items before merge:

  1. The overlap bot identifies open #7647 in the same queue-planning block. #7647 replaces the inline settle/sparse queue selection with PlanDeviceQueues, while this PR adds a presentation index to that selection. The PR body acknowledges whichever lands second must adapt, but it does not state an agreed merge order or list Depends on: #7647 (or the reverse). Please coordinate with that author and declare the order on the checklist as CONTRIBUTING requires.
  2. VulkanDevice.hpp still documents QueuePresent as running under GuestMemory::GpuMutex (the comment above FlipInFlight). This PR runs it outside that mutex when using the new queue. Please update the comment and record the required explanation in technical debt so the conventions check accepts the comment change.
  3. CONTRIBUTING requires the performance traces to be attached. The description names hades-traces-main-da54c652e.zip and hades-traces-pr-7343b1875.zip, but I can find only those filenames in the PR body and no attachment links in the conversation. Please attach/link both captures so the reported A/B summaries can be audited.

@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 additional Linux RADV/lavapipe report is useful: it verifies the single-queue fallback, runs the flip/VideoOut test set with a real Wayland swapchain, and reports 36/36 passing on both drivers with the validation layer loaded and no new VUIDs. That strengthens the portability evidence beyond the Windows queue path.

The three items from the earlier review still appear unresolved:

  1. Open #7647 changes the same device queue selection. I could not find an author agreement or a checklist Depends/merge-order entry. Please coordinate and record which PR lands first and what the second rebase must preserve.
  2. VulkanDevice.hpp still describes QueuePresent as running under GuestMemory::GpuMutex. The updated implementation releases that mutex when it uses the presentation queue. Please update the stale comment and include the required technical-debt change so the conventions rule accepts it.
  3. The Hades body names two trace ZIP files, but I still see no attachment links to either capture. Please attach or link both so the performance summary can be audited as CONTRIBUTING requires.

I did not rebuild locally; the author and independent reviewer have supplied Windows and Linux test results, and the current hosted product jobs are skipped under repository policy.

@winstrex

Copy link
Copy Markdown
Contributor Author

@MarcZX10 thanks. The three items:

  1. fix(agc): bind heap mirrors only where the guest holds bytes #7647: I proposed the order to its author in fix(agc): bind heap mirrors only where the guest holds bytes #7647 (comment): this PR lands first, and fix(agc): bind heap mirrors only where the guest holds bytes #7647 adds the presentation queue to its DeviceQueuePlan when it rebases. The comment lists what that rebase has to keep. I'm waiting for the author's answer. Once the order is agreed, I'll update the checklist; if the author prefers to go first, I'll rebase this PR on top of fix(agc): bind heap mirrors only where the guest holds bytes #7647.
  2. Comment and debt: 4d228cd updates the comment above FlipInFlight to say where QueuePresent runs. It also adds an entry to docs/dev/debt/libSceAgcDriver.md: a device without a free queue in the presentation family, such as RADV and lavapipe as @root-over measured, still presents under GuestMemory::GpuMutex. check_conventions.py passes. The 17 flip and VideoOut tests and 3 pacing runs pass again with the validation layer and no VUID- errors. The code is unchanged, so the measurements still apply.
  3. Traces: both are attached at the end of the description: hades-traces-main-da54c652e.zip and hades-traces-pr-7343b1875.zip.

@BKlaasWerkman

Copy link
Copy Markdown
Contributor

Windows run on NVIDIA (new path) and Intel (fallback), which the description lists as not run on Intel.

Windows 11 Pro 10.0.26300, WinLibs GCC 15.2.0 r7, Release. NVIDIA GeForce RTX 3060 Ti (driver 617.42) and Intel Arc Graphics (Meteor Lake, 0x7d55, driver 101.9150) in one machine, selected with ANYPS5_GPU. main 83462cc against main with head 4d228cd merged (clean merge). ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1, VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation (1.4.363); no was not found but was requested line.

  • Queue families: the RTX 3060 Ti's graphics family has 16 queues; all 20 swapchain runs log Presentation queue family=0 index=2. Intel Arc's graphics family has queueCount = 1 on the Windows driver, so it takes the fallback like RADV and lavapipe, and no run logs Presentation queue family=. The debt entry could name Intel's Windows driver too.
  • Tests: ctest -R 'flip|present|video_out|pacing' -V, 34 tests. On Windows, the VideoOut flip tests use the windows SDL driver by default and create 20 real swapchains per run. main and this PR: 34/34 on both GPUs, with one skip on both sides (guest_video_out_wqhd_detection_no_vulkan), and no VUID-.
  • Synchronization validation (VK_KHRONOS_VALIDATION_VALIDATE_SYNC=true), same 34 tests, --repeat until-fail:3: 34/34 on both sides and both GPUs. The only hazard is SYNC-HAZARD-WRITE-RACING-READ ×3 in agc_driver_flip_stores_small_results, identical on main and this PR on both GPUs: the deferred settle copy that fix(agc): order deferred import settles with main-queue accesses #8527 addresses. The PR adds no hazard on the presentation queue.

No performance measurement here.

AI-assisted: yes (Claude Code)

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
winstrex and others added 2 commits October 23, 2026 09:50
vkQueuePresentKHR blocks the presenter for a frame's pacing on some
drivers (about 6 ms per present on NVIDIA in FIFO mode), and it ran under
GuestMemory::GpuMutex because the main queue needs external
synchronization with the recorder's submits. When the presentation
family has a free queue, the device now takes it as a presentation queue
guarded by its own mutex, and the presenter hands the image to it after
releasing the GPU mutex. WaitIdle takes that mutex around
vkDeviceWaitIdle. Without a free queue the present stays on the main
queue under the GPU mutex as before.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@winstrex
winstrex force-pushed the perf/agc-present-queue branch from 4d228cd to 008e327 Compare October 23, 2026 13:15
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@winstrex

Copy link
Copy Markdown
Contributor Author

Rebased onto main e3d03fb now that #7647 has landed; the head is 008e327. The presentation queue is now one more field of #7647's DeviceQueuePlan (present/presentIndex). PlanDeviceQueues picks it after the main and settle queues, and the sparse queue is unchanged. agc_driver_device_queue_plan covers it with and without a window.

  • The flip, present, VideoOut, pacing and queue plan tests pass with the validation layer on main and on the head (35 each), with no VUID- messages. The head logs Presentation queue family=0 index=2.
  • One Hades capture per side confirms the gain: 374 → 470 presents per 10 s.

@BKlaasWerkman thanks for the runs on the RTX 3060 Ti and Intel Arc. They're cited in the description, and the debt entry now names Intel's Windows driver next to RADV and lavapipe.

@adriadam10 adriadam10 self-assigned this Oct 23, 2026
@adriadam10
adriadam10 dismissed MarcZX10’s stale review October 24, 2026 10:20

Dismissing a stale review: the requested changes were addressed after it (new head and/or updated description with the asked-for runs) and the merge queue blocks on it. Please request changes again if anything is still open.

@adriadam10
adriadam10 merged commit f4f1160 into boykopovar:main Oct 25, 2026
12 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the presentation queue in DeviceQueuePlan (chosen after the main and settle queues, sparse queue unchanged), the lock order (WaitIdle takes the presentation queue mutex; the presenter never takes the GPU mutex while holding it), the blit/present ordering through rendered[index], the single-queue fallback, the window cases in agc_driver_device_queue_plan and the debt entry. Recent main changes to VulkanDevice.cpp are in unrelated hunks. The earlier change request is dismissed; merged.

@winstrex
winstrex deleted the perf/agc-present-queue branch October 25, 2026 13:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs perf Performance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants