Skip to content

perf(agc): read a large watched texture straight into its upload staging buffer - #10845

Closed
milescoviello wants to merge 1 commit into
boykopovar:mainfrom
milescoviello:perf/texture-upload-without-snapshot
Closed

milescoviello wants to merge 1 commit into
boykopovar:mainfrom
milescoviello:perf/texture-upload-without-snapshot

Conversation

@milescoviello

@milescoviello milescoviello commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

What

Since #6982, a sampled-texture snapshot larger than 256 KiB that the write tracker covers keeps no host copy of its guest bytes. The tracker covers it when it has a write generation, or an untracked key with no write pending. The bytes were still read into a vector first, then Texture's constructor copied the vector into the upload's staging buffer, and then the vector was dropped (since #8026, parked in the spare pool). This change reads the guest bytes straight into the staging buffer. That saves one full copy of the surface per upload, and the vector allocation (zero-filled) whenever the spare pool has no buffer of that size.

Forspoken's opening has a texture-heavy stretch where the draw committer is about 90 % busy and three quarters of the process's CPU samples are under cachedTexture. There, the share in memmove falls from 20.0 % to 15.4 % (profiles under Tested).

How

  • Texture gets a constructor that takes the staging Buffer. The span constructor makes the staging buffer from its span and delegates, so its callers are unchanged.
  • cachedTexture decides before the read whether the snapshot will be kept. The rule is perf(agc): drop the guest byte snapshot of a large cached texture the write tracker covers #6982's: uncompressed keys, more than 256 KiB, and a generation or an untracked key with no write pending. When it won't be kept, the surface is read into a pooled host staging buffer and the texture is created from that buffer. No snapshot vector is made, so none is parked.
  • DCC clear and single-key surfaces keep the vector path. ExpandSingleBlocks reads back from the span it fills, and the staging memory is host-coherent memory that may be write-combined. Those surfaces still park their dropped buffer as before. The spare pool still serves them, and entries that a storage-image view replaces.
  • copyCommitted writes every byte of its destination (zeroes, then the committed ranges), so a recycled pool buffer can't leak stale bytes into the texture, the invariant perf(agc): reuse sampled snapshot byte buffers across cache entry replacement #8026 relies on for its spares.

Tested

Linux: Ubuntu 26.04.1, GCC 15.2, Release, RTX 4070 Ti, NVIDIA 615.71.09, on main 52a8bed.

  • Red/green: recycleTests now requires a 1 MiB snapshot of write-watched memory to leave the spare pool empty, since no host copy is made. With only the test changes on main (4551d4f9b4), agc_driver_sampled_cache_budget and agc_driver_sampled_cache_budget_without_storage_image_multisample fail 3 of 3 with a big snapshot of watched memory made a 1048576-byte host copy and parked it, instead of reading guest memory into its staging buffer. With this change both pass 3 of 3.

  • New cases:

    • agc_driver_large_tracked_snapshot now also reads its 512x512 texture back on the GPU, through a texel fetch. Detiling only permutes 32-bit words, so the sorted texels must equal the sorted words of the surface, with zeros for uncommitted pages. It runs once with the surface committed and once with a single 64 KiB block committed. The second run reuses staging buffers the first one filled, so a staging buffer that kept stale bytes would fail it.
    • The texel fetch helpers move out of the Windows-only block and dispatch one group per texel in two dimensions, so 512x512 stays within 65535 groups per dimension.
    • spareAgeTests keeps its age bounds: a spare idle for 17 presents is released, one idle for 16 is kept, and a same-size snapshot takes it. Its spare now comes from a 1 MiB snapshot that a view of its host-imported storage image replaces, the pool's other source.
  • Focused tests: the 29 tests matching sampled|snapshot|texture|dcc|detile|^agc_driver_graphics$|staged_clear pass 3 of 3.

  • Windows 11 VM (GT 1030, driver 582.78, WinLibs GCC 15.2.0 r7), the same regex:

    • 31 of the 32 tests pass 3 of 3, on the merge base and on this head alike.
    • The other, agc_driver_sampled_cache_budget_memory, fails the same way on both: it expects 1024 MiB of storage-image budget, but the GT 1030's heap (about 1966 MiB) caps it at 983 MiB, half the heap.
  • Khronos validation 1.4.363 over the same 29, each arm run as VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 ctest -R "$R" -V -j4: 29 of 29 pass on main 52a8bed and on this head. Neither log has a layer-missing line or a VUID- message.

  • Forspoken (PPSA03984) CPU profiles: release libraries, warm caches, 16G, pad input, no trace or profile switch. The PR side was built from 83334037e1, whose library code is this head's; later commits changed only tests. perf was attached about 10 s in, then recorded DWARF stacks at 299 Hz from 190 s to 210 s. Shares are of all the process's samples:

    main this PR
    __memmove_avx_unaligned_erms (self) 20.02 % 15.39 %
    Texture::Texture (inclusive) 59.53 % 55.22 %
    ReadTextureSurface (inclusive) 8.78 % 13.72 %
    cachedTexture (inclusive) 76.92 % 76.03 %

    ReadTextureSurface grows because it now writes the staging memory itself. The copy that goes away is the one Texture::Texture made from the vector. That's one profile per side. Each side had a second profile run that stopped before the window, on the depth-plane refusal noted below.

  • Depth-plane stop (not this change): two Forspoken runs stopped with sampling the depth plane of depth surface ... (1920x1080, vk format 130) as a 1920x1080 texture of guest format 22 (vk 100), tile mode 7, dimension 1, levels 0-4, slice 0 is not implemented: one on main (179 s), one on this PR (101 s). It comes from DepthSurfaceTexture, before the snapshot path this PR changes. I'll follow it up separately.

Performance captures

Forspoken (PPSA03984), APS5_TRACE_SECONDS=130:60, warm caches, same machine (Linux, RTX 4070 Ti, NVIDIA 615.71.09). A warm-up run of main came first, then three runs per side, interleaved. main run 2 hit the 16G memory cap at 188 s and left no capture. The window holds only 6 to 9 presents of 6 to 12 s each, and both sides get faster from run to run as caches warm:

  • main: 6 and 7 presents, p50 11.3 and 8.3 s.
  • This PR: 6, 7 and 9 presents, p50 12.1, 8.6 and 6.6 s.

So I don't claim a frame-time change; the profiles above are the measurement. The last pair is below. All runs, their traces, digests and summary lines are in the release on my fork.

main 52a8bed (run main-3):

{"commit":"52a8bed576e5","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_SECONDS":"130:60"},"gpu":"NVIDIA GeForce RTX 4070 Ti","driver":"NVIDIA 615.71.09","os":"Linux 7.0.0-38-generic","cpu":"13th Gen Intel(R) Core(TM) i7-13700K","cpu_threads":24,"memory_mib":30993,"title_id":"PPSA03984","title":"Forspoken","content_version":"01.020.000","resolution":"1920x1080","window":{"spec":"seconds 130:60","first_present":560,"ms":62882.190},"presents":7,"frame_ms":{"p50":8267.748,"p90":10903.792,"p99":10903.792,"max":10903.792},"gpu_busy_ms_per_present":3324.257,"worker_busy_pct":{"queue worker 0x0":99.955,"queue worker 0x20":99.999,"queue worker 0x28":100.000},"top_phases_ms_per_frame":{"Driver.Packet":26911.412,"Driver.Packet.memory_wait":17661.471,"Graphics.Draw":8221.968,"Graphics.Draw.draw_and_resource_release":8162.619,"Resources.Complete":8102.600,"Driver.Packet.draw":7787.542,"Driver.Draw":7784.785,"Driver.Draw.draw_enqueued":7595.656},"trace_dropped":false,"trace":"aps5-trace-52a8bed576e5-3.json.gz"}
tools/trace_digest.py

seconds 130:60 from present 560: 7 presents, frame p50 8267.7 / p90 10903.8 / p99 10903.8 / max 10903.8 ms, GPU busy 3324.3 ms per present
NVIDIA GeForce RTX 4070 Ti, NVIDIA 615.71.09, Linux 7.0.0-38-generic, 1920x1080, commit 52a8bed, dropped: no

{"commit":"52a8bed576e5","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_SECONDS":"130:60"},"gpu":"NVIDIA GeForce RTX 4070 Ti","driver":"NVIDIA 615.71.09","os":"Linux 7.0.0-38-generic","cpu":"13th Gen Intel(R) Core(TM) i7-13700K","cpu_threads":24,"memory_mib":30993,"title_id":"PPSA03984","title":"Forspoken","content_version":"01.020.000","resolution":"1920x1080","window":{"spec":"seconds 130:60","first_present":560,"ms":62882.19},"presents":7,"frame_ms":{"p50":8267.748,"p90":10903.792,"p99":10903.792,"max":10903.792},"gpu_busy_ms_per_present":3324.257,"worker_busy_pct":{"queue worker 0x0":99.955,"queue worker 0x20":99.999,"queue worker 0x28":100.0},"top_phases_ms_per_frame":{"Driver.Packet":26911.412,"Driver.Packet.memory_wait":17661.471,"Graphics.Draw":8221.968,"Graphics.Draw.draw_and_resource_release":8162.619,"Resources.Complete":8102.6,"Driver.Packet.draw":7787.542,"Driver.Draw":7784.785,"Driver.Draw.draw_enqueued":7595.656},"trace_dropped":false,"trace":"aps5-trace-52a8bed576e5-3.json.gz"}

Slowest frames (largest slices inside each, inclusive)

  • present 561: 10903.8 ms at 9714 ms: Driver.Packet (queue worker 0x28) 10903.6, Driver.Packet (queue worker 0x20) 10902.7, Driver.Packet.memory_wait (queue worker 0x28) 10851.0, Driver.Packet (queue worker 0x0) 10834.4
  • present 560: 9713.7 ms at 0 ms: Driver.Packet (queue worker 0x20) 9711.2, Driver.Packet (queue worker 0x28) 9710.8, Driver.Packet.memory_wait (queue worker 0x28) 9672.9, Driver.Packet (queue worker 0x0) 9639.5
  • present 564: 9646.9 ms at 36763 ms: Driver.Packet (queue worker 0x20) 9645.7, Driver.Packet (queue worker 0x0) 9588.2, Driver.Packet (queue worker 0x28) 9576.1, Driver.Packet.memory_wait (queue worker 0x28) 9537.7
  • present 565: 8267.7 ms at 46410 ms: Driver.Packet (queue worker 0x20) 8266.8, Driver.Packet (queue worker 0x28) 8210.9, Driver.Packet (queue worker 0x0) 8204.1, Driver.Packet.memory_wait (queue worker 0x28) 8136.4
  • present 566: 8204.1 ms at 54678 ms: Driver.Packet (queue worker 0x0) 8141.0, Driver.Packet (queue worker 0x20) 7990.4, Driver.Packet.memory_wait (queue worker 0x20) 7751.4, Graphics.Draw (draw committer) 7433.2

Threads (ms per frame)

thread busy wait untraced in slices < 20 us largest
queue worker 0x0 8830.6 (98 %) 89.5 (1 %) 63.1 372.8 Driver.Packet 8919.8, Driver.EndSubmission 0.3
draw committer 8201.0 (91 %) 157.7 (2 %) 624.5 121.0 Graphics.Draw 8175.3, Recorder.Submit 22.4
title 0.6 (0 %) 2.3 (0 %) 8980.3 11.4 Shader.ResolveGraphicsStagesAbi 0.2, Shader.ResolveGraphicsAbi 0.2
queue worker 0x20 141.2 (2 %) 8810.3 (98 %) 31.6 3.7 Driver.Packet 8951.6, Driver.EndSubmission 0.0
queue worker 0x28 73.2 (1 %) 7947.5 (88 %) 962.5 1.3 Driver.Packet 7987.2, Driver.EndSubmission 33.6
presenter 1.3 (0 %) 0.0 (0 %) 8981.9 0.1 VideoOut.Flip 1.2, Driver.Present 0.1
gpu 3165.2 (35 %) 0.0 (0 %) 5818.0 0.0 batch 3165.2

Largest slices (ms per frame, inclusive, all threads): Driver.Packet 25858.5, Driver.Packet.memory_wait 16668.6, Graphics.Draw 8176.3, Graphics.Draw.draw_and_resource_release 8113.0, Resources.Complete 8101.1, Driver.Packet.draw 7746.4, Driver.Draw 7743.3, Driver.Draw.draw_enqueued 7565.4, batch 3165.2, Driver.Packet.pipeline_drain 750.2

Longest waits

  • Driver.Packet.memory_wait (queue worker 0x28) 7446.7 ms at 13172 ms, present 561
  • Driver.Packet.memory_wait (queue worker 0x28) 7131.5 ms at 2586 ms, present 560
  • Driver.Packet.memory_wait (queue worker 0x20) 6974.5 ms at 13488 ms, present 561
  • Driver.Packet.memory_wait (queue worker 0x28) 6850.1 ms at 47830 ms, present 565
  • Driver.Packet.memory_wait (queue worker 0x28) 6789.7 ms at 29976 ms, present 563
  • Driver.Packet.memory_wait (queue worker 0x28) 6726.8 ms at 21962 ms, present 562
  • Driver.Packet.memory_wait (queue worker 0x28) 6698.7 ms at 39713 ms, present 564
  • Driver.Packet.memory_wait (queue worker 0x20) 6523.0 ms at 2954 ms, present 560
  • Driver.Packet.memory_wait (queue worker 0x20) 6522.5 ms at 47984 ms, present 565
  • Driver.Packet.memory_wait (queue worker 0x20) 6385.2 ms at 30082 ms, present 563

Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)

  • batch 477: 763.8 ms at 10641 ms, present 561: GPU no ranges; recorded by Graphics.Draw (draw committer) 94.3, Graphics.Draw.draw_and_resource_release (draw committer) 94.2, Resources.Complete (draw committer) 93.6, Resources.Prepare (draw committer) 0.0
  • batch 493: 762.1 ms at 12266 ms, present 561: GPU no ranges; recorded by Graphics.Draw (draw committer) 173.5, Graphics.Draw.draw_and_resource_release (draw committer) 173.0, Resources.Complete (draw committer) 168.9, Resources.Prepare (draw committer) 0.4
  • batch 1559: 751.5 ms at 38466 ms, present 564: GPU no ranges; recorded by Graphics.Draw (draw committer) 162.8, Graphics.Draw.draw_and_resource_release (draw committer) 162.2, Resources.Complete (draw committer) 156.8, Resources.Prepare (draw committer) 0.6
  • batch 90: 737.1 ms at 874 ms, present 560: GPU no ranges; recorded by Graphics.Draw (draw committer) 96.1, Graphics.Draw.draw_and_resource_release (draw committer) 96.0, Resources.Complete (draw committer) 95.4, Resources.Prepare (draw committer) 0.0
  • batch 1557: 405.9 ms at 37758 ms, present 564: GPU no ranges; recorded by Graphics.Draw (draw committer) 44.6, Graphics.Draw.draw_and_resource_release (draw committer) 43.7, Resources.Complete (draw committer) 39.9, Resources.Prepare (draw committer) 0.2

This PR, 83334037e1 (the head's library code, run pr-3):

{"commit":"83334037e1d2","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_SECONDS":"130:60"},"gpu":"NVIDIA GeForce RTX 4070 Ti","driver":"NVIDIA 615.71.09","os":"Linux 7.0.0-38-generic","cpu":"13th Gen Intel(R) Core(TM) i7-13700K","cpu_threads":24,"memory_mib":30993,"title_id":"PPSA03984","title":"Forspoken","content_version":"01.020.000","resolution":"1920x1080","window":{"spec":"seconds 130:60","first_present":582,"ms":60562.854},"presents":9,"frame_ms":{"p50":6635.682,"p90":7383.433,"p99":7383.433,"max":7383.433},"gpu_busy_ms_per_present":2350.433,"worker_busy_pct":{"queue worker 0x0":99.943,"queue worker 0x28":90.692,"queue worker 0x20":99.717},"top_phases_ms_per_frame":{"Driver.Packet":19485.847,"Driver.Packet.memory_wait":12527.982,"Graphics.Draw":6235.515,"Graphics.Draw.draw_and_resource_release":6175.233,"Resources.Complete":6113.778,"Driver.Packet.draw":5701.221,"Driver.Draw":5698.727,"Driver.Draw.draw_enqueued":5523.932},"trace_dropped":false,"trace":"aps5-trace-83334037e1d2-3.json.gz"}
tools/trace_digest.py

seconds 130:60 from present 582: 9 presents, frame p50 6635.7 / p90 7383.4 / p99 7383.4 / max 7383.4 ms, GPU busy 2350.4 ms per present
NVIDIA GeForce RTX 4070 Ti, NVIDIA 615.71.09, Linux 7.0.0-38-generic, 1920x1080, commit 83334037e1d2, dropped: no

{"commit":"83334037e1d2","dirty":false,"env":{"APS5_PAD_INPUT_FILE":"<path>","APS5_TRACE_SECONDS":"130:60"},"gpu":"NVIDIA GeForce RTX 4070 Ti","driver":"NVIDIA 615.71.09","os":"Linux 7.0.0-38-generic","cpu":"13th Gen Intel(R) Core(TM) i7-13700K","cpu_threads":24,"memory_mib":30993,"title_id":"PPSA03984","title":"Forspoken","content_version":"01.020.000","resolution":"1920x1080","window":{"spec":"seconds 130:60","first_present":582,"ms":60562.854},"presents":9,"frame_ms":{"p50":6635.682,"p90":7383.433,"p99":7383.433,"max":7383.433},"gpu_busy_ms_per_present":2350.433,"worker_busy_pct":{"queue worker 0x0":99.943,"queue worker 0x28":90.692,"queue worker 0x20":99.717},"top_phases_ms_per_frame":{"Driver.Packet":19485.847,"Driver.Packet.memory_wait":12527.982,"Graphics.Draw":6235.515,"Graphics.Draw.draw_and_resource_release":6175.233,"Resources.Complete":6113.778,"Driver.Packet.draw":5701.221,"Driver.Draw":5698.727,"Driver.Draw.draw_enqueued":5523.932},"trace_dropped":false,"trace":"aps5-trace-83334037e1d2-3.json.gz"}

Slowest frames (largest slices inside each, inclusive)

  • present 588: 7383.4 ms at 39727 ms: Driver.Packet (queue worker 0x20) 7382.7, Driver.Packet (queue worker 0x28) 7366.7, Driver.Packet (queue worker 0x0) 7329.8, Driver.Packet.memory_wait (queue worker 0x20) 7232.6
  • present 589: 6817.1 ms at 47110 ms: Driver.Packet (queue worker 0x20) 6816.1, Driver.Packet (queue worker 0x28) 6759.8, Driver.Packet (queue worker 0x0) 6753.5, Driver.Packet.memory_wait (queue worker 0x28) 6709.2
  • present 583: 6744.8 ms at 6636 ms: Driver.Packet (queue worker 0x20) 6743.6, Driver.Packet (queue worker 0x28) 6700.0, Driver.Packet (queue worker 0x0) 6677.9, Driver.Packet.memory_wait (queue worker 0x28) 6677.4
  • present 584: 6646.0 ms at 13380 ms: Driver.Packet (queue worker 0x20) 6644.7, Driver.Packet (queue worker 0x28) 6617.5, Driver.Packet (queue worker 0x0) 6590.4, Driver.Packet.memory_wait (queue worker 0x28) 6588.9
  • present 582: 6635.7 ms at 0 ms: Driver.Packet (queue worker 0x20) 6630.9, Driver.Packet (queue worker 0x0) 6563.2, Driver.Packet (queue worker 0x28) 6555.2, Driver.Packet.memory_wait (queue worker 0x28) 6525.1

Threads (ms per frame)

thread busy wait untraced in slices < 20 us largest
queue worker 0x0 6563.5 (98 %) 103.7 (2 %) 62.0 369.1 Driver.Packet 6667.0, Driver.EndSubmission 0.2
draw committer 6224.6 (93 %) 137.9 (2 %) 366.7 127.1 Graphics.Draw 6185.9, Recorder.Submit 35.2
title 0.2 (0 %) 1.3 (0 %) 6727.7 10.7 Shader.ResolveGraphicsStagesAbi 0.1, Shader.ResolveGraphicsAbi 0.1
queue worker 0x28 77.8 (1 %) 6024.9 (90 %) 626.5 1.4 Driver.Packet 6050.4, Driver.EndSubmission 52.3
queue worker 0x20 127.6 (2 %) 6581.5 (98 %) 20.1 4.1 Driver.Packet 6709.1, Driver.EndSubmission 0.0
presenter 1.7 (0 %) 0.0 (0 %) 6727.5 0.1 VideoOut.Flip 1.7, Driver.Present 0.1
gpu 2367.0 (35 %) 0.0 (0 %) 4362.2 0.0 batch 2367.0

Largest slices (ms per frame, inclusive, all threads): Driver.Packet 19426.6, Driver.Packet.memory_wait 12528.0, Graphics.Draw 6186.7, Graphics.Draw.draw_and_resource_release 6122.8, Resources.Complete 6112.2, Driver.Packet.draw 5659.5, Driver.Draw 5656.5, Driver.Draw.draw_enqueued 5492.7, batch 2367.0, Driver.Packet.pipeline_drain 716.5

Longest waits

  • Driver.Packet.memory_wait (queue worker 0x28) 5834.9 ms at 14196 ms, present 584
  • Driver.Packet.memory_wait (queue worker 0x28) 5804.9 ms at 48124 ms, present 589
  • Driver.Packet.memory_wait (queue worker 0x28) 5659.1 ms at 41456 ms, present 588
  • Driver.Packet.memory_wait (queue worker 0x28) 5644.3 ms at 993 ms, present 582
  • Driver.Packet.memory_wait (queue worker 0x28) 5625.7 ms at 21005 ms, present 585
  • Driver.Packet.memory_wait (queue worker 0x28) 5621.7 ms at 7760 ms, present 583
  • Driver.Packet.memory_wait (queue worker 0x28) 5612.4 ms at 27560 ms, present 586
  • Driver.Packet.memory_wait (queue worker 0x28) 5551.0 ms at 34177 ms, present 587
  • Driver.Packet.memory_wait (queue worker 0x20) 5506.6 ms at 14327 ms, present 584
  • Driver.Packet.memory_wait (queue worker 0x20) 5416.8 ms at 48341 ms, present 589

Largest GPU batches (GPU ranges inside, and the CPU slices that recorded each, inclusive)

  • batch 393: 97.9 ms at 6983 ms, present 583: GPU no ranges; recorded by Graphics.Draw (draw committer) 125.8, Graphics.Draw.draw_and_resource_release (draw committer) 125.1, Resources.Complete (draw committer) 122.5, Resources.Prepare (draw committer) 0.3
  • batch 2593: 97.5 ms at 47960 ms, present 589: GPU no ranges; recorded by Driver.Packet.pipeline_drain (queue worker 0x0) 156.5, Driver.Packet (queue worker 0x0) 88.5, Driver.Packet.draw (queue worker 0x0) 0.1, Driver.Draw (queue worker 0x0) 0.1
  • batch 2947: 96.1 ms at 54774 ms, present 590: GPU no ranges; recorded by Driver.Packet.pipeline_drain (queue worker 0x0) 157.7, Driver.Packet (queue worker 0x0) 89.5, Driver.Packet.draw (queue worker 0x0) 0.1, Driver.Draw (queue worker 0x0) 0.1
  • batch 1876: 88.8 ms at 33983 ms, present 587: GPU no ranges; recorded by Driver.Packet.pipeline_drain (queue worker 0x0) 157.1, Driver.Packet (queue worker 0x0) 96.1, Driver.Packet.draw (queue worker 0x0) 0.1, Driver.Draw (queue worker 0x0) 0.1
  • batch 1521: 70.9 ms at 27387 ms, present 586: GPU no ranges; recorded by Driver.Packet.pipeline_drain (queue worker 0x0) 143.6, Driver.Packet (queue worker 0x0) 87.2, Driver.Packet.draw (queue worker 0x0) 0.1, Driver.Draw (queue worker 0x0) 0.1

Overlap (tools/pr_overlap.py, and git merge-tree of each head with this one):

Checklist

🤖 Generated with Claude Code

…ing buffer

Since 6849eea, a sampled-texture snapshot over 256 KiB that the write
tracker covers keeps no host copy. Its bytes were still read into a
vector, which Texture's constructor copied into the upload's staging
buffer before the vector was parked in the spare pool. Such a surface
with uncompressed keys is now read straight into the pooled host staging
buffer, and the texture is created from it. That saves one copy of the
whole surface per upload.

DCC clear and single-key surfaces keep the vector: ExpandSingleBlocks
reads back from the span it fills, and the staging memory may be
write-combined.

recycleTests now requires a big watched snapshot to leave the spare pool
empty; on main it fails with the 1 MiB host copy parked. spareAgeTests
feeds the pool with a snapshot that a view of its imported storage image
replaces. The large tracked snapshot test also reads its 512x512 texture
back on the GPU and requires the texels to be the surface's words, with
zeros for uncommitted pages; the partially resident pass reuses staging
buffers the committed pass filled. The texel fetch helpers leave the
Windows-only block and dispatch one group per texel in two dimensions,
so 512x512 stays within the 65535 groups a dimension may hold.

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.

I reviewed head 5513eb7d6557bb7e3c49ad118c7d291cedf33a1d and its three-dot patch against current main.

The large uncompressed snapshot path reads into the same host-visible staging buffer later copied to the tiled device buffer. ReadTextureSurface uses ReadCommitted, preserving zero-filled uncommitted ranges; the cache only selects this path when the snapshot is tracker-covered and no untracked write is pending. The recorder retains the staging buffer through command completion; the synchronous path submits and waits before returning. DCC clear and ClearSingle remain on the vector path, which avoids the write-combined staging readback concern described in the PR. The new texel test checks both committed and partially resident large surfaces and exercises reused staging storage.

Local checks on this exact patch: git diff --check upstream/main...upstream/pr-10845 and python3 tools/check_conventions.py --base upstream/main --head upstream/pr-10845 pass. I could not run the product tests here; hosted product build/test jobs are skipped by repository policy. The Linux/Windows/Vulkan-validation runs and profiles in the description are author-reported, not independently reproduced in this review.

I found no code blocker in the current diff. I am leaving this as a comment rather than approving because CONTRIBUTING.md requires authors to agree on merge order for overlapping PRs and record the ordering in Depends on. The current checklist says Depends on: none, while the overlap report names #10773, #7085 and #10064. The proposed order for #10773 and #7085 has not yet received confirmation from those authors. #10064 is a draft and its author has been asked about the opposite ordering; the !entry.slabs exclusion and constructor forwarding described in that thread will also need to be retained when the branches are reconciled. Please update the checklist once the affected authors have agreed on the order.

AI-assisted review: yes.

@github-actions github-actions Bot added the agc libSceAgc* label Oct 25, 2026
@milescoviello

Copy link
Copy Markdown
Contributor Author

@MarcZX10 thanks for the review. I've asked all three authors (#10773, #7085, #10064). The checklist now records the proposal, and I'll turn it into Depends on once they answer. If #10064 lands first, I'll keep slab snapshots off the staged path and forward packedSlabs through the staging constructor when I rebase.

AI-assisted: yes (Claude Code)

@github-actions

github-actions Bot commented Oct 25, 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 7085 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L361
PR 9884 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp L266-267, L974-976, L1007, L1012-1013, L1015, L1017
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L337, L361, L417-418
core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp L696-699, L706, L708-710, L717-720, L722-723
PR 10064 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp L62
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp L974-976, L1007, L1015
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L337, L361, L417-420
PR 10773 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp L416-420

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/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 1395 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 4448 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 5021 core/libs/prx/libSceAgcDriver/tests/execution/SampledCacheBudget.cpp
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6187 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6192 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6194 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6197 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6209 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6223 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6280 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6412 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 6481 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6539 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6960 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6971 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7048 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7449 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
PR 7484 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 7604 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7642 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7689 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 8246 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 8484 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 8998 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9176 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9287 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9289 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9431 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9432 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9508 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9592 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 9672 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9673 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9674 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9675 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9676 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9842 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9852 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 10193 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 10249 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 10435 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 10442 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderResources.cpp
PR 10886 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp

@wachi35676

Copy link
Copy Markdown
Contributor

Windows run on NVIDIA and an AMD UMA iGPU. Windows 11 10.0.26300, WinLibs GCC 15.2.0, Release; main aeff26b and this PR (5513eb7) merged onto it (clean); ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 (no validation layer installed here); NVIDIA RTX 5070 Ti (621.01) and AMD Radeon RDNA2 iGPU (AMD proprietary 2026.04.17).

  • The snapshot and sampled-cache tests (15 entries: the three agc_driver_sampled_cache_budget*, agc_driver_large_tracked_snapshot with its GPU read-back, agc_driver_partially_resident_snapshot, agc_snapshot_copy_failure, ...): 15/15 on both GPUs.
  • Control: with main's Texture.hpp/.cpp and ShaderResources.cpp, agc_driver_sampled_cache_budget and _without_storage_image_multisample fail on both with a big snapshot of watched memory made a 1048576-byte host copy and parked it, instead of reading guest memory into its staging buffer.
  • A wider set of 257 agc_ entries, every executable rebuilt, has the same failures with and without the change: agc_host_import_cut_lifetime and agc_driver_depth_plane_partial_store.

AI-assisted: yes (Claude Code); results above were run on this machine.

@milescoviello

Copy link
Copy Markdown
Contributor Author

@wachi35676 thank you, especially for the control with main's files: it shows the new assertion catches the old behaviour on AMD too. I've added your run to Tested.

AI-assisted: yes (Claude Code)

@oneandonlydean oneandonlydean self-assigned this Oct 26, 2026
@oneandonlydean

Copy link
Copy Markdown
Collaborator

@milescoviello Thank you for this, and for how thoroughly you tested it. #9884 makes the same change with the same rule in the same function and was opened first, so I'm closing this one as a duplicate of #9884.

Your evidence isn't lost: I've summarized it on #9884 with a link back here and credited you (the red/green on the sampled-cache budget tests, the GPU read-back of the large tracked snapshot, the validation run, the CPU profiles, @wachi35676's Windows run with its control, and the merge-order agreements with #10773, #10064 and #7085). If you think something in your version is worth having on top of #9884, such as the read-back test, please say so there.

@milescoviello

Copy link
Copy Markdown
Contributor Author

@oneandonlydean agreed, and thank you for carrying the evidence over to #9884. My duplicate search missed it, which I should have caught before opening. I'll offer the read-back test on #9884.

AI-assisted: yes (Claude Code)

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

Labels

agc libSceAgc* perf Performance change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants