Skip to content

perf(agc): refresh only the layers a use of an array color target covers - #6202

Open
MiguelAdolfsen wants to merge 1 commit into
boykopovar:mainfrom
MiguelAdolfsen:perf/array-target-layer-refresh
Open

MiguelAdolfsen wants to merge 1 commit into
boykopovar:mainfrom
MiguelAdolfsen:perf/array-target-layer-refresh

Conversation

@MiguelAdolfsen

@MiguelAdolfsen MiguelAdolfsen commented Oct 15, 2026 •

Copy link
Copy Markdown

What

Fixes #6189.

ps5-opengl declares every color target as a 2D array of two slices and allocates the next target over the second one. A color view of slice 0, a 2D texture of it and a present of it each refreshed the whole array image (StorageTexture::Refresh), so every pass:

  • stored the pending results of the next target, which only the unused second slice overlaps;
  • wrote back its own;
  • uploaded the whole image from guest memory again.

StorageTexture::RefreshLayers(first, count) refreshes only the tracked layers that hold the given array layers. It flushes and compares their memory alone, and results of other images pending over the other layers stay pending. A layer outside the range whose memory changed, or that holds another image's pending results, is left behind: StorageTexture::GenerationFor(address, bytes) reports a range over it as never current, so a later use over it takes the whole Refresh. The sampled-texture caches and the present check GenerationFor of the range they read instead of Generation(). It falls back to Refresh for volumes, single-layer and DCC images, an alias, or results of the image itself pending outside the range.

It is used by:

  • a draw into an array slice (DrawWithRecipe, and the resident lookup through the new CachedStorageLayers);
  • a 2D view of one layer of an array image (refreshForView in ShaderResources.cpp);
  • a present of one layer (VulkanDevice::PresentDisplayBuffer).

The compare memo of untracked memory (compareUntracked, #4878) now keeps four entries keyed by range, so a layer refresh's compare is memoized like the whole image's.

No switch. No change for single-layer targets.

Tested

Windows 11 Home 26200, NVIDIA GeForce RTX 4070 (driver 595.79), WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON, ANYPS5_ENABLE_SPIRV_TOOLS=OFF. Tests, homebrew runs and frame dumps at 60229fae6 on main 92e50418f.

  • agc_driver_array_layer_target gains OverlappingArrays: two two-slice arrays, the second starting at the first one's slice 1, with the second one's draw pending. A draw into slice 0 of the first array and a 2D texture sampled from that slice leave the second one's results pending; reading slice 1 of the first array then sees them, and every slice holds the right color.

    • On main the draw check fails.
    • With refreshForView refreshing the whole image, the 2D-texture check fails.
    • On Linux x86-64 with lavapipe (llvmpipe, LLVM 20.1.2), Release, with 60229fae6 merged into main 44df21632: it passes, and on main the draw check fails (run by @coltken95).
  • ctest -R agc_driver: 452 of 452 pass; agc_driver_sparse_dma_buf_import and agc_driver_stencil_ref_export are skipped, as on main.

  • tools/check_conventions.py --base origin/main: passes.

  • Three ps5-opengl homebrews, standing on their first screen without input:

    • PS5 OpenGL Showcase 1.0.2 (PPSA99005);
    • PS5MC 1.2 (PPSA99420);
    • OpenNOW PS5 Prototype 0.1 (PPSA99082).

    Each run is the performance-report capture: a 30 s warm-up run without switches, then a run with APS5_TRACE_SECONDS=15:10 only. One build tree, built at main and at this PR in turn, in the order PR, main, PR, main. Unrelated single-threaded jobs kept 2 to 3 of the 16 logical cores busy during all four runs.

    Homebrew Build Frame p50 / p90 / p99 ms Presents in 10 s GPU busy per present ms Queue worker busy Trace
    PS5 OpenGL Showcase main, 1st pair 180.6 / 210.9 / 227.2 56 70.9 27 % aps5-trace-92e50418f33b-40108.json.gz
    PS5 OpenGL Showcase main, 2nd pair 174.8 / 198.6 / 224.7 57 74.0 27 % aps5-trace-92e50418f33b-38504.json.gz
    PS5 OpenGL Showcase this PR, 1st pair 74.6 / 80.9 / 88.4 133 57.3 9 % aps5-trace-60229fae6469-22964.json.gz
    PS5 OpenGL Showcase this PR, 2nd pair 79.3 / 84.2 / 95.3 126 60.3 9 % aps5-trace-60229fae6469-45960.json.gz
    PS5MC main, 1st pair 54.3 / 67.0 / 72.9 182 10.1 76 % aps5-trace-92e50418f33b-40072.json.gz
    PS5MC main, 2nd pair 48.1 / 56.7 / 66.8 214 8.9 75 % aps5-trace-92e50418f33b-43436.json.gz
    PS5MC this PR, 1st pair 16.7 / 17.0 / 19.5 599 7.4 33 % aps5-trace-60229fae6469-40524.json.gz
    PS5MC this PR, 2nd pair 17.8 / 21.0 / 29.2 542 8.6 36 % aps5-trace-60229fae6469-22948.json.gz
    OpenNOW PS5 main, 1st pair 63.0 / 79.5 / 88.5 160 1.3 31 % aps5-trace-92e50418f33b-36020.json.gz
    OpenNOW PS5 main, 2nd pair 43.1 / 65.1 / 74.0 231 1.2 41 % aps5-trace-92e50418f33b-43152.json.gz
    OpenNOW PS5 this PR, 1st pair 28.5 / 36.5 / 41.1 341 1.2 3 % aps5-trace-60229fae6469-40800.json.gz
    OpenNOW PS5 this PR, 2nd pair 31.2 / 39.2 / 49.6 316 1.6 3 % aps5-trace-60229fae6469-4524.json.gz

    main has had perf(agc): keep Windows host imports of write-watched private memory watched #6060 since this PR was first measured: on Windows, host imports of write-watched private memory stay watched instead of being compared (its probe says yes on this machine). On main 8cd937e29, before it, the three homebrews ran at 336 / 366, 66 / 59 and 48 / 50 ms p50, and this PR at 97 / 95, 21 / 20 and 34 / 27 ms.

    Frame dumps of main and of this PR (a separate 45 s run each, APS5_DUMP_FRAMES=5, APS5_DUMP_FRAMES_EVERY=20, five frames per homebrew):

    • OpenNOW PS5: identical.
    • PS5MC: 0.012 % of bytes differ, all in the clock in the top right corner (5:51 PM and 5:57 PM).
    • PS5 OpenGL Showcase: animated by time, so the frames show different moments of the scene; both draw the whole scene, the overlay text and the bloom, with no stale or missing target.

    Summary lines (aps5-summary.jsonl; APS5_TRACE_DIR points at the run's folder):

    {"commit":"60229fae6469","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":192,"ms":10005.976},"presents":133,"frame_ms":{"p50":74.617,"p90":80.905,"p99":88.395,"max":94.287},"gpu_busy_ms_per_present":57.331,"worker_busy_pct":{"queue worker 0x0":9.033},"top_phases_ms_per_frame":{"Driver.Packet":6.336,"Driver.Packet.draw":3.534,"Driver.Draw":3.504,"Driver.Present":2.666,"Driver.Draw.draw_enqueued":2.315,"Driver.Present.render_fence_wait":2.310,"Vulkan.Retire":2.309,"Vulkan.Retire.wait":2.286},"trace_dropped":false,"trace":"aps5-trace-60229fae6469-22964.json.gz"}
    {"commit":"60229fae6469","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":800,"ms":10009.845},"presents":599,"frame_ms":{"p50":16.656,"p90":17.044,"p99":19.478,"max":30.006},"gpu_busy_ms_per_present":7.421,"worker_busy_pct":{"queue worker 0x0":32.802},"top_phases_ms_per_frame":{"Driver.Packet":5.478,"Driver.Packet.draw":4.270,"Driver.Draw":4.227,"Driver.Draw.draw_enqueued":2.080,"Driver.Present":2.045,"Driver.Draw.shader_compile_and_link":1.826,"Driver.Present.render_fence_wait":1.689,"Vulkan.Retire":1.689},"trace_dropped":false,"trace":"aps5-trace-60229fae6469-40524.json.gz"}
    {"commit":"60229fae6469","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":485,"ms":10007.120},"presents":341,"frame_ms":{"p50":28.451,"p90":36.542,"p99":41.133,"max":43.527},"gpu_busy_ms_per_present":1.211,"worker_busy_pct":{"queue worker 0x0":3.412},"top_phases_ms_per_frame":{"Driver.Present":1.395,"Driver.Present.render_fence_wait":1.057,"Vulkan.Retire":1.056,"Vulkan.Retire.wait":1.041,"Driver.Packet":0.998,"Driver.Packet.device_lock":0.766,"Driver.Present.present_display_buffer":0.200,"Driver.Packet.draw":0.117},"trace_dropped":false,"trace":"aps5-trace-60229fae6469-40800.json.gz"}
    {"commit":"92e50418f33b","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":85,"ms":10153.502},"presents":56,"frame_ms":{"p50":180.646,"p90":210.853,"p99":227.219,"max":227.219},"gpu_busy_ms_per_present":70.937,"worker_busy_pct":{"queue worker 0x0":27.297},"top_phases_ms_per_frame":{"Driver.Packet":48.954,"Driver.Packet.device_lock":28.377,"Driver.Packet.draw":19.416,"Driver.Draw":19.386,"Driver.Draw.draw_enqueued":18.707,"Graphics.Wait":5.263,"Graphics.Wait.fence_wait":5.247,"Driver.Present":3.324},"trace_dropped":false,"trace":"aps5-trace-92e50418f33b-40108.json.gz"}
    {"commit":"92e50418f33b","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":299,"ms":10025.486},"presents":182,"frame_ms":{"p50":54.267,"p90":67.012,"p99":72.934,"max":73.289},"gpu_busy_ms_per_present":10.112,"worker_busy_pct":{"queue worker 0x0":75.521},"top_phases_ms_per_frame":{"Driver.Packet":41.597,"Driver.Packet.draw":40.293,"Driver.Draw":40.244,"Driver.Draw.draw_enqueued":37.862,"Driver.Present":10.309,"Driver.Present.render_fence_wait":9.884,"Vulkan.Retire":9.883,"Vulkan.Retire.wait":9.859},"trace_dropped":false,"trace":"aps5-trace-92e50418f33b-40072.json.gz"}
    {"commit":"92e50418f33b","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":286,"ms":10023.429},"presents":160,"frame_ms":{"p50":63.023,"p90":79.537,"p99":88.469,"max":93.946},"gpu_busy_ms_per_present":1.280,"worker_busy_pct":{"queue worker 0x0":31.388},"top_phases_ms_per_frame":{"Driver.Packet":19.656,"Driver.Packet.device_lock":19.435,"Driver.Present":1.552,"Driver.Present.render_fence_wait":1.166,"Vulkan.Retire":1.165,"Vulkan.Retire.wait":1.149,"Driver.Present.present_display_buffer":0.226,"Graphics.Wait":0.180},"trace_dropped":false,"trace":"aps5-trace-92e50418f33b-36020.json.gz"}
    {"commit":"60229fae6469","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":187,"ms":10029.735},"presents":126,"frame_ms":{"p50":79.327,"p90":84.211,"p99":95.338,"max":95.992},"gpu_busy_ms_per_present":60.345,"worker_busy_pct":{"queue worker 0x0":9.334},"top_phases_ms_per_frame":{"Driver.Packet":6.933,"Driver.Packet.draw":3.812,"Driver.Draw":3.786,"Driver.Present":3.127,"Driver.Present.render_fence_wait":2.738,"Vulkan.Retire":2.736,"Vulkan.Retire.wait":2.712,"Driver.Draw.draw_enqueued":2.531},"trace_dropped":false,"trace":"aps5-trace-60229fae6469-45960.json.gz"}
    {"commit":"60229fae6469","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":762,"ms":10013.145},"presents":542,"frame_ms":{"p50":17.838,"p90":20.997,"p99":29.208,"max":34.818},"gpu_busy_ms_per_present":8.569,"worker_busy_pct":{"queue worker 0x0":36.155},"top_phases_ms_per_frame":{"Driver.Present":7.230,"Driver.Present.render_fence_wait":6.842,"Vulkan.Retire":6.841,"Vulkan.Retire.wait":6.809,"Driver.Packet":6.675,"Driver.Packet.draw":5.266,"Driver.Draw":5.215,"Driver.Draw.draw_enqueued":2.691},"trace_dropped":false,"trace":"aps5-trace-60229fae6469-22948.json.gz"}
    {"commit":"60229fae6469","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":460,"ms":10025.002},"presents":316,"frame_ms":{"p50":31.182,"p90":39.224,"p99":49.608,"max":53.253},"gpu_busy_ms_per_present":1.569,"worker_busy_pct":{"queue worker 0x0":3.124},"top_phases_ms_per_frame":{"Driver.Present":1.818,"Driver.Present.render_fence_wait":1.474,"Vulkan.Retire":1.473,"Vulkan.Retire.wait":1.458,"Driver.Packet":0.988,"Driver.Packet.device_lock":0.741,"Driver.Present.present_display_buffer":0.200,"Driver.Packet.draw":0.128},"trace_dropped":false,"trace":"aps5-trace-60229fae6469-4524.json.gz"}
    {"commit":"92e50418f33b","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":79,"ms":10069.121},"presents":57,"frame_ms":{"p50":174.829,"p90":198.616,"p99":224.671,"max":224.671},"gpu_busy_ms_per_present":74.029,"worker_busy_pct":{"queue worker 0x0":26.940},"top_phases_ms_per_frame":{"Driver.Packet":47.221,"Driver.Packet.device_lock":27.812,"Driver.Packet.draw":18.304,"Driver.Draw":18.278,"Driver.Draw.draw_enqueued":17.504,"Graphics.Wait":5.288,"Graphics.Wait.fence_wait":5.277,"Driver.Present":3.673},"trace_dropped":false,"trace":"aps5-trace-92e50418f33b-38504.json.gz"}
    {"commit":"92e50418f33b","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":285,"ms":10015.270},"presents":214,"frame_ms":{"p50":48.134,"p90":56.675,"p99":66.756,"max":70.020},"gpu_busy_ms_per_present":8.887,"worker_busy_pct":{"queue worker 0x0":75.006},"top_phases_ms_per_frame":{"Driver.Packet":35.099,"Driver.Packet.draw":33.934,"Driver.Draw":33.884,"Driver.Draw.draw_enqueued":31.524,"Driver.Present":9.020,"Driver.Present.render_fence_wait":8.694,"Vulkan.Retire":8.693,"Vulkan.Retire.wait":8.674},"trace_dropped":false,"trace":"aps5-trace-92e50418f33b-43436.json.gz"}
    {"commit":"92e50418f33b","dirty":false,"env":{"APS5_TRACE_DIR":"<path>","APS5_TRACE_SECONDS":"15:10"},"gpu":"NVIDIA GeForce RTX 4070","driver":"NVIDIA 595.79","os":"Windows 10.0.26200","resolution":"3840x2160","window":{"spec":"seconds 15:10","first_present":335,"ms":10007.967},"presents":231,"frame_ms":{"p50":43.109,"p90":65.078,"p99":74.028,"max":80.978},"gpu_busy_ms_per_present":1.185,"worker_busy_pct":{"queue worker 0x0":40.766},"top_phases_ms_per_frame":{"Driver.Packet":17.659,"Driver.Packet.device_lock":17.420,"Driver.Present":1.404,"Driver.Present.render_fence_wait":1.099,"Vulkan.Retire":1.098,"Vulkan.Retire.wait":1.087,"Driver.Present.present_display_buffer":0.181,"Driver.Packet.draw":0.131},"trace_dropped":false,"trace":"aps5-trace-92e50418f33b-43152.json.gz"}

    The traces are linked from the table above.

  • Runs by others, with 60229fae6 merged into a newer main:

    • AMD Radeon 860M (RDNA 3.5, driver 32.0.13058.8001), Windows 11 26300, on main 5d9dcc42d: PS5 OpenGL Showcase at 3.64 and 3.75 FPS on main and 5.47, 5.55 and 5.93 FPS with this PR (60 s runs; a third main run exited after 4 s on a cold shader cache). agc_driver_array_layer_target passes, and frame dumps show the same scene on both builds; both also draw a white band across the instanced cubes on this GPU (run by @LeandroLP23).
    • NVIDIA GeForce RTX 4070 Ti (driver 595.99.02), Ubuntu 24.04, on main 82ddae75b with fix(agc): drain library prebuilds before device replacement #6814 in both builds: PS5 OpenGL Showcase at 106.2 ms p50 on main and 50.0 ms with this PR. PS5MC stays at vsync on both, but this PR drops its 600 uploads and 300 storage-refresh write-backs (about 20 GiB) per 10 s (run by @milescoviello).
  • Not tested: Intel, AMD on Linux; on lavapipe only agc_driver_array_layer_target was run, and on Linux with NVIDIA only the homebrews, without frame dumps.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Unblocks: none (frame rate only; the three homebrews run on main)
  • GPUs tested: NVIDIA GeForce RTX 4070, driver 595.79, Windows 11 26200 (the tests and the homebrews); lavapipe (llvmpipe, LLVM 20.1.2), Linux x86-64 (agc_driver_array_layer_target, by @coltken95); AMD Radeon 860M, driver 32.0.13058.8001, Windows 11 26300 (the test and the Showcase, by @LeandroLP23); NVIDIA GeForce RTX 4070 Ti, driver 595.99.02, Ubuntu 24.04 (the Showcase and PS5MC, by @milescoviello)
  • AI-assisted: yes (Claude Code)

🤖 Generated with Claude Code

@github-actions github-actions Bot added the agc libSceAgc* label Oct 15, 2026
@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 15, 2026
This was referenced Oct 15, 2026
A color view of one slice of a 2D array target refreshed the whole array
image before each draw, sampled lookup and present. When the array's unused
slices lie over other live targets (ps5-opengl declares every target as a
two-slice array and allocates the next one over its second slice), each
refresh stored the other targets' pending results, wrote back its own and
uploaded the whole image from guest memory again, on every pass.

StorageTexture::RefreshLayers refreshes the tracked layers that hold the
given array layers alone: it flushes and compares their memory only, and
results of other images pending over the other layers stay pending. A layer
outside the range whose memory changed or that holds another image's pending
results is left behind; GenerationFor reports it as never current, so a use
over it takes the whole Refresh. It falls back to Refresh for volumes,
single-layer and DCC images, an alias, or results of the image pending
outside the range.

A draw into an array slice, a 2D view of one layer of an array image and a
present of one layer use it. The compare memo of untracked memory is kept
per range, so a layer refresh's compare is memoized like the whole image's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MiguelAdolfsen
MiguelAdolfsen force-pushed the perf/array-target-layer-refresh branch from 3a39b6c to 60229fa Compare October 15, 2026 16:15
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 15, 2026
@coltken95

Copy link
Copy Markdown

Tested on Linux x86-64 (Release build, lavapipe / llvmpipe LLVM 20.1.2), with 60229fa merged into main 44df216:

  • With the PR: agc_driver_array_layer_target passes.
  • With main's sources: it fails with AGC graphics: a draw into slice 0 of the first array stored the next target's results, which only its unused second slice overlaps.

AI-assisted: yes (Claude Code)

@LeandroLP23

Copy link
Copy Markdown
Contributor

An AMD run, for the iGPU side: about 1.5x on PS5 OpenGL Showcase on a Radeon 860M, no new failure.

Setup: Windows 11 Home 26300, AMD Radeon 860M (RDNA 3.5, 1002:1114, driver 32.0.13058.8001), Ryzen AI 7 350, WinLibs GCC 15.2.0, Release. main 5d9dcc42d against this head 60229fae6 merged onto it (00d8240f6, clean merge). Catalog PPSA99005 1.0.1, relinked once with --windows; both builds use the same warm shader cache. 60 s runs, no input; FPS is the emulator's window-title counter at 60 s.

Build Run FPS (frames)
main 1 exits after 4 s with exit code 1 (cold shader cache; the same happens on cold runs in my earlier catalog sweeps)
main 2 / 3 3.64 (213) / 3.75 (211)
this PR 1 / 2 / 3 5.47 (321) / 5.55 (323) / 5.93 (319)

The title's own overlay agrees: 262–273 ms per frame on main, 173–174 ms with this PR.

  • agc_driver_array_layer_target passes 3 of 3 with this PR, including OverlappingArrays.
  • Frame dumps (APS5_DUMP_FRAMES=5, APS5_DUMP_FRAMES_EVERY=20, one 45 s run each) show the same scene on both builds: grid, particles, torus, overlay text. On this GPU both builds also draw a large white band with black speckles across the middle of the screen, where the instanced cubes are. It is there on main too, so it doesn't come from this PR. I haven't looked into it yet.

Not tested: Linux, NVIDIA, other ps5-opengl titles.

AI-assisted: yes (Claude Code)

@milescoviello

Copy link
Copy Markdown
Contributor

A Linux/NVIDIA run, which the description hasn't covered: Ubuntu 24.04, RTX 4070 Ti (NVIDIA 595.99.02), GCC 13.3, Release. Head 60229fa merged onto main 82ddae7.

Both builds also have #6814. Without it these apps segfault 0 s in on a warm cache on that main (#6805).

APS5_TRACE_SECONDS=15:10, no input, 60 s runs, summary lines from aps5-summary.jsonl, [storage] lines from APS5_PROFILE_DRAW=1:

Title Build frame p50 / p90 ms presents in the window GPU busy per present queue worker busy [storage], 10 s
PPSA99005 Showcase main + #6814 106.2 / 109.7 94 53.0 ms 20.9 % 651 CPU uploads (19.9 GiB); write-backs: refresh 181, storage refresh 879, idle 40
PPSA99005 Showcase + this PR 50.0 / 51.3 201 42.0 ms 7.4 % no [storage] line printed
PPSA99420 PS5MC main + #6814 16.7 / 16.7 (vsync) 600 6.0 ms 16.7 % 600 direct uploads (10.4 GiB); 300 storage-refresh write-backs (9.3 GiB)
PPSA99420 PS5MC + this PR 16.7 / 16.7 (vsync) 600 6.0 ms 18.9 % no uploads; 1 idle write-back (31.9 MiB)

So on Linux the Showcase goes from about 9.4 to 20 FPS. PS5MC is at vsync either way, but it stops moving about 20 GiB per 10 s. OpenNOW (PPSA99082) runs to the limit with both builds, but its log goes to its own console, so there is no summary for it.

Not checked: whether the frames match (no frame dumps in these runs), AMD/Intel on Linux.

AI-assisted: yes

@screamm

screamm commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Cross-validation on Windows 11 Pro 26220, NVIDIA GeForce RTX 4070 (driver 596.21), WinLibs MinGW-w64 GCC 15.2 r7, Release. Head 60229fae6 merged on main 673f7406c (clean). Before-runs on main ca82d7d88, which differs from 673f7406c only by #6103 (libkernel).

  • Build: full build and libs pass.
  • agc_driver_array_layer_target: 3 of 3. With the six changed source files taken from main (test kept) it fails 3 of 3 with AGC graphics: a draw into slice 0 of the first array stored the next target's results, which only its unused second slice overlaps; restored, 3 of 3 again.
  • GPU tests (-j1 --timeout 300 -R '^agc_driver_'): 484 of 488 pass, 2 skipped. The two failures, agc_driver_f16_fma_denormal_modes and agc_driver_fma_mix_f16_denormal_modes, fail the same way on main here. Non-GPU suite: no real failures (5 load timeouts pass serially). check_conventions.py and git diff --check pass.

Titles, 60 s each, no input, order PR / main / PR / main, APS5_PROFILE_DRAW=1 APS5_PROFILE_GPU=1 APS5_TRACE_SUMMARY_EVERY=10. Medians over the five 10 s summary lines of each run; storage-upload / storage-writeback are the [gputime] classes per present.

Title Build Frames Presents / 10 s Frame p50 ms GPU busy / present storage-upload / present storage-writeback / present
PPSA99005 Showcase this PR 716 / 637 119 / 109 82.7 / 83.7 53.2 / 60.6 ms 0 after start-up 0.00 / 0.03 ms
PPSA99005 Showcase main 425 / 343 75 / 63 124.6 / 141.9 48.7 / 53.3 ms 2 uploads, 0.48 / 0.56 ms, 25 MiB 1.21 / 1.46 ms, 152 / 175 MiB
PPSA99420 PS5MC this PR 1640 / 1500 394 / 327 22.7 / 23.6 7.6 / 10.4 ms 0 after start-up 0.00 / 0.03 ms
PPSA99420 PS5MC main 1339 / 1640 290 / 350 27.0 / 26.6 10.2 / 11.2 ms 1 upload, 0.18 ms, 17.75 MiB 0.13 ms, 16 MiB

None of the eight runs printed a stop line (all were still running at 60 s).

The [storage] lines show where main loses the time. For the Showcase on main, every 10 s: 144-156 GPU-direct uploads (1.8-2.0 GiB), 503-545 CPU-path uploads (15.3-16.6 GiB), 113-139 refresh write-backs and 633-716 storage-refresh write-backs. With this PR, after the 16 first uploads (349 MiB) at start-up, only idle write-backs are left. For PS5MC on main: 290-361 direct uploads (5.1-6.4 GiB) and 144-179 storage-refresh write-backs (4.6-5.7 GiB) per 10 s. With this PR: none after start-up.

Most of this is CPU-side. [gputime] times only the GPU-direct uploads (about 0.5 ms per present for the Showcase on main), and [present] shows the frame time difference as idle gaps: 78.8 ms per frame on main against 19.4 ms with this PR, with GPU busy at 48.5 and 45.4 ms. On this machine GPU busy per present is not lower with this PR (the draw class is 26.5 / 29.8 ms against 17.3 / 17.5 ms on main). I haven't looked into why. The host was shared and the GPU clocks were not pinned. PS5MC runs at 35-44 FPS here, not at vsync, so its frame-time gain is small.

Frame dumps (12 per run, every 30 presents): no black frame. The Showcase's mean brightness is 78-116 with this PR and 83-121 on main. It is animated, so I can't compare pixels between runs. PS5MC: every dump has mean 31.5. Between this PR and main 0.02-0.03 % of pixels differ, which is less than between two main runs (0.05 %).

Not checked: AMD or Intel, Linux, OpenNOW, a count of refreshed layers (this PR doesn't log one), an idle host.

Written with Claude Code assistance (validation by a subagent, reviewed in this session).

@milescoviello

Copy link
Copy Markdown
Contributor

Linux data for this head (60229fa), from OliSe Player (PPSA01153). It's a single-preset copy with Rovastar's Dark Ritual, the preset whose two-slice 1920x1080 targets screamm measured on Windows in #6530. Square once, 120 s, APS5_PROFILE_DRAW=1 APS5_PROFILE_GPU=1. Ubuntu 24.04, RTX 4070 Ti (NVIDIA 595.99), Release. The merged-coverage draws need #6530, so both builds are main 40315fc + #6530, with and without this PR, 2 runs each. The rows are 10 s intervals after the first one:

without this PR with this PR
[storage] CPU uploads per 10 s 130–134 (2.19–2.26 GiB, the whole 16.875 MiB image each time; flushed 72–74, store 57–60) 0
write-backs per 10 s: refresh / storage refresh 130–134 / 72–75 0 / 0
write-backs per 10 s: linear target 86–90 113–116
presents per 10 s 44–45 57–58
memory no longer registered; 135 pending ranges dropped 1–2 per run 1–2 per run

Per present, the linear-target write-backs stay at 2 (88/44.5 before, 115/57.5 after). On Linux, the CPU round trip of the whole two-slice image goes away entirely, and the visualiser runs about 29% faster. On Windows, screamm saw this PR remove the refresh write-backs, but the untracked uploads stayed. Linux has no untracked uploads in either build, so the remainder there is the Windows path that compares resident targets against guest memory instead of write-tracking them.

AI-assisted: yes (Claude Code)

@karanvora2599

Copy link
Copy Markdown
Contributor

A data point from Windows, on a different machine and GPU. PS5 OpenGL Showcase (PPSA99005), RTX 4060 Laptop, driver 591.86, APS5_TRACE_SECONDS=15:10, APS5_SPIRV_OPT=none. This PR at 60229fa was merged onto main 51cee90 locally, without conflicts. The 45 s runs were interleaved; the runs of this PR came in a second round, after a warm-up of their own.

Build Frame p50 / p90 / p99 ms Presents in 10 s GPU busy per present ms Trace
main 51cee90 194.7 / 199.9 / 202.2 52 120.6 run01-main-aps5-trace-51cee90311b4-5516.json.gz
main 51cee90 196.6 / 200.1 / 202.7 51 121.3 run04-main-aps5-trace-51cee90311b4-32156.json.gz
this PR 96.4 / 100.7 / 103.4 104 83.9 run07-pr6202-aps5-trace-8864cfdf3cb1-22948.json.gz
this PR 97.8 / 102.5 / 108.7 103 84.9 run09-pr6202-aps5-trace-8864cfdf3cb1-22216.json.gz

The title's own counter goes from about 5.0 fps to 9.7. agc_driver_array_layer_target passes on the merge.

Together with #6486 it is 46 ms and about 19.5 fps; that table is in #6486.

@github-actions github-actions Bot added perf Performance change needs rebase Conflicts with main, rebase needed labels Oct 18, 2026

This branch has not been deployed

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

Labels

agc libSceAgc* fix error Closes an issue (Fixes #N) needs rebase Conflicts with main, rebase needed perf Performance change

Projects

None yet

7 participants