Skip to content

fix(agc): hash an idle unit when its range leaves the write watch instead of at every upload - #10026

Merged
oneandonlydean merged 25 commits into
boykopovar:mainfrom
winstrex:fix/agc-unwatch-baseline
Nov 2, 2026
Merged

oneandonlydean merged 25 commits into
boykopovar:mainfrom
winstrex:fix/agc-unwatch-baseline

Conversation

@winstrex

@winstrex winstrex commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

This PR's own commits, without #7048's below them: ec6ceece9, f27fcde50 and d73e64951.

What

#7048 stores results that land in a unit over memory that has left the write watch, as long as the unit's bytes still match what was uploaded. For surfaces over 1 MiB it checks this against a per-unit hash, which upload() takes on every settled upload. That pass reads and hashes the whole surface each time. On The Pathless (PPSA01825) menu it costs about 70 % of the presents, with 89 % of the draw committer's samples in StorageTexture::upload. The hash is only read in markLayersPending, for a unit with a write generation, no saved bytes and a range that is no longer watched.

This PR takes that hash once, when it can be trusted and is about to be needed: when the unit's range leaves the watch. This is the shape @oneandonlydean described in #7048. The first commits here are #7048's own, followed by a merge with main that resolves #7048's conflict with it (described below). This PR's change is the next commit, ec6ceec. A second merge with main on top of it adapts that change to main's tracker lock, f27fcde makes the refusal name its cause, d73e649 corrects the debt entry, and a third merge with main (2853a75c1) resolves a conflict with #9159 (all below).

  • The unwatch hook (StorageTexture::SaveBeforeUnwatch) also visits the live textures over 1 MiB that overlap the range. For each unit there with a write generation, no pending results and no saved bytes, it collects the range's writes. If the watch shows no write since that generation, it records a 128-bit hash of the unit's bytes together with that generation. upload() and refresh no longer hash anything.
  • markLayersPending captures such a unit only while its bytes still match the hash and the hash's generation is still the unit's. With no hash, or a mismatch, it refuses, as it does now. Surfaces of 1 MiB or less keep comparing with the copy taken at the upload, so the hook doesn't visit them.
  • The hook doesn't flush pending GPU writes before hashing. A GPU write that lands later changes the bytes, so the hash no longer matches and the capture is refused.
  • ImportWatched now runs the hook over the two spans of an import that stays watched which left the watch without it: a span whose page walk fails before the import (the hook runs before the import) and one whose walk fails after it. These are the spans fix(agc): run the unwatch hook before an import whose range leaves the write watch whatever the import does #7505 covers. Whichever of the two lands second drops its GuestMemory.cpp hunk.
    • After @ahmadmuj's review (43a9245e8): the hooks of the spans whose walk fails after the import run after every span's window walk, under one suspension of the tracker lock. Running each hook inside the loop let another thread collect a later span without its window, and stamp the pages the import touched as CPU writes. A guard resumes the lock even if a hook throws.
  • Where the CPU's writes to a range can't be proven as it leaves the watch, the hook still saves pending results but records no hash: those two spans, and a failed fresh collect in MarkWritten (UnwatchWritesProven). A unit there has no baseline, and its capture is refused, as before.
  • Ranges that leave the watch without going through the hook are unchanged: GuestWriteWatchUnregister called from libkernel on Linux when a mapping is aliased, unmapped or replaced. On Windows there are none: the host regions are fixed when the guest arena is reserved, the commit generation only decides when a later commit restores the watch, and every path that takes a range out of the watch runs the hook (d73e649 drops the Windows case I had listed). Taking the Linux case through the hook touches libkernel and is left for a separate PR. The new debt entry "Units without a baseline outside the write watch" lists them. A unit there has no hash, so results that land in it outside the watch throw when they are stored, as on main.
  • SetImportWalkFailureForTest lets the tests fail either walk on both platforms.

The refusal names its cause (f27fcde, after @zaidhawk's Persona 3 Reload run below). What is captured and what is refused don't change. The refusal now ends with the first unit refused and why it has no baseline, for example (unit 63 of 64: its range left the watch through the unwatch hook without its CPU writes proven (a failed collect or import page walk)). The causes:

  • its results became pending while its range was watched, and its bytes weren't saved when the range left the watch;
  • a save of its bytes was in flight when its results became pending and didn't complete, or its saved bytes were dropped while its results were pending;
  • on surfaces of 1 MiB or less: no copy was taken at the upload, or the bytes differ from it;
  • on larger surfaces: no hash for the unit's current generation (its range left the watch without the hook, or its generation changed after the hash), the hook ran without the CPU writes proven, the watch saw CPU writes after the unit's generation, the unit had pending results or saved bytes when the hook ran, or the bytes no longer match the hash.

The hook records why it didn't hash a unit next to the hash, with the generation it applies to. To record the unproven case it now also visits the live textures over 1 MiB when the CPU writes aren't proven. That path takes GpuMutex when one of them overlaps, and it only runs after a failed collect or a failed import page walk.

A wait I found and fixed while measuring. In the first version, the hook took GuestMemory::GpuMutex whenever the range overlapped any live texture. On main it only takes it when a texture with pending results overlaps. During The Pathless's menu that happened hundreds of times, and the thread doing the unwatch waited for the draw committer to release the mutex: 296–316 presents per 10 s against main's 315–336. The hook's own code didn't show in the samples, because the cost was the wait. The hook now visits only textures over 1 MiB, the same limit as #7048's hash. Those are the only ones a hash is ever taken for, so in The Pathless it takes the mutex no more often than main does.

What the hook costs. A local counter that is not part of this PR, over the menu of each title:

  • The Pathless: about 1,800 unwatch events per second while it boots and 44–112 per second in the menu. LEGO Horizon Adventures (PPSA14632): 19 in 4 minutes. Hades (PPSA03355): none.
  • All of them went through the hook. The two ImportWatched exits were never reached.
  • None found an idle unit to hash, so the hook read 0 bytes in all three titles. On these titles the hash path itself is exercised only by the tests below. @zaidhawk (Persona 3 Reload, PPSA10872), @dantdmdarealguy (Stray, PPSA02100) and @oneandonlydean (ASTRO BOT), could you run this head on the scene where you measured fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048's slowdown? Those are the titles where a unit is expected to lose the watch while idle. I don't have Persona 3 Reload, so it wasn't tested here.

Merge with main. Since c0dbd18 (perf(agc): draw finisher pipeline), #7048 conflicts with main in StorageTexture::markLayersPending: main reworked the loop that #7048 changes. Until #7048 is rebased, this branch carries a merge of #7048's head da9f0ea with main e3d03fb (93c878b):

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. All three builds are on main 83462cc: main itself, main merged with #7048's head da9f0ea (e2c1fe440), and main merged with this PR's head (4989cab12). The merges are local and clean.

  • The Pathless (PPSA01825), main menu: main 319–326 presents per 10 s, main + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 85–105, main + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 + this PR 307–316. Two APS5_TRACE_SECONDS=120:10 captures per build, alternated, no APS5_PROFILE_*.

    Measurements
    main main + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 + this PR + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 + this PR
    presents in the 10 s window 319 330 104 85 304 316
    mean of the menu's 10 s intervals 319 326 104 88 307 316
    frame p50 / p99 in the window (ms) 27.7 / 64.6 27.6 / 59.7 110.2 / 127.9 130.3 / 175.1 28.2 / 71.7 27.8 / 65.3
    GPU busy per present (ms) 17.60 17.48 18.59 20.65 18.20 17.64
    committer Graphics.Draw per frame (ms) 15.1 14.6 77.4 96.2 16.3 15.5

    Isolation: fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 with its upload hash turned off locally and without this PR's hook (hashUnits = false, a dirty local tree, alternated with two more main runs) gives 314 and 316 presents on average (windows 316 and 328), against 321 and 322 for main (windows 319 and 319). So the 2–4 % left between main and the two PRs together comes from the rest of fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048, not from the hook.

    APS5_PLAYGO_UNLISTED_CHUNKS=absent, warm caches, APS5_TRACE_EVENTS=1048576 so no thread dropped events, and APS5_TRACE_SUMMARY_EVERY=10. The traces are in pathless-traces-main-83462cc57.zip, pathless-traces-main-7048-e2c1fe440.zip, pathless-traces-pr-4989cab12.zip and pathless-traces-isolation-7048-no-upload-hash.zip.

    aps5-summary.jsonl, main, main + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048, then main + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 + this PR:

    {"commit":"83462cc57dde","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_PLAYGO_UNLISTED_CHUNKS":"absent","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"120: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":"PPSA01825","title":"The Pathless","content_version":"02.000.000","resolution":"2560x1440","window":{"spec":"seconds 120:10","first_present":4146,"ms":10018.488},"presents":319,"frame_ms":{"p50":27.729,"p90":59.545,"p99":64.626,"max":73.425},"gpu_busy_ms_per_present":17.596,"worker_busy_pct":{"queue worker 0x0":92.006,"queue worker 0x20":10.334},"top_phases_ms_per_frame":{"Driver.Packet":31.437,"Graphics.Draw":15.107,"Driver.Packet.device_lock":14.321,"Graphics.Draw.draw_and_resource_release":13.759,"Driver.Packet.draw":6.495,"Driver.Draw":6.421,"Resources.Complete":4.895,"Driver.Draw.shader_compile_and_link":4.765},"trace_dropped":false,"trace":"aps5-trace-83462cc57dde-29980.json.gz"}
    {"commit":"83462cc57dde","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_PLAYGO_UNLISTED_CHUNKS":"absent","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"120: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":"PPSA01825","title":"The Pathless","content_version":"02.000.000","resolution":"2560x1440","window":{"spec":"seconds 120:10","first_present":4187,"ms":10020.868},"presents":330,"frame_ms":{"p50":27.649,"p90":54.126,"p99":59.696,"max":60.637},"gpu_busy_ms_per_present":17.480,"worker_busy_pct":{"queue worker 0x0":91.835,"queue worker 0x20":10.195},"top_phases_ms_per_frame":{"Driver.Packet":30.406,"Graphics.Draw":14.573,"Driver.Packet.device_lock":13.607,"Graphics.Draw.draw_and_resource_release":13.237,"Driver.Packet.draw":6.545,"Driver.Draw":6.470,"Driver.Draw.shader_compile_and_link":4.702,"Resources.Complete":4.549},"trace_dropped":false,"trace":"aps5-trace-83462cc57dde-33460.json.gz"}
    {"commit":"e2c1fe440ccd","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_PLAYGO_UNLISTED_CHUNKS":"absent","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"120: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":"PPSA01825","title":"The Pathless","content_version":"02.000.000","resolution":"2560x1440","window":{"spec":"seconds 120:10","first_present":1997,"ms":10040.790},"presents":104,"frame_ms":{"p50":110.226,"p90":120.240,"p99":127.892,"max":135.139},"gpu_busy_ms_per_present":18.587,"worker_busy_pct":{"queue worker 0x0":96.081,"queue worker 0x20":43.082},"top_phases_ms_per_frame":{"Driver.Packet":130.972,"Graphics.Draw":77.392,"Graphics.Draw.draw_and_resource_release":76.029,"Driver.Packet.draw":48.377,"Driver.Draw":48.303,"Driver.Draw.draw_enqueued":42.480,"Driver.Packet.dispatch":40.343,"Driver.Dispatch":40.177},"trace_dropped":false,"trace":"aps5-trace-e2c1fe440ccd-10948.json.gz"}
    {"commit":"e2c1fe440ccd","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_PLAYGO_UNLISTED_CHUNKS":"absent","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"120: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":"PPSA01825","title":"The Pathless","content_version":"02.000.000","resolution":"2560x1440","window":{"spec":"seconds 120:10","first_present":1841,"ms":10038.742},"presents":85,"frame_ms":{"p50":130.344,"p90":140.557,"p99":175.127,"max":175.127},"gpu_busy_ms_per_present":20.646,"worker_busy_pct":{"queue worker 0x0":95.237,"queue worker 0x20":42.248},"top_phases_ms_per_frame":{"Driver.Packet":155.389,"Graphics.Draw":96.209,"Graphics.Draw.draw_and_resource_release":94.571,"Driver.Packet.draw":55.405,"Driver.Draw":55.314,"Driver.Draw.draw_enqueued":48.652,"Driver.Packet.device_lock":47.731,"Driver.Packet.dispatch":45.805},"trace_dropped":false,"trace":"aps5-trace-e2c1fe440ccd-14240.json.gz"}
    {"commit":"4989cab1235e","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_PLAYGO_UNLISTED_CHUNKS":"absent","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"120: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":"PPSA01825","title":"The Pathless","content_version":"02.000.000","resolution":"2560x1440","window":{"spec":"seconds 120:10","first_present":3973,"ms":10026.852},"presents":304,"frame_ms":{"p50":28.193,"p90":64.734,"p99":71.709,"max":71.988},"gpu_busy_ms_per_present":18.203,"worker_busy_pct":{"queue worker 0x0":91.923,"queue worker 0x20":10.543},"top_phases_ms_per_frame":{"Driver.Packet":32.941,"Graphics.Draw":16.282,"Driver.Packet.device_lock":15.617,"Graphics.Draw.draw_and_resource_release":14.918,"Driver.Packet.draw":6.533,"Driver.Draw":6.461,"Resources.Complete":5.824,"VideoOut.Flip":5.168},"trace_dropped":false,"trace":"aps5-trace-4989cab1235e-38540.json.gz"}
    {"commit":"4989cab1235e","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_PLAYGO_UNLISTED_CHUNKS":"absent","APS5_TRACE_DIR":"<path>","APS5_TRACE_EVENTS":"1048576","APS5_TRACE_SECONDS":"120: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":"PPSA01825","title":"The Pathless","content_version":"02.000.000","resolution":"2560x1440","window":{"spec":"seconds 120:10","first_present":4098,"ms":10021.926},"presents":316,"frame_ms":{"p50":27.804,"p90":60.844,"p99":65.277,"max":67.448},"gpu_busy_ms_per_present":17.637,"worker_busy_pct":{"queue worker 0x20":12.187,"queue worker 0x0":91.937},"top_phases_ms_per_frame":{"Driver.Packet":32.360,"Graphics.Draw":15.507,"Driver.Packet.device_lock":14.930,"Graphics.Draw.draw_and_resource_release":14.196,"Driver.Packet.draw":6.531,"Driver.Draw":6.459,"Driver.Packet.dispatch":5.332,"Resources.Complete":5.249},"trace_dropped":false,"trace":"aps5-trace-4989cab1235e-42320.json.gz"}
    

    tools/trace_digest.py, main:

    **16 intervals**, 0.0 to 160.7 s, 5420 presents
    NVIDIA GeForce RTX 5070 Ti, NVIDIA 591.86, Windows 10.0.26300, 3840x2160, commit 83462cc57dde
    commit, environment, GPU, driver or OS changed at interval 2
    
    | # | seconds | presents | p50 | p90 | p99 | max | GPU busy | busiest worker | largest phase |
    |---|---|---|---|---|---|---|---|---|---|
    | 1 | 0-10 | 547 | 16.7 | 17.0 | 38.8 | 518.4 | 0.7 | 15 % | VideoOut.Flip 11.4 |
    | 2 | 10-20 | 454 | 16.7 | 16.9 | 193.1 | 1448.0 | 1.2 | 92 % | Driver.Packet 21.4 |
    | 3 | 20-30 | 259 | 27.9 | 55.9 | 228.4 | 454.0 | 18.1 | 93 % | Driver.Packet 40.8 |
    | 4 | 30-40 | 329 | 27.5 | 53.1 | 59.5 | 60.6 | 17.6 | 92 % | Driver.Packet 30.6 |
    | 5 | 40-50 | 331 | 27.6 | 53.1 | 56.6 | 58.5 | 17.7 | 93 % | Driver.Packet 30.6 |
    | 6 | 50-60 | 329 | 27.6 | 53.6 | 58.3 | 60.7 | 17.7 | 92 % | Driver.Packet 30.5 |
    | 7 | 60-70 | 326 | 27.8 | 53.5 | 60.8 | 61.3 | 17.7 | 92 % | Driver.Packet 31.1 |
    | 8 | 70-80 | 317 | 27.8 | 60.4 | 64.5 | 68.3 | 17.7 | 93 % | Driver.Packet 31.7 |
    | 9 | 80-90 | 315 | 27.9 | 60.5 | 67.2 | 73.4 | 17.6 | 92 % | Driver.Packet 31.9 |
    | 10 | 90-100 | 317 | 27.9 | 60.5 | 67.8 | 72.4 | 17.6 | 92 % | Driver.Packet 31.6 |
    | 11 | 100-110 | 315 | 27.7 | 62.2 | 66.8 | 70.0 | 17.6 | 92 % | Driver.Packet 32.2 |
    | 12 | 110-120 | 318 | 27.9 | 59.5 | 67.2 | 69.2 | 17.6 | 92 % | Driver.Packet 31.6 |
    | 13 | 120-131 | 309 | 27.7 | 59.6 | 67.2 | 653.6 | 17.6 | 92 % | Driver.Packet 33.4 |
    | 14 | 131-141 | 317 | 27.8 | 60.0 | 65.0 | 66.2 | 17.7 | 92 % | Driver.Packet 32.3 |
    | 15 | 141-151 | 318 | 27.8 | 59.8 | 66.6 | 71.4 | 17.6 | 92 % | Driver.Packet 31.1 |
    | 16 | 151-161 | 319 | 27.7 | 60.3 | 65.6 | 72.0 | 17.6 | 92 % | Driver.Packet 31.7 |
    
    

    p99 ms :%@::::--------- 0-228.4
    gpu ms ._@@@@@@@@@@@@@@ 0-18.1

    tools/trace_digest.py, main + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048:

    **14 intervals**, 0.0 to 141.0 s, 2215 presents
    NVIDIA GeForce RTX 5070 Ti, NVIDIA 591.86, Windows 10.0.26300, 3840x2160, commit e2c1fe440ccd
    commit, environment, GPU, driver or OS changed at interval 2
    
    | # | seconds | presents | p50 | p90 | p99 | max | GPU busy | busiest worker | largest phase |
    |---|---|---|---|---|---|---|---|---|---|
    | 1 | 0-10 | 541 | 16.7 | 18.8 | 36.1 | 500.6 | 0.7 | 30 % | VideoOut.Flip 8.7 |
    | 2 | 10-20 | 453 | 16.7 | 17.0 | 17.4 | 1341.9 | 1.1 | 94 % | Driver.Packet 22.7 |
    | 3 | 20-30 | 82 | 113.2 | 185.0 | 488.5 | 488.5 | 20.4 | 96 % | Driver.Packet 167.4 |
    | 4 | 30-40 | 103 | 110.5 | 121.0 | 126.0 | 126.4 | 18.4 | 96 % | Driver.Packet 138.4 |
    | 5 | 40-51 | 102 | 115.4 | 122.1 | 131.0 | 131.5 | 18.8 | 97 % | Driver.Packet 138.1 |
    | 6 | 51-61 | 102 | 115.9 | 122.0 | 127.3 | 128.5 | 18.7 | 96 % | Driver.Packet 138.3 |
    | 7 | 61-71 | 103 | 113.7 | 121.1 | 123.3 | 126.8 | 18.4 | 96 % | Driver.Packet 136.9 |
    | 8 | 71-81 | 102 | 116.7 | 121.8 | 128.7 | 139.8 | 18.7 | 96 % | Driver.Packet 135.9 |
    | 9 | 81-91 | 105 | 110.2 | 120.5 | 123.3 | 125.1 | 18.4 | 95 % | Driver.Packet 134.2 |
    | 10 | 91-101 | 105 | 109.5 | 119.6 | 123.0 | 125.2 | 18.8 | 96 % | Driver.Packet 133.2 |
    | 11 | 101-111 | 105 | 80.3 | 119.8 | 122.5 | 122.9 | 18.2 | 96 % | Driver.Packet 129.8 |
    | 12 | 111-121 | 103 | 110.6 | 120.3 | 128.5 | 130.3 | 18.5 | 95 % | Driver.Packet 133.0 |
    | 13 | 121-131 | 104 | 109.8 | 120.5 | 135.1 | 215.6 | 18.6 | 96 % | Driver.Packet 131.0 |
    | 14 | 131-141 | 105 | 110.4 | 119.4 | 123.8 | 125.0 | 18.7 | 95 % | Driver.Packet 128.4 |
    
    

    p99 ms _.@::::::::::: 0-488.5
    gpu ms ..@%%%%%%%%%%% 0-20.4

    tools/trace_digest.py, main + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 + this PR:

    **15 intervals**, 0.0 to 150.7 s, 4900 presents
    NVIDIA GeForce RTX 5070 Ti, NVIDIA 591.86, Windows 10.0.26300, 3840x2160, commit 4989cab1235e
    commit, environment, GPU, driver or OS changed at interval 2
    
    | # | seconds | presents | p50 | p90 | p99 | max | GPU busy | busiest worker | largest phase |
    |---|---|---|---|---|---|---|---|---|---|
    | 1 | 0-10 | 538 | 16.7 | 17.0 | 45.0 | 567.6 | 0.7 | 16 % | VideoOut.Flip 13.1 |
    | 2 | 10-20 | 446 | 16.7 | 16.9 | 191.5 | 1366.8 | 1.0 | 88 % | Driver.Packet 21.6 |
    | 3 | 20-30 | 232 | 28.1 | 67.9 | 173.7 | 1038.1 | 19.2 | 92 % | Driver.Packet 47.2 |
    | 4 | 30-40 | 308 | 27.8 | 63.9 | 71.2 | 73.7 | 18.1 | 92 % | Driver.Packet 33.2 |
    | 5 | 40-50 | 304 | 28.0 | 65.6 | 71.4 | 73.0 | 18.2 | 92 % | Driver.Packet 33.3 |
    | 6 | 50-60 | 310 | 28.0 | 61.7 | 67.4 | 70.2 | 18.1 | 92 % | Driver.Packet 32.0 |
    | 7 | 60-70 | 307 | 28.0 | 63.4 | 70.4 | 73.3 | 18.1 | 92 % | Driver.Packet 32.9 |
    | 8 | 70-80 | 307 | 27.9 | 64.4 | 70.6 | 71.9 | 18.2 | 92 % | Driver.Packet 32.6 |
    | 9 | 80-90 | 310 | 27.9 | 62.3 | 68.3 | 68.9 | 18.2 | 92 % | Driver.Packet 32.8 |
    | 10 | 90-101 | 308 | 27.9 | 62.5 | 71.5 | 73.8 | 18.1 | 92 % | Driver.Packet 32.9 |
    | 11 | 101-111 | 307 | 27.9 | 62.8 | 69.1 | 79.4 | 18.1 | 92 % | Driver.Packet 32.9 |
    | 12 | 111-121 | 312 | 27.7 | 62.1 | 67.7 | 71.6 | 18.2 | 92 % | Driver.Packet 32.9 |
    | 13 | 121-131 | 289 | 28.2 | 64.8 | 71.9 | 634.7 | 18.2 | 93 % | Driver.Packet 35.2 |
    | 14 | 131-141 | 311 | 28.0 | 61.4 | 66.8 | 68.9 | 18.1 | 92 % | Driver.Packet 32.6 |
    | 15 | 141-151 | 311 | 28.0 | 61.3 | 69.2 | 71.4 | 18.1 | 92 % | Driver.Packet 32.8 |
    
    

    p99 ms :@%------------ 0-191.5
    gpu ms ..@%@%@@@@@@@@@ 0-19.2

  • Palworld (PPSA23126): abort at compute shader 0x15c1486e00, "storing storage image results pending over memory outside the write watch without the guest bytes saved before them is not implemented" #7017's refusal on The Pathless: 60 s runs, alternated. main stopped on storing storage image results pending over memory outside the write watch without the guest bytes saved before them is not implemented in 3 of 8 runs, 8 to 10 s in. main + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 + this PR stopped in none: 0 of 7 runs with the hook's first version (before the 1 MiB limit) and 0 of 6 with this head.

  • LEGO Horizon Adventures (PPSA14632) and Hades (PPSA03355): one run per side, main against main + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 + this PR. LEGO's loading screen gives 57–65 presents per 10 s against 63–65, and its menu 29–33 against 30–32. Hades gameplay gives 295–330 against 296–336. No refusal in any run. These two ran with the hook's first version; the 1 MiB limit only narrows the textures it visits.

  • Persona 3 Reload (PPSA10872): I don't have it. @zaidhawk ran df1b2d5 on an RTX 5090 (comment): the start-up screens and the opening animation are back at main's rate (596–600 presents per 10 s, against 117–122 with fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 alone), but 1 of 5 runs stopped on the refusal, which fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 alone didn't in 5. Its log doesn't say which case it was; f27fcde adds that to the message. They ran d73e649 again with 120 s runs (comment): 0 of 10 runs stopped on the refusal, against 2 of 5 on main 9d164f0 and 0 of 5 with main + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048, so there is no cause line yet. Over both sessions this PR stopped in 1 of 15 runs, fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 alone in 0 of 10 and main in 4 of 10. The start-up screens stayed at 594–596 presents per 10 s with this PR, against 120–122 with fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048.

  • Tests:

    • agc_storage_pending_after_unwatch checks the cause named in every refusal, with three new cases: the last block leaves the watch without the hook (refused at 1024×1024 for having no hash; stored at 256×256, which compares with the upload copy), the same while the results are pending (refused in both sizes), and a failed collect, on Windows (refused at 1024×1024 as unproven; stored at 256×256). Recording the wrong cause in the hook makes it fail.
    • agc_storage_pending_after_unwatch passes in both sizes (256×256 through the upload copy, 1024×1024 through the hash). New case: a CPU write into the last block while it is still watched, before it leaves the watch. The unit then has no baseline, and the capture is refused in both sizes with the CPU's bytes kept. The existing case with a CPU write after the unwatch, which is after the baseline, is still refused at 1024×1024.
    • agc_write_tracking: CheckImportWalkUnwatchHooks reaches both ImportWatched exits through SetImportWalkFailureForTest. It checks that each runs the hook once while its block is still watched, before the import for the first exit and after it for the second, and with the CPU writes reported as not proven. It also checks that Unwatch reports them as proven. CheckFailedCollectUnwatch now also checks that a failed collect reports them as not proven. CheckImportWalkHookKeepsLaterWindows fails the walk of only the first of two spans after the import, and its unwatch hook collects the second span while the lock is suspended, as another thread could. It checks that a page the import touched there without changing it still reads as unchanged, with its window walk's possible CPU touch. It fails on b61555e and passes from 43a9245.
    • Mutation check, redone at 660cef6fe: 11 changes to the new code, one at a time, each rebuilt and run against these tests. 10 make a test fail. The one that doesn't is skipping the generation check in baselineMiss. A unit whose content changes through this emulator either has its hash forgotten (write-back, clear, alias borrow) or no longer matches it, so that check only refuses earlier.
    • At @oneandonlydean's request (comment), agc_storage_pending_after_unwatch no longer requires any case to be refused, so an implementation that stores these units safely still passes. It checks the cause named in a refusal, that a refused write-back leaves the CPU's bytes as they were, and that a store merges the GPU's texels with the CPU's bytes. Four new cases write the CPU's bytes into the last unit after GPU results that cover part of it were written: into the hashed unit, before the unwatch, after an unwatch without the hook, and before an unwatch through a failed collect. Accepting any of them would store the image's older bytes over the CPU's, and the byte checks catch it. Ignoring the hash or the upload copy, hashing a unit the watch saw written, or taking a missing hash for a baseline now fails on the bytes. Hashing after a failed collect is caught by the cause instead: the next collect still sees the CPU's write, so the hook refuses the unit for that write. A write the failed collect loses for good, which the test can't produce, is the case where it would store the wrong bytes.
  • Validation layer: the write-watch, storage image, import, draw, recorder and VideoOut tests pass on main 83462cc (59) and on main + fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048 + this PR (60, agc_storage_pending_after_unwatch included), with no VUID- messages on either side. They ran with VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ANYPS5_NO_SHADER_CACHE=1 and ANYPS5_REQUIRE_VULKAN=1, and neither log has was not found but was requested by env var VK_INSTANCE_LAYERS. The set is ctest -V -R '^(agc_write_tracking.*|agc_storage_.*|agc_import_.*|agc_driver_store_over_untracked.*|agc_unit_shadow|agc_metadata_pass|agc_slice_dirty|agc_resident_present|agc_tiled_resolve|agc_queued_write_groups|agc_driver_graphics|agc_draw_.*|agc_recorder_.*|agc_driver_flip.*|video_out_flip_.*)$'.

  • After the merge with main e3d03fb, at ec6ceec, with NVIDIA driver 610.88:

    • The validation set above passes on main e3d03fb (59) and on this head (60), with APS5_DRAW_PIPELINE unset and set, with no VUID- messages on either side.
    • The Pathless (PPSA01825), one APS5_TRACE_SECONDS=120:10 control run per side: the menu's 10 s intervals give 327–330 presents on main and 324–329 on this head.
  • After the second merge, with main 9d164f0, at df1b2d5, with NVIDIA driver 610.88:

    • The validation set above passes on main 9d164f0 (59) and on this head (60), with APS5_DRAW_PIPELINE unset and set, with no VUID- messages on either side.
    • The Pathless (PPSA01825), two APS5_TRACE_SECONDS=120:10 control runs per side, in alternated order: the menu's 10 s intervals give 303–313 and 299–318 presents on main, and 293–307 and 302–320 on this head.
  • After f27fcde, with NVIDIA driver 610.88 (d73e649 above it only changes the debt entry):

    • The validation set above passes at f27fcde (60) with APS5_DRAW_PIPELINE unset and set, with no VUID- messages; main 9d164f0 passes its 59 the same way (above).
    • The Pathless (PPSA01825), two APS5_TRACE_SECONDS=120:10 control runs per side, alternated with main 9d164f0: the menu's 10 s intervals give 290–305 and 287–321 presents at f27fcde (one interval of 260 in the second run), and 289–313 and 281–304 on main. No refusal in any run.
  • After the third merge, with main cf528e7, at 2853a75, with NVIDIA driver 610.88:

    • The validation set above, plus fix(agc): keep a rewatched import's memory compared once it is shared #9159's new tests (agc_rewatched_.*, agc_compared_.*, agc_driver_compared_.* and agc_driver_partially_resident.*), passes on main (70) and on this head (71), with APS5_DRAW_PIPELINE unset and set, with no VUID- messages on either side.
    • The Pathless (PPSA01825), APS5_TRACE_SECONDS=120:10 control runs alternated with main cf528e7: the menu's 10 s intervals from 20 s on give 332–345 and 308–360 presents on this head, and 342–358 and 351–363 on main. main stopped on the refusal in 1 of its 3 runs, and this head in none of its 3, nor in the one below. One more run of this head ran at 171–179 from start to end, with Driver.Packet at 54 ms per frame against 29 in the others; the same build gave 332–345 in its next run, so I've left that run out.
    • Full ctest suite with the GPU (--parallel 4 --timeout 120): 2278 of 2289 pass and 7 are skipped. The other 4 (log_macros, log_macros_timestamps, agc_host_import_cut_lifetime and agc_driver_depth_plane_partial_store) fail the same way on main cf528e7.
  • After the fourth merge, with main 4017310, at b61555e, with NVIDIA driver 610.88:

    • The same validation set passes on main (72) and on this head (73), with APS5_DRAW_PIPELINE unset and set, with no VUID- messages on either side and the layer loaded.
    • The full suite and The Pathless were not rerun for this merge, whose only conflict was the debt entry; the results above are from 2853a75.
  • After the fix for the import walk hooks, at 43a9245, with NVIDIA driver 610.88:

    • The same validation set passes on main 4017310 (72) and on this head (73), with APS5_DRAW_PIPELINE unset and set, with no VUID- messages on either side and the layer loaded. In one run, main with APS5_DRAW_PIPELINE=1 failed agc_write_tracking_metadata_commit (an untouched leaf has a write stamp); it passed 5 of 5 reruns on each side, and this branch doesn't change what it tests.
    • The full suite and The Pathless were not rerun for this fix; the results above are from 2853a75.
  • After the fifth merge, with main f55190f, at cfbe00c, with NVIDIA driver 610.88:

    • The validation set passes on main (72) and on this head (73), and fix(agc): store resident linear color targets at completion points, limited to their drawn rectangles #9685's agc_driver_linear_target_* and agc_driver_address_passes_* tests pass on both (13), with APS5_DRAW_PIPELINE unset and set, with no VUID- messages on either side and the layer loaded. The new case passes seeded and unseeded.
    • Full ctest suite with the GPU (--parallel 4 --timeout 120): 2363 of 2375 pass on this head and 7 are skipped. The other 5 (guest_wide_formatting, log_macros, log_macros_timestamps, agc_host_import_cut_lifetime and agc_driver_depth_plane_partial_store) fail the same way on main f55190f. public_headers timed out at 120 s on both sides, leaving its 64 compile tests not run; rerun on their own with --timeout 1800, all 65 pass on both, in 53 s. video_out_flip_overlay failed on main only, with an overlay's implicit Vulkan layer loaded, and passed when rerun with that layer disabled.
    • The Pathless (PPSA01825), one APS5_TRACE_SECONDS=120:10 control run per side at 3840x2160, because fix(agc): store resident linear color targets at completion points, limited to their drawn rectangles #9685 changes how render targets are stored: the menu's 10 s intervals from 30 s on give 359–374 presents on main and 375–401 on this head. No refusal in either run.
    • @ahmadmuj's approval is on 43a9245, before this merge; the import walk change it covers is unchanged.
  • After the sixth merge, with main 27ad724, at 3397834, with NVIDIA driver 621.01:

  • After the seventh merge, at bfe0e86, with NVIDIA driver 621.01, against main ad622fc:

  • Full ctest suite at f27fcde (--parallel 4 --timeout 120): 2026 of 2036 pass and 5 are skipped. The other 5 (log_macros, log_macros_timestamps, agc_driver_sample_order, agc_driver_sample_order_grid_reference and agc_driver_sample_order_recipe_depth_locations) fail the same way on main 9d164f0.
    The fork's CI wasn't run: its runner has no Vulkan device and skips the GPU tests.

  • tools/check_conventions.py passes against main.

  • tools/pr_overlap.py: fix(agc): run the unwatch hook before an import whose range leaves the write watch whatever the import does #7505 and fix(agc): keep a rewatched import's memory compared once it is shared #9159 touch ImportWatched; fix(agc): run the unwatch hook before an import whose range leaves the write watch whatever the import does #7505 is described above, and fix(agc): keep a rewatched import's memory compared once it is shared #9159 only neighbours its lines. The other entries (perf(agc): keep a fast clear's guest bytes while they match unwatched memory #6108, perf(agc): clear single-key DCC surfaces on a verified uniform color #6848, perf(agc): draw finisher pipeline (APS5_DRAW_PIPELINE) #6946, feat(agc): look up table images at run time through a device image table on the GPU heap (APS5_GPU_PATH) #7642, docs(debt): record the storage image refusal over uncommitted guest memory #9275, perf(agc): GPU descriptor heap on VK_EXT_descriptor_heap (APS5_GPU_HEAP) #9508, fix(agc): free a completed batch's command buffer instead of keeping it #9565, perf(agc): keep constant RAM dumps and unregistered labels on the GPU without draining #9898) come from fix(agc): save the guest bytes of results that become pending over a surface that is no longer fully write-watched #7048's own changes, which this branch carries. perf(agc): track pending and borrowed texture units in 64-bit words #10886 builds on this PR (Depends on: #10026) and keeps its markLayersPending scan inside its AllIn guard.

Checklist

pathless-traces-isolation-7048-no-upload-hash.zip
pathless-traces-main-7048-e2c1fe440.zip
pathless-traces-main-83462cc57.zip
pathless-traces-pr-4989cab12.zip

…surface that is no longer fully write-watched

markLayersPending saved the guest bytes of a newly pending unit only when it had no write generation. A unit that was
last stored or uploaded while its memory was watched keeps a generation, so when a block of the surface left the write
watch while the unit was idle (a flush for the access that caused the unwatch had just stored it, so the unwatch hook
found it not pending) and the unit became pending again afterwards, it had neither the watch nor saved bytes to tell
the CPU's writes from its results. Its write-back then refused: storing storage image results pending over memory
outside the write watch without the guest bytes saved before them is not implemented. One such unit makes the whole
span untracked, so every unit of the write-back needs its bytes.

A unit without saved bytes and without a save on its way now takes its guest bytes when it becomes pending over a surface
that is not fully watched, whatever its generation. The blocks the CPU or the driver wrote since the unit's generation
are still told from the stamps while the unit's own blocks are watched and keep their bytes at the store, exactly as
when the hook saves a pending unit (keepWrittenSince, shared with saveBeforeUnwatch); their writes are collected unit by
unit, since a collect over a range holding an unwatched block does nothing. A unit whose own block is unwatched gets its
bytes alone.
… is write-watched

A write-back routes each kept range that is wholly watched into the unit shadow's slab, and marks the pieces shadowed at
the generation its collect over the whole stored span returns. When a block of the span has left the watch that collect
returns 0, MarkShadowed ignores a generation of 0, and the pieces stay in the slab without a record: never published,
their results lost. Over a span with an unwatched block every piece now goes to the import, as the saved bytes of the
store already are decided by the span.
…urface left the write watch

A storage image over write-watched memory is uploaded, so its units have a write generation and no saved guest bytes.
A block of the surface then leaves the write watch while nothing is pending, which the unwatch hook does not see, and
the image is marked written again without a lookup (as the colour mirror of a depth plane is after each dispatch). The
write-back cannot tell the CPU's writes from the results without saved bytes and refuses.

agc_storage_pending_after_unwatch runs that flow with no CPU write, with the CPU writing a block of the surface after
the results became pending again and with it writing before; the store has to succeed, keep the CPU's block and store
the rest, the block that left the watch included. Where the image's host import takes the surface out of the write
watch (a Windows runner), the test reports that it was not run.
…ay differ from its upload

A unit uploaded while watched has no saved bytes. Once its block left the
write watch the CPU's writes there are unstamped, and capturing the current
bytes as the baseline when the results turn pending without a lookup stored
the stale upload content over them for every texel the GPU did not write.
Capture only when the unit is still watched or equals the bytes it was
uploaded from; otherwise keep the refusal for that unit.
… its upload bytes

Surfaces above 1 MiB keep no byte snapshot of their upload, so an unwatched
unit with no saved bytes fell back to the unsaved-outside-watch refusal even
when the CPU never wrote there. Keep a 128-bit hash of each unit's bytes taken
when the unit was uploaded and compare it at markLayersPending: equal means the
current bytes are the upload and are saved as the baseline, a different hash or
none keeps the refusal. Units whose content came from elsewhere (borrowed from an
alias, cleared, stored by a write-back) drop their hash.

The pending-after-unwatch test also runs a 1024x1024 surface: an unwatched unit
the CPU did not write is stored, one it wrote is refused with its bytes intact.

@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.

Reviewed the stacked diff through d613893, including the declared dependency #7048. The follow-up replaces the per-upload hash with a hash captured at a proven write-watch transition, gates it by the write generation, and invalidates it on writes/refreshes; the focused tests cover the tested import-walk failure paths and pending-result behavior. The PR reports same-machine Pathless measurements in which #7048 alone regresses from 319–330 to 85–104 presents/10s and the combined stack returns to 304–316; those are the author’s Windows captures, not a local reproduction. Local diff whitespace, conventions, and test-registration checks pass. I could not run the focused AGC executable here: the available pre-existing binary aborts before test startup because this host lacks LZCNT, BMI1, BMI2, MOVBE, and AVX2. Hosted product jobs are skipped by the current workflow policy. Please preserve the dependency/merge order so #7048 is not delivered without its performance correction.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@github-actions

github-actions Bot commented Oct 23, 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 2434 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp L4305
PR 4448 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp L106
PR 6108 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L2631, L2645
PR 8246 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L6247
PR 9898 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp L12749
PR 10159 core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake L160
PR 12792 docs/dev/debt/libSceAgcDriver.md L181
PR 13251 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L18-19, L2512-2519
PR 14302 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L1515, L3937, L3943, L3947, L4518-4532
PR 14311 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L3931

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 1368 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 1896 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 4472 docs/dev/ENVIRONMENT.md
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6186 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
docs/dev/ENVIRONMENT.md
docs/dev/debt/libSceAgcDriver.md
PR 6187 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6197 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6209 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6273 core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6280 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6300 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6412 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6481 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6501 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 6548 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 6918 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 6924 docs/dev/ENVIRONMENT.md
PR 6970 core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7048 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp
core/libs/prx/libSceAgcDriver/tests/execution/LinearTargetCpuWrites.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7223 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7275 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7449 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
docs/dev/ENVIRONMENT.md
docs/dev/debt/libSceAgcDriver.md
PR 7457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/ENVIRONMENT.md
PR 7508 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7514 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/ENVIRONMENT.md
PR 7604 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/WriteTracking.cpp
docs/dev/ENVIRONMENT.md
docs/dev/debt/libSceAgcDriver.md
PR 7642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Execution/include/GuestMemory.hpp
core/libs/prx/libSceAgcDriver/Execution/src/GuestMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
core/libs/prx/libSceAgcDriver/tests/cmake/agc_driver_recorder_tests.cmake
docs/dev/ENVIRONMENT.md
docs/dev/debt/libSceAgcDriver.md
PR 7689 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7978 docs/dev/debt/libSceAgcDriver.md
PR 8004 docs/dev/ENVIRONMENT.md
PR 8484 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/ENVIRONMENT.md
PR 8592 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 8707 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9012 core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
docs/dev/ENVIRONMENT.md
PR 9020 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9248 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 9335 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 9345 docs/dev/ENVIRONMENT.md
PR 9356 docs/dev/ENVIRONMENT.md
docs/dev/debt/libSceAgcDriver.md
PR 9387 docs/dev/ENVIRONMENT.md
docs/dev/debt/libSceAgcDriver.md
PR 9432 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 9592 docs/dev/debt/libSceAgcDriver.md
PR 9712 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/ENVIRONMENT.md
PR 9852 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/GuestBufferMemory.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
docs/dev/ENVIRONMENT.md
PR 9884 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 9915 docs/dev/debt/libSceAgcDriver.md
PR 9950 docs/dev/debt/libSceAgcDriver.md
PR 10025 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 10069 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 10233 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 10249 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 10256 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/debt/libSceAgcDriver.md
PR 10359 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/debt/libSceAgcDriver.md
PR 10442 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
docs/dev/ENVIRONMENT.md
PR 10547 docs/dev/ENVIRONMENT.md
PR 10757 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 10894 docs/dev/ENVIRONMENT.md
PR 10920 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/tests/Recorder.cpp
PR 11351 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 11551 docs/dev/debt/libSceAgcDriver.md
PR 12120 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
docs/dev/debt/libSceAgcDriver.md
PR 12157 core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 12323 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 12324 core/libs/prx/libSceAgcDriver/CMakeLists.txt
docs/dev/ENVIRONMENT.md
PR 12544 docs/dev/ENVIRONMENT.md
PR 12746 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
docs/dev/ENVIRONMENT.md
PR 12828 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 12863 docs/dev/ENVIRONMENT.md
PR 13148 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
PR 13262 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 14212 docs/dev/debt/libSceAgcDriver.md
PR 14299 docs/dev/ENVIRONMENT.md

@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.

I reviewed exact head d6138938c99c24a21b6dce947cf6cb31e0d9a89e. The change is correctly declared as depending on open #7048, and its author-provided test/performance report is unusually detailed. Local git diff --check and python3 tools/check_conventions.py --base upstream/main pass. The hosted eligibility, intake, conventions, labels, and report checks pass; hosted Linux and Windows product builds are skipped by repository policy. I could not run this Windows/GPU test suite locally.

I am requesting changes because the branch no longer merges cleanly into current main. python3 tools/check_main_sync.py --base upstream/main reports 9 commits ahead / 1,079 behind and a merge conflict in core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp; it also identifies eight changed files modified on both sides since the merge base. The repository's build guidance requires current-main builds and a clean merge before review/merge, so the reported tests and performance comparison against 83462cc57 do not validate the current merge result.

Please rebase onto current main, resolve the Texture.cpp conflict while preserving intervening changes, then rerun the focused write-watch/storage-image tests and the same-scene main versus rebased-PR comparison. Please include the exact tested heads and refresh the trace summaries/digests if the rebase changes the tested result. This request is for the concrete merge conflict and stale validation base; it is not about queue size.

winstrex and others added 2 commits October 23, 2026 08:00
Resolves boykopovar#7048's conflicts with main in Texture.cpp: markLayersPending keeps
main's CommitTrims structure with boykopovar#7048's baseline condition, and the
generation baseline constant stays where boykopovar#7048 moved it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tead of at every upload

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@winstrex
winstrex force-pushed the fix/agc-unwatch-baseline branch from d613893 to ec6ceec Compare October 23, 2026 11:52
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
Adapts this branch to main's TrackerLock (boykopovar#6169): markWritten and
ImportWatched release the tracker around beforeUnwatchUnproven with
Suspend and Resume, which do not publish the generation, since both
stamp again after relocking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@eliasbruno124-dev

Copy link
Copy Markdown
Contributor

Rebase detail for ec6ceece9 with #7048: on main dcd40e119dc524435692de754b40a1c2fc9ed0ca, the new ImportWatched leave lambda still calls lock.unlock() / lock.lock(). The Windows build fails because #6169 changed lockTracker() to return TrackerLock.

The hook needs the non-publishing pair:

lock.Suspend();
beforeUnwatchUnproven(begin, static_cast<std::size_t>(end - begin));
lock.Resume();

Keep Suspend() / Resume() around the failed-collect hook in markWritten too. TrackerLock::Unlock() publishes the generation, so it is not an equivalent replacement while that transition is unfinished.

I built and tested this adaptation on Windows 11 Pro 10.0.26300, RTX 3060 Laptop GPU, NVIDIA 617.42, WinLibs GCC 15.2.0 r7:

This is native integration evidence only. The current GTA III baseline run ended before the first mission, so I do not yet have a valid title or performance comparison for these heads. Linux and other GPU vendors are untested here.

Prepared with AI assistance.

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 23, 2026
@zaidhawk

Copy link
Copy Markdown
Contributor

@winstrex Persona 3 Reload (PPSA10872) on this head, on the scene where I measured #7048's slowdown (the start-up screens and logos) and the opening animation after it.

Setup: Windows 11 Pro 26200, RTX 5090 (NVIDIA 617.42), Ryzen 7 9800X3D, WinLibs GCC 15.2.0 r7, Release, 3840x2160, default settings. Three builds that differ only in libs/, with the same app.exe, app0/ and warm shader cache: main 9d164f0, which is the main this head merges; main + #7048, which is 9d164f0 with this branch's 93c878b (its merge of #7048 with main) merged locally and cleanly, so its summary says dirty; and this PR, df1b2d5.

Every run gets the same scripted pad input: Cross at 40 s and 55 s for the online-features dialog, nothing else. APS5_TRACE_SUMMARY_EVERY=10, no APS5_PROFILE_*. One warm-up run per build, then four rounds alternating main, + #7048, this PR, 240 s each.

Presents per 10 s (600 is the 60 FPS cap), one value per run:

main + #7048 this PR
start-up screens, intervals 10–50 s 596, 596, 596 122, 121, 121, 122 596, 597, 596
opening animation, intervals 70–180 s 600, 600, 600 117, 119, 119, 119 600, 600, 600
GPU busy per present in the animation (ms) 4.0–4.2 10.1–10.9 3.96–3.97

So on this title the PR brings presents back to main's rate, and the title gets through the same screens. Both main and this PR sit at the cap, so a cost smaller than the headroom wouldn't show here.

The refusal #7048 removes came back once with this PR. In 1 of 5 runs (the warm-up; the shader cache was already warm from the main warm-up, 28 misses), the title stopped about 70 s after launch, right after the ATLUS logo:

compute shader 0x2012260000: AGC graphics: storing storage image results pending over memory outside the write watch without the guest bytes saved before them is not implemented

That's the shader that stops main here. On this main the stop is intermittent: 2 of 5 warm-cache runs (at 16 s and 76 s), plus the cold-cache warm-up. With #7048 alone it happened in 0 of 5. Five runs can't tell 1 in 5 from 0 in 5, but they do show this PR refusing a capture that #7048 allowed on this title. The log doesn't say whether the unit had no hash or a hash that no longer matched. Could it be the Windows case in the new debt entry, a range that a host region starts to overlap?

Not this PR: every run that didn't refuse, in all three builds, stops at about 185 s in the opening animation on compute shader 0x148ee0600: AGC graphics: guest buffer has no GPU owner; DISPATCH_INDIRECT on queue 0x0. On main 9d6adeb with #7048, #9068, #8070 and #9701 to #9703, this title ran 50 minutes without it. #10304 is for that message.

Left out: one run of this PR (10026-1), where the launcher's console window covered the game; its start-up intervals were 519–539. A first main round-2 run that stopped on the refusal at 16 s (0 intervals) was overwritten by my script. Its stop comes from the script's log.

The summaries and digests of every run: p3r-10026-summaries.zip

Not covered: other GPUs, Linux, anything past the opening animation, enough runs for a refusal rate.

tools/trace_digest.py of one run per build (round 2)

main 9d164f0

18 intervals, 0.0 to 180.2 s, 10401 presents
NVIDIA GeForce RTX 5090, NVIDIA 617.42, Windows 10.0.26200, 3840x2160, commit 9d164f0

# seconds presents p50 p90 p99 max GPU busy busiest worker largest phase
1 0-10 383 16.7 24.4 151.7 1589.3 4.7 69 % Driver.Packet 32.0
2 10-20 600 16.7 16.8 16.9 17.1 3.7 90 % Driver.Packet 20.1
3 20-30 600 16.7 16.9 17.3 29.0 3.9 74 % Driver.Packet 18.3
4 30-40 600 16.7 16.8 16.9 17.3 3.9 97 % Driver.Packet 23.9
5 40-50 584 16.7 16.9 26.9 107.4 4.1 97 % Driver.Packet 32.3
6 50-60 434 16.7 25.6 149.9 157.3 5.1 90 % Driver.Packet 34.9
7 60-70 600 16.7 16.9 18.3 24.1 5.6 88 % Driver.Packet 23.0
8 70-80 600 16.7 16.9 17.0 18.1 4.3 88 % Driver.Packet 20.5
9 80-90 600 16.7 16.9 17.0 17.3 4.2 99 % Driver.Packet 30.0
10 90-100 600 16.7 16.9 17.0 17.3 4.2 100 % Driver.Packet 33.1
11 100-110 600 16.7 16.9 16.9 17.0 4.2 100 % Driver.Packet 33.1
12 110-120 600 16.7 16.9 16.9 17.3 4.2 100 % Driver.Packet 33.1
13 120-130 600 16.7 16.9 17.0 17.3 4.2 100 % Driver.Packet 33.1
14 130-140 600 16.7 16.9 16.9 17.0 4.2 100 % Driver.Packet 33.1
15 140-150 600 16.7 16.9 16.9 17.0 4.2 100 % Driver.Packet 33.1
16 150-160 600 16.7 16.9 16.9 17.0 4.2 100 % Driver.Packet 33.1
17 160-170 600 16.7 16.9 17.0 17.0 4.2 100 % Driver.Packet 33.1
18 170-180 600 16.7 16.9 16.9 17.0 4.2 100 % Driver.Packet 33.1
p99 ms  @___:@____________  0-151.7
gpu ms  %***#%@###########  0-5.6

main 9d164f0 + #7048 (93c878b merged locally)

18 intervals, 0.0 to 180.9 s, 2073 presents
NVIDIA GeForce RTX 5090, NVIDIA 617.42, Windows 10.0.26200, 3840x2160, commit 9d164f0 (dirty)

# seconds presents p50 p90 p99 max GPU busy busiest worker largest phase
1 0-10 77 88.2 185.9 1852.8 1852.8 4.6 87 % Driver.Packet 226.0
2 10-20 127 78.3 81.0 98.8 100.0 3.5 100 % Driver.Packet 157.7
3 20-30 126 79.4 82.1 89.4 89.6 3.5 100 % Driver.Packet 159.0
4 30-40 118 84.5 89.0 93.7 95.8 9.9 100 % Driver.Packet 170.1
5 40-50 114 85.1 89.5 187.8 189.3 10.6 100 % Driver.Packet 175.6
6 50-60 89 92.5 199.4 257.3 257.3 10.7 99 % Driver.Packet 218.6
7 60-70 112 86.2 91.3 211.2 225.7 10.9 99 % Driver.Packet 176.5
8 70-80 119 85.8 89.2 91.6 91.8 10.3 100 % Driver.Packet 168.8
9 80-90 117 86.9 89.0 91.9 93.9 12.3 100 % Driver.Packet 171.5
10 90-100 118 86.2 88.8 90.9 92.5 11.2 100 % Driver.Packet 169.4
11 100-111 120 85.5 88.7 90.2 90.5 9.6 100 % Driver.Packet 167.5
12 111-121 121 81.7 88.4 91.1 92.6 8.5 100 % Driver.Packet 165.6
13 121-131 119 85.6 88.8 90.8 91.4 10.8 100 % Driver.Packet 168.8
14 131-141 120 85.2 87.8 90.5 91.5 9.3 100 % Driver.Packet 166.9
15 141-151 120 85.1 88.8 91.2 91.9 9.9 100 % Driver.Packet 167.3
16 151-161 119 85.6 89.0 90.4 91.3 9.8 100 % Driver.Packet 168.0
17 161-171 118 86.3 89.0 91.1 92.1 10.6 100 % Driver.Packet 169.6
18 171-181 119 85.6 89.2 92.3 92.4 10.3 100 % Driver.Packet 167.9
p99 ms  @...___...........  0-1852.8
gpu ms  ---#%%%%@%#*%###%%  0-12.3

this PR, df1b2d5

18 intervals, 0.0 to 180.2 s, 10419 presents
NVIDIA GeForce RTX 5090, NVIDIA 617.42, Windows 10.0.26200, 3840x2160, commit df1b2d5

# seconds presents p50 p90 p99 max GPU busy busiest worker largest phase
1 0-10 392 16.7 27.5 164.8 1596.0 6.4 78 % Driver.Packet 35.9
2 10-20 599 16.7 16.9 17.7 25.4 4.2 97 % Driver.Packet 26.8
3 20-30 598 16.7 16.9 21.7 42.2 6.0 88 % Driver.Packet 24.9
4 30-40 600 16.7 16.9 18.8 23.7 6.4 77 % Driver.Packet 21.0
5 40-50 588 16.7 16.9 17.1 109.1 5.9 90 % Driver.Packet 23.4
6 50-60 442 16.7 23.7 139.9 189.4 5.7 81 % Driver.Packet 31.3
7 60-70 600 16.7 16.9 17.0 22.0 3.9 69 % VideoOut.Flip 16.4
8 70-80 600 16.7 16.9 17.1 17.6 4.0 99 % Driver.Packet 26.1
9 80-90 600 16.7 16.9 17.0 17.3 4.0 99 % Driver.Packet 32.9
10 90-100 600 16.7 16.9 17.0 17.3 4.0 100 % Driver.Packet 33.1
11 100-110 600 16.7 16.9 17.1 17.3 3.9 100 % Driver.Packet 33.1
12 110-120 600 16.7 16.9 17.0 17.2 3.9 100 % Driver.Packet 33.1
13 120-130 600 16.7 16.9 16.9 17.0 4.0 100 % Driver.Packet 33.1
14 130-140 600 16.7 16.9 16.9 17.0 3.9 100 % Driver.Packet 33.1
15 140-150 600 16.7 16.9 17.0 17.2 4.0 100 % Driver.Packet 33.1
16 150-160 600 16.7 16.9 17.0 17.2 4.0 100 % Driver.Packet 33.1
17 160-170 600 16.7 16.9 16.9 17.3 4.0 100 % Driver.Packet 33.1
18 170-180 600 16.7 16.9 16.9 17.3 4.0 100 % Driver.Packet 33.1
p99 ms  @____%____________  0-164.8
gpu ms  @*%@%%************  0-6.4

winstrex and others added 2 commits October 23, 2026 21:19
…ytes

The refusal of results pending over memory outside the write watch
without the guest bytes saved before them now names the first unit
refused and why it has no baseline: its results became pending while
watched and the range left the watch without its bytes saved, a save
in flight did not complete, its saved bytes were dropped, no upload
copy, bytes that differ from the upload copy, no hash for its current
generation, an unwatch hook that ran without the CPU writes proven,
CPU writes the watch saw before the unwatch, a unit that had pending
results or saved bytes at the unwatch, or bytes that no longer match
the hash.

The unwatch hook now also visits the live textures when the CPU writes
are not proven, only to record that it could not hash them. What is
captured and what is refused does not change.

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

On Windows no range leaves the write watch without the unwatch hook: the
host regions are fixed when the guest arena is reserved, the commit
generation only decides when a later commit restores the watch, and
every path that excludes a range runs the hook first. The Linux case,
GuestWriteWatchUnregister called from libkernel, stays.

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

Copy link
Copy Markdown
Contributor Author

@oneandonlydean done in 660cef6fe. I dropped the three Require(refused, ...) lines and mustRefuse, which only they used. Those cases are still refused today, but the test no longer requires it: it keeps the checks that a refused write-back leaves the CPU's bytes intact and that a store merges correctly. Without those lines, some baseline errors (ignoring the hash or the upload copy, or taking a missing hash for a baseline) no longer failed any test, because the stored bytes came out right anyway. So I added four cases where the CPU writes into the unit after the GPU results were written. A wrong acceptance there stores the image's older bytes over the CPU's, so these errors show up in the bytes and the coverage no longer depends on requiring the refusal. Hashing without proven CPU writes is still caught, by the cause the refusal names rather than by the bytes: a failed collect doesn't clear the OS's record of the write, so the next collect still sees it. That cause check only runs when there is a refusal, so it doesn't require one. The mutation check in the description has the details.

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head 660cef6 merged onto main ba5b533 · tested 2026-10-29 17:18 UTC · 27 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,289 passed · 4 failed (0 new, 4 also fail on main) · 39 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,332 of 2,891 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 (895 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_bda_device 59.6 s
Failing on main too (4, not counted against this PR)
agc_compute_pipeline_inspection
agc_submit_progress_no_timeline
guest_mmap_executable
guest_socket_options_multicast
Validated but not compared (1, not in the main baseline's validation pass)
agc_storage_pending_after_unwatch
Validation IDs already present on main (4 IDs in 436 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (436 tests): agc_compute_pipeline_replay, agc_driver, agc_driver_address_passes_draw_transitions, agc_driver_address_passes_targets_split, agc_driver_alias_copy_label, agc_driver_array_layer_target, agc_driver_array_layer_target_cpu, agc_driver_bda_anchor_reads, and 428 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 (39)
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_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_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 platform
video_out_flip_labels: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SD
Reproduce
git fetch origin 660cef6fe9ada0979f22ed2dd9c76419ae36306b ba5b5330b37724bca4b99f30faab504653635a04
git checkout ba5b5330b37724bca4b99f30faab504653635a04 && git merge --no-ff 660cef6fe9ada0979f22ed2dd9c76419ae36306b
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.

…PU lock

ImportMappedRanges imports at map time without the GPU lock while holding
the imports mutex, and refused only ranges that a pending storage image
overlaps, the one case in which the unwatch hook took the GPU lock. Since
the hook also hashes live storage images larger than 1 MiB, it took the
GPU lock for them under the imports mutex while a GPU lock owner could
wait for that mutex in HostImportFor. One predicate,
UnwatchHookLocksGpu, now decides both when the hook takes the GPU lock and
when the import without it is deferred to the GPU lock first.

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.

Reviewed — changes requested

The current head does not merge cleanly with live main: GuestBufferMemory.cpp and Texture.cpp conflict, so this exact change cannot be validated against the required current-base tree.

What I ran

  • PR head: 53c3cd4eee3b15cd6408cd78a8934c9fcfb1a8ec.
  • Current base: main 420b6a064eadaa7c5e50b080c7fad59c41150b4b.
  • Merge preview: git merge-tree --write-tree upstream/main refs/remotes/upstream/pr-10026 — conflicts in GuestBufferMemory.cpp and Texture.cpp.
  • Environment: Debian 12 x86-64, GCC 12.2.0, CMake 3.25.1, Ninja 1.11.1; host Xeon E5-1620 v2. QEMU user-mode TCG ran with llvmpipe (LLVM 15.0.6).
  • Commands:
cmake -S /dev/shm/AnyPS5-review-10026 -B /dev/shm/AnyPS5-build-10026 -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
cmake --build /dev/shm/AnyPS5-build-10026 --target agc_driver_recorder_tests -j2
git -C /dev/shm/AnyPS5-review-10026 diff --check cda9c9bedf143010185aa17cf04e221389dd78b5...HEAD
git -C /marc/AnyPS5 merge-tree --write-tree upstream/main refs/remotes/upstream/pr-10026
ctest --test-dir /dev/shm/AnyPS5-build-10026 -R '^agc_unwatch_hook_lock_order$' -V --timeout 120
cd /dev/shm/AnyPS5-build-10026/core/libs/prx/libSceAgcDriver
APS5_NO_PIPELINE_JOURNAL=1 qemu-x86_64 -cpu max /dev/shm/AnyPS5-build-10026/tests/agc_driver_recorder_tests --unwatch-lock-order-only

Results

Check Result
Current-main merge preview Fails with content conflicts in GuestBufferMemory.cpp and Texture.cpp.
Local configure and agc_driver_recorder_tests build Passed; target linked successfully.
git diff --check against the PR base Passed with no output.
Hosted exact-head checks Conventions and labels pass; no product build/test job is present for head 53c3cd4.
Native agc_unwatch_hook_lock_order CTest Could not start the test: the host CPU gate aborts because this Xeon lacks LZCNT, BMI1, BMI2, MOVBE, AVX2 and FMA.
QEMU --unwatch-lock-order-only run Skipped with exit 77: llvmpipe keeps host imports watched and does not provide the write-watch path this test requires.
Unedited command output
# Final lines from the successful initial build
[955/956] Building CXX object core/libs/prx/libSceAgcDriver/CMakeFiles/agc_driver_recorder_tests.dir/tests/Recorder.cpp.o
[956/956] Linking CXX executable tests/agc_driver_recorder_tests

# git diff --check cda9c9bedf143010185aa17cf04e221389dd78b5...HEAD
(no output; exit 0)

# Current-main merge preview
CONFLICT (content): Merge conflict in core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
CONFLICT (content): Merge conflict in core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp

# Native CTest
[src/HostCpu.cpp:193 operator()] host CPU Intel(R) Xeon(R) CPU E5-1620 v2 @ 3.70GHz: has SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, POPCNT, CMPXCHG16B, LAHF/SAHF, AVX, F16C, AES, PCLMULQDQ, RDRAND, XSAVEOPT, OS AVX state (XCR0); lacks LZCNT, BMI1, BMI2, MOVBE, AVX2, FMA, RDSEED, ADX, SHA, CLFLUSHOPT, CLWB, RDPID, XSAVEC, SSE4A, PREFETCHW, CLZERO
[libc] unhandled exception 'St13runtime_error' thrown from 0x7f64bac4f9b7
terminate called after throwing an instance of 'std::runtime_error'
  what():  The host CPU (Intel(R) Xeon(R) CPU E5-1620 v2 @ 3.70GHz) lacks LZCNT, BMI1, BMI2, MOVBE, AVX2, FMA. The PS5's Zen 2 CPU has these extensions and titles run their instructions natively, so this CPU cannot run them.
1/1 Test #618: agc_unwatch_hook_lock_order ......Subprocess aborted***Exception:   0.01 sec

# QEMU user-mode run
[src/HostCpu.cpp:193 operator()] host CPU QEMU TCG CPU version 2.5+: has SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, POPCNT, LZCNT, BMI1, BMI2, MOVBE, CMPXCHG16B, LAHF/SAHF, AVX, AVX2, FMA, F16C, AES, PCLMULQDQ, RDRAND, ADX, CLFLUSHOPT, CLWB, XSAVEOPT, SSE4A, OS AVX state (XCR0); lacks RDSEED, SHA, RDPID, XSAVEC, PREFETCHW, CLZERO
Vulkan device: llvmpipe (LLVM 15.0.6, 256 bits)
Vulkan device: llvmpipe (LLVM 15.0.6, 256 bits)
host imports stay watched
no write watching: the unwatch hook's lock order at a map-time import not tested

Negative control

Assertion Code change made Did the new test fail?
A map-time import is deferred before an unwatch hook can acquire the GPU mutex. None; I did not mutate the code. The test could not execute its write-watch path on this host. Not exercised.

Findings

Blocking

  • The exact PR head conflicts with current main in GuestBufferMemory.cpp and Texture.cpp. The repository's contribution instructions require rebasing a PR that no longer merges cleanly. Resolve both conflicts while preserving the current main changes and this PR's lock-order fix.

Non-blocking

  • I found no separate demonstrated defect in the new predicate from this static review. The new lock-order assertion remains unverified locally because the available host cannot exercise the required unwatch path.

Sources

Not verified

No actual write-watch/unwatch scenario ran on this host, and no PS5-console behavior was tested. The latest NVIDIA report predates commit 53c3cd4; its results do not validate this new lock-order change. Full Linux/Windows PR builds and tests are not automatic under the repository's current workflow.

To get this to a good state

Rebase onto 420b6a064eadaa7c5e50b080c7fad59c41150b4b, resolve both conflicts, then run the focused agc_unwatch_hook_lock_order test on a supported host where the write-watch path executes. Per AGENTS.md, compare the same focused test on that host's main and updated head with Vulkan validation enabled. I will rerun the merge preview, conventions, and available focused checks on the new head.

…the imports mutex

The host imports mutex now notes on its thread that it is held, and the
unwatch hook aborts before it takes GuestMemory::GpuMutex if this thread
holds the imports mutex without it. The check is always on in builds
without NDEBUG and, in release builds, only with APS5_ASSERT_GPU_LOCK, which
agc_unwatch_hook_lock_order now sets, so that test fails at the inverted
acquisition itself instead of after its watchdog.

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.

Re-checked — new head, changes requested

The new lock-order assertion is present, but head 8b14bde3ca1c256413582b8ec2d7d88889d560cd still conflicts with current main in GuestBufferMemory.cpp and Texture.cpp, and the available GPU report predates this head.

What I ran

Reviewed head 8b14bde3ca1c256413582b8ec2d7d88889d560cd against PR base cda9c9bedf143010185aa17cf04e221389dd78b5; current main is dc38224141e0b6096007b876b4c42fc9144d11b8. The merge preview is conflicted. Local checks ran in Linux; no compiler or build was invoked.

git merge-tree --write-tree refs/remotes/upstream/main refs/review/pr-10026-current
git diff --check cda9c9bedf143010185aa17cf04e221389dd78b5..refs/review/pr-10026-current
python3 tools/check_conventions.py --base cda9c9bedf143010185aa17cf04e221389dd78b5 --head refs/review/pr-10026-current
gh pr checks 10026 --repo boykopovar/AnyPS5

Results

Check Result
Current-main merge preview Conflicts in GuestBufferMemory.cpp and Texture.cpp
Diff check against PR base Passed; no output
Repository conventions against PR base Passed
Hosted checks Conventions and labels passed on the exact head; no product test job ran
NVIDIA GPU test report The latest report tests 660cef6fe9ada0979f22ed2dd9c76419ae36306b, not this head; it cannot verify the new lock-order assertion
Unedited local check output
CONFLICT (content): Merge conflict in core/libs/prx/libSceAgcDriver/Graphics/src/GuestBufferMemory.cpp
CONFLICT (content): Merge conflict in core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
All conventions checks passed.

Findings

Blocking

  • The PR does not merge cleanly with current main, so the required current-base validation cannot be completed. Please rebase onto dc38224141e0b6096007b876b4c42fc9144d11b8 and preserve both main's changes and this PR's lock-order fix.
  • The new assertion/test commit 8b14bde3 is not covered by the posted NVIDIA report, which tested 660cef6fe. After resolving the conflicts, run the focused unwatch-lock-order test on the new exact head and a same-machine main baseline with Vulkan validation, as required by the AGC test guidance.

Non-blocking

The conventions check passes against the PR base. I did not find another new source-level blocker in the added assertion, but its GPU path is not verified by the existing report.

Sources

Not verified

No current-main merged build, focused GPU test on this head, or console run was performed. The latest NVIDIA test report is on an older commit.

To get this to a good state

Please resolve the two current-main conflicts, then run the focused lock-order test on the updated head and same-machine main baseline with Vulkan validation. I will rerun the merge preview, diff check, conventions, and review the new test evidence on that exact head.

@winstrex

Copy link
Copy Markdown
Contributor Author

@MarcZX10 thanks. This PR will be closed as soon as 53c3cd4 and 8b14bde are in #7048, which already carries the rest of its commits and is the PR they'll land through. So there's no need to rebase or review it further here; a review of those two commits makes more sense on #7048.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@winstrex the import-lock fix still has a check/use gap. ImportMappedRanges checks UnwatchHookLocksGpu while holding the imports mutex, but releases Live().mutex before the import invokes SaveBeforeUnwatch. A concurrent StorageTexture::Create can finish its upload, then publish an overlapping image larger than 1 MiB into Live() between those two steps while holding the GPU mutex. The hook then sees that image and tries to take the GPU mutex under the imports mutex. The new assertion aborts in checked builds; without the check, the GPU owner can wait for imports and deadlock.

Could you cover publication between the precheck and the hook with a deterministic barrier regression, and make the decision remain valid until the hook completes? The new test creates the image before starting the mapping thread, so it only exercises the pre-existing-image case. This finding comes from the source interleaving; I have not reproduced it in a native run.

Please carry the correction through #7048 as planned. At the checked #7048 head 730c721773, neither final commit 53c3cd4ee / 8b14bde3c is an ancestor and UnwatchHookLocksGpu is absent, so the stated condition for closing this PR is not yet established.

winstrex and others added 2 commits November 1, 2026 08:07
…eline

- ImportMappedRanges: main's `replacing` structure, with UnwatchHookLocksGpu in place of AnyPendingOverlaps.
- markLayersPending: main's `!layerPending.AllIn(first, count)` block around this branch's pendingMiss bookkeeping and captureGuestBytes(&unsaved, true). With every unit already pending, that bookkeeping changed nothing, so skipping it keeps the behaviour.
- writeBackWindows: no unit shadow destination when the stored span is not watched or the image is at most SmallStoreBytes.
- Tests: agc_storage_pending_after_unwatch and agc_unwatch_hook_lock_order move to tests/cmake/agc_driver_recorder_tests.cmake.

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

ImportMappedRanges decides under the imports mutex, without the GPU lock,
that the unwatch hook will not need the GPU lock, then releases the live
and pending registries before the import runs the hook. A storage image
published in between (StorageTexture::Create) or one whose first units
became pending (markLayersPending) made the hook take the GPU lock under
the imports mutex while the GPU lock owner waited for that mutex.

A map-time import gate now orders them. A map-time import without the GPU
lock takes, before the imports mutex, the 2 MiB stripes of its run
exclusively and keeps them until its import returns; Create, from before
its upload to its publication, and a first pending transition take the
stripes of their image shared. Order: GPU lock, stripes (ascending index),
imports mutex, live and pending registries. The run is recomputed once the
stripes are held and they are taken again if it grew past them.

Publications take no stripe while no map-time import is announced: a
publisher announces itself and then checks for imports; an import
announces itself and then waits, yielding, until no announced publisher
remains. Checked builds abort when a publication waits for the gate while
its thread holds the imports mutex or a registry mutex.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Nov 1, 2026
@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head f7bfe87 merged onto main e6f5730 · tested 2026-11-01 15:24 UTC · 20 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,495 passed · 4 failed (0 new, 3 also fail on main, 1 unstable on main) · 45 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,544 of 3,148 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 (941 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.1 s, ⚠️ agc_driver_graphics_maps_fallback 39.1 s, ⚠️ agc_driver_graphics 34.7 s
Unstable on main (1, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_pipeline_journal: failed in 1 of the last 10 main runs
Failing on main too (3, not counted against this PR)
agc_driver_image_table
guest_mmap_executable
guest_socket_options_multicast
Validated but not compared (8, not in the main baseline's validation pass)
agc_map_gate_disjoint
agc_map_gate_fast_path
agc_map_gate_pending
agc_map_gate_publication
agc_map_gate_retry
agc_map_gate_stress
agc_storage_pending_after_unwatch
agc_unwatch_hook_lock_order
Validation IDs already present on main (5 IDs in 484 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (484 tests): agc_driver, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, agc_driver_bda_span_reads, and 476 more
VUID-vkCmdCopyImage-srcImage-01548 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (1 test): agc_driver_prepared_exports
VUID-VkShaderModuleCreateInfo-pCode-08737 (1 test): agc_driver_image_sample_derivatives
Skipped (45)
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_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_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_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_backtoback: 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_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_dcc: 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_deferred: 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_fps_limit: 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_o
Reproduce
git fetch origin f7bfe87d111a6fb161ece7d6acfa166eabfff5d1 e6f57307fa7f47165d7fc1bee945abdb2f6816e7
git checkout e6f57307fa7f47165d7fc1bee945abdb2f6816e7 && git merge --no-ff f7bfe87d111a6fb161ece7d6acfa166eabfff5d1
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.

@TechLevelZero

Copy link
Copy Markdown
Contributor

✅ AMD GPU test: passed

PR head f7bfe87 merged onto main 291e02d · tested 2026-11-01 15:31 UTC · 26 min

GPU AMD Radeon RX 6300 (RADV NAVI24), driver Mesa 25.2.8-0ubuntu0.24.04.4, Vulkan 1.4.318
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-42-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,482 passed · 13 failed (0 new, 12 also fail on main, 1 unstable on main) · 54 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,549 of 3,155 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 (941 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.0 s, ⚠️ agc_driver_graphics_maps_fallback 88.5 s, ⚠️ agc_driver_graphics 81.9 s, ⚠️ public_headers 42.7 s, ⚠️ unwrap_self 37.8 s
Unstable on main (1, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_pipeline_journal: failed in 3 of the last 4 main runs
Failing on main too (12, not counted against this PR)
agc_driver_guest_memory_lifetime_direct
agc_driver_image_table
agc_driver_job_manager_compute_capacity
agc_driver_native_depth_bda_clear
agc_driver_native_depth_buffer_clear
agc_driver_native_depth_clears
agc_driver_native_multisample_depth
agc_idle_publish_wait
agc_submit_progress
agc_submit_progress_no_timeline
guest_mmap_executable
guest_socket_options_multicast
Validated but not compared (8, not in the main baseline's validation pass)
agc_map_gate_disjoint
agc_map_gate_fast_path
agc_map_gate_pending
agc_map_gate_publication
agc_map_gate_retry
agc_map_gate_stress
agc_storage_pending_after_unwatch
agc_unwatch_hook_lock_order
Validation IDs already present on main (5 IDs in 481 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (481 tests): agc_driver, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, agc_driver_bda_span_reads, and 473 more
VUID-vkCmdCopyImage-srcImage-01548 (6 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (6 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (1 test): agc_driver_prepared_exports
VUID-VkShaderModuleCreateInfo-pCode-08737 (1 test): agc_driver_image_sample_derivatives
Skipped (54)
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_copied_writer_label_order: skipped, the device reports a minStorageBufferOffsetAlignment of 4, so the dispatch binds its record at +0x1004 in place and nothing is written back; the test needs an alignment above 4
agc_driver_image_table_vouched_lookups: skipped, the device reports 4 of 4 host-imported pages written after a GPU read, so a watched imported table cannot be vouched for
agc_driver_linear_target_direct_write_completion: skipped, the device does not keep host imports write-watched, so no completion stamp over the target is tracked
agc_driver_ngg_vertex_wave_reduction: skipped, s3 describes a 32-lane wave only on 32-lane subgroups, and the device has 64 lanes
agc_driver_unit_shadow: skipped, the device does not import watched host memory for unit shadows
agc_driver_unit_shadow_budget: skipped, the device does not import watched host memory for unit shadows
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_shadowed_key_stores: skipped, the device imports no watched host memory under a unit shadow
agc_small_tiled_shadow_label: skipped, host imports are unavailable: the test block is not imported and watched
agc_storage_clear_hand_over: skipped, the device imports no watched host memory for the clear-key hand-over
agc_unit_shadow: skipped, the device does not import watched host memory for unit shadows
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_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_backtoback: 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_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_dcc: 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
Reproduce
git fetch origin f7bfe87d111a6fb161ece7d6acfa166eabfff5d1 291e02d4877784d03d346962af51713b9df63b91
git checkout 291e02d4877784d03d346962af51713b9df63b91 && git merge --no-ff f7bfe87d111a6fb161ece7d6acfa166eabfff5d1
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 12 --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 16 --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 AMD machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@winstrex

winstrex commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

@oneandonlydean Thanks, the gap is real. I reproduced it locally by pausing the mapper between UnwatchHookLocksGpu and the hook while another thread finished StorageTexture::Create for an overlapping 4 MiB image. With APS5_ASSERT_GPU_LOCK=1 the lock-order assertion fires; without it, the GPU owner and the mapper deadlock.

Fixed in f7bfe87, on top of a merge with current main (42f9605). A map-time import that runs without the GPU mutex now holds a gate over the guest range of its run from before the precheck until importAllocation returns, so the decision stays valid until the hook, and the later unwatch, complete. Publishers hold the same gate shared: Create from before its upload until the image is in Live(), and markLayersPending while a texture with no pending unit gets its first one. A texture that already has a pending unit stays visible to the precheck, so it takes no gate. Lock order: GPU, gate, imports, then Live() and pending writes.

Alternatives I rejected:

  • Keeping Live().mutex until the hook inverts the order against Create (GPU, then Live()), and the hook takes Live() again.
  • try_lock in the hook: during the race the publisher almost always holds the GPU, so the unit would be left without a hash and later stores would abort. It tolerates a stale decision instead of keeping it valid.
  • Having the hook veto and defer the import: it works where the hook runs before import(), but in the stays-watched branch the hook runs after it.

To keep the cost low:

  • The gate is split into 256 range stripes of 2 MiB, so imports and publications on disjoint ranges never wait for each other.
  • While no map-time import is active, publishers take no stripe. They announce themselves on an atomic counter, which the import drains before it locks its stripes.
  • The import computes its run under the imports mutex, takes the stripes without it, then revalidates and retries if the run grew.
  • Waits yield and back off, never spin.

Tests (all with APS5_ASSERT_GPU_LOCK=1 and a watchdog):

  • agc_map_gate_publication and agc_map_gate_pending publish in the gap and fail without the fix.
  • agc_map_gate_fast_path, agc_map_gate_disjoint and agc_map_gate_retry cover the no-import path, disjoint ranges and a run that grows while the stripes are taken.
  • agc_map_gate_stress runs Create, markLayersPending and map-time imports from several threads for a few seconds.

Each of 12 mutations of the gate (no gate, publication-only hold, inverted transition, no drain, a single global stripe, no retry, and others) fails at least one of them. The full suite on my machine has the same 4 failures as main (out of 3274). The AGC tests show no new VUID- messages under the validation layer.

Cost, measured with temporary counters (not in the PR), two runs per build in The Pathless and in Hades under the same conditions. Runs disturbed by other activity on the machine were discarded.

The Pathless, without the gate The Pathless, with the gate Hades, without Hades, with
Map-time imports without the GPU lock 1995 / 1979 2001 / 1964 3 / 3 3 / 3
Average import time 628 / 615 us 592 / 617 us 377 / 375 ms 384 / 367 ms
Publications: lock-free / striped - 138004 / 56, 140201 / 36 - 76530 / 0, 75948 / 0
Publications that waited - 0 / 0 - 0 / 0
Imports that waited for publishers - 12 (105 ms total) / 8 (71 ms) - 0 / 0
Retries - 0 / 0 - 0 / 0

No publication waited on the gate in any run, so the render threads never blocked on it. The import side waited about 9 ms per drain, on the mapping thread only, because Create holds its publication across the upload. Frame pacing is unchanged: presents per 10 s window at 80-100 s on the Pathless title menu were 496-511 / 509-510 without the gate and 478-497 / 503-511 with it, and in Hades at 110-130 s 534-536 / 501-503 without and 529-538 / 506-510 with. Given these numbers I kept the symmetric seq_cst fast path and did not add an asymmetric barrier.

TechLevelZero's Linux GPU runs of f7bfe87 merged onto current main (NVIDIA RTX 2000 Ada and AMD RX 6300 with RADV, the tests this change can reach, with the validation layer) report no new failures and no new validation IDs.

#7048 head is still 730c721773 and does not contain these commits. Would you rather this land through #10026, which already includes #7048's change, or should I keep waiting for it to be carried into #7048?

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants