Skip to content

fix(agc): do not wait for a storage image's own stamped writes before flushing it - #12070

Open
imellag wants to merge 4 commits into
boykopovar:mainfrom
imellag:perf/agc-flush-skip-stamped-writes
Open

imellag wants to merge 4 commits into
boykopovar:mainfrom
imellag:perf/agc-flush-skip-stamped-writes

Conversation

@imellag

@imellag imellag commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

What

StorageTexture::FlushPending syncs when an unfinished batch writes the flushed range (#6364, 9fffcea): a writer that stamps guest memory only when its batch completes (an address-based dispatch) must have its marks land before the write-back decides which 64 KiB blocks to keep. When the writer is in the open batch, that is a full Recorder::Sync.

Some writers need no such wait: storage image write-backs (writeBackLayers, writeBackWindows) call GuestMemory::MarkWritten for what they store while they are recorded, and a unit shadow publish copies bytes those write-backs already stamped. The next write-back of the same range is recorded after them in the same queue, so it lands after them on the GPU anyway. Today the flush still waits for them. Example: an image is written back at one label, written again by a dispatch, then flushed again at the next label; the second flush finds the first one's publish in the open batch and drains the queue.

  • Recorder::NoteStampedWrites notes such ranges like NotePendingWrites and also keeps their write notes in the batch (stampedNotes). The two write-back paths and recordCopies in UnitShadow.cpp note their writes this way. The staged publish (the copy into a host buffer that writes guest memory at completion) does not.
  • When a flush stores a single image, FlushPending keeps the stamped notes that flush recorded (the image's write-backs and its publish), from Recorder::StampedNotesSince, in the image's flushNotes (the last 32). A flush that stores several images keeps none, so no image takes another's notes.
  • The next flush adds the image's flushNotes to the existing ownNotes exception of fix(agc): store resident linear color targets at completion points, limited to their drawn rectangles #9685, so it applies only where that one does: the flush stores a single image and that image has a host import. Otherwise ownNotes is cleared as on main. Any other writer over the range (another image's write-back or publish, a dispatch, draw, fill, label, CPU-resolved copy, address-based lease) still makes the flush wait, and CPU readers still sync on these writes through the pending-write snapshot as before.

tests/execution/StorageFlushOrdering.cpp gains a stored-again case: images are stored, written again by a dispatch and flushed again in the same open batch, and the newer texels must land. With one image and a host import the second flush must not submit the open batch. It must wait with two adjacent images flushed together, without a host import, when another stamped write that is not the image's own (noted with NoteStampedWrites over the image) is in the open batch, or when the image is flushed alone after a joint flush with its neighbour. A second registration, agc_driver_storage_flush_ordering_no_import, runs the stored-again cases with APS5_NO_HOST_IMPORT=1. main fails the one-image case (it waits), the first head of this PR failed the two-image case, the second head failed the unrelated stamped write case and the third the alone-after-joint case (they did not wait), the current head passes all of them. The two existing cases (writer in flight, writer in the open batch) still pass.

Overlaps:

Tested

No performance claim: main cannot run titles or the AGC execution tests on macOS yet, and I have no other platform for a main-vs-change capture.

macOS 27.0.1, Apple M4 Pro, MoltenVK 1.4.2, x86_64 build under Rosetta. Support branch, not main: fork branch support/macos-boot-1027 (76d41ef) = main d13b3d7 + only the macOS boot PRs #7631 #10552 #11249 #11447 #11554 #10669 #7804 (#7804's conflicts in Process.cpp and Time.cpp resolved locally). Change: fork branch support/macos-boot-1027-pr12070 (f6730c3) = that branch + the four commits of this PR.

Focused tests on both with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ctest -R 'storage_flush_ordering|linear_readback_label|linear_target|agc_driver_storage_(cache_budget|one_slice_alias|read_during_idle_store|write_back)|storage_cache_retained|depth_storage_reuse|flip_stores_small' -V: the same 10 tests fail on both with the same messages (the aborts are "deferred destruction found two different device resolvers", fixed by #11705, which is not in this support branch), all the others pass, both storage_flush_ordering registrations pass on the change (rerun on the current head on macOS 27.2: same failures, except that agc_driver_linear_target_direct_write_completion now hits the resolver abort only intermittently), no was not found but was requested by env var VK_INSTANCE_LAYERS, no VUID- in either log. God of War (PPSA28997) ran through the Cyclops fight on the second head with no crash or device loss; not rerun on this head.

python3 tools/check_conventions.py --base upstream/main (0c4d871): passed.

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: perf(agc): wait on a flush only for writers over the image layers it stores #8887
  • Fixes: none
  • Unblocks: none
  • GPUs tested: Apple M4 Pro, MoltenVK 1.4.2, macOS 27.0.1
  • AI-assisted: yes

@github-actions github-actions Bot added perf Performance change perf numbers missing perf pull request without a before and after summary line labels Oct 27, 2026
@github-actions

Copy link
Copy Markdown

This pull request is labeled perf, so it needs the summary line of a capture on main and one with the change, from the same title, scene and machine, as the Performance report form asks. Please paste both aps5-summary.jsonl lines, attach the two traces and add the output of tools/trace_digest.py.

@github-actions github-actions Bot added perf numbers missing perf pull request without a before and after summary line and removed perf numbers missing perf pull request without a before and after summary line labels Oct 27, 2026
@github-actions

github-actions Bot commented Oct 27, 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 9626 core/libs/prx/libSceAgcDriver/CMakeLists.txt L2343
PR 9875 core/libs/prx/libSceAgcDriver/CMakeLists.txt L2343
PR 10472 core/libs/prx/libSceAgcDriver/CMakeLists.txt L2342-2343

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
PR 1395 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 2434 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 4448 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 4449 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5021 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5371 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 5609 core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 5925 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6185 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
PR 6186 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp
PR 6187 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6188 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp
PR 6192 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6197 core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6209 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6280 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6300 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6412 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6481 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 6743 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 6808 core/libs/prx/libSceAgcDriver/CMakeLists.txt
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 7256 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7275 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7449 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
PR 7457 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 7464 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
PR 7514 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7604 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 7642 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp
PR 8014 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8484 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 8542 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 8592 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9020 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9335 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9356 core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
PR 9387 core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.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/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.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/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 9700 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9712 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
PR 9748 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9842 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9891 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9892 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9893 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9895 core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
PR 9897 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 9898 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
PR 10193 core/libs/prx/libSceAgcDriver/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/include/Texture.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/UnitShadow.cpp
PR 10233 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10249 core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
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/CMakeLists.txt
core/libs/prx/libSceAgcDriver/Graphics/include/Recorder.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/Recorder.cpp
PR 10757 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 10920 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 11351 core/libs/prx/libSceAgcDriver/CMakeLists.txt
PR 11554 core/libs/prx/libSceAgcDriver/CMakeLists.txt

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@imellag thanks, the record-time stamping itself looks sound. One question before this can go in: #9685 (c7f9eaa) made FlushPending skip a target's own earlier stores only when the flush stores that target alone and it has a host import; otherwise ownNotes is cleared and the flush waits. Stamping writeBackLayers' note makes PendingUnstampedWriterSerial skip those same stores in the cases #9685 excluded (several images flushed, or no import), and it also skips other images' write-backs and every unit-shadow publish. Could you either apply the stamped skip only where the own-store skip is allowed today, or explain why #9685's restriction does not apply to stamped writes, and add a case that flushes two adjacent images (one without an import) together after a stamped store? A same-machine main-vs-head measurement on current main would also help, since the current numbers are from one scene on a non-main tree.

@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 the recorder changes and the new ordering test, and I agree with the concern already raised by the collaborator: PendingUnstampedWriterSerial skips every stamped note before FlushPending can apply the existing ownNotes exception, which is deliberately restricted to a single image with an existing host import. The new test covers a repeated store for one image, but not multiple flushed images or an image without a host import. Please explain why the #9685 restriction does not apply to those stamped writes, or constrain the skip to the same safe cases and add the requested multi-image/no-import regression case. I am not treating the current test as evidence for those cases.

The performance report also does not use the repository main baseline: it names a custom tree containing open macOS boot PRs and #11705. Please rerun the matched capture against current main and this exact change, and refresh the attached digest/traces as requested by the performance-report check. Finally, coordinate the same-line/test overlap with open #8887 and record the agreed ordering in Depends on per CONTRIBUTING; the overlap section currently says either PR may land second while the checklist says none.

…ng a storage image

FlushPending syncs when an unfinished batch writes the image's range, so that
the marks of a writer that stamps guest memory only when its batch completes
(an address-based dispatch) land before the write-back decides which blocks to
keep. Storage image write-backs and unit shadow publishes stamp their blocks
while they are recorded, so waiting for them adds nothing: the next write-back
of the same range is recorded after them in GPU order.

The recorder now notes those writes as stamped (NoteStampedWrites), and
FlushPending looks for writers with PendingUnstampedWriterSerial, which skips
them. Every other writer still makes the flush wait.
The stamped-write skip now applies under the same condition as the own-store
skip of boykopovar#9685: the flush stores a single image and that image has a host
import. A flush of several images, or of an image without an import, waits for
stamped writes as before.

StorageFlushOrdering checks a single image stored again (the flush must not
wait when the image has an import, and must wait without one) and two adjacent
images stored again together (the flush must wait). A second registration runs
the stored-again cases with APS5_NO_HOST_IMPORT=1.
@imellag
imellag force-pushed the perf/agc-flush-skip-stamped-writes branch from 7973cef to 156bbaa Compare October 27, 2026 19:30
@imellag

imellag commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

@oneandonlydean @MarcZX10 thanks, you are right about #9685's restriction. A flush that stores several images, or an image without a host import, can store on the CPU or over a neighbour, and must wait for every pending GPU write over the range, stamped or not.

Pushed 156bbaa (branch rebased on current main d13b3d7):

  • The stamped skip now applies only where the own-store skip applies today: the flush stores one image and that image has a host import. Otherwise FlushPending uses PendingWriterSerial exactly as on main.
  • StorageFlushOrdering.cpp: the stored-again case now runs for one image (must not wait with an import, must wait without one) and for two adjacent images flushed together (must wait). A second registration, agc_driver_storage_flush_ordering_no_import, runs the stored-again cases with APS5_NO_HOST_IMPORT=1. Checked on the three versions: main fails the one-image case (it waits), the first head of this PR fails the two-image case (it does not wait), this head passes both. I could not make only one of two adjacent images lack an import inside one test block (imports cover the block), so the no-import case runs alone under APS5_NO_HOST_IMPORT=1.

Baseline. main cannot run titles or the AGC execution tests on macOS yet. Both arms are now main plus only the boot PRs, nothing else:

Why each one: #7631, #7804, #10552, #11249 libkernel/libc on macOS; #11447 and #11554 AGC driver on macOS; #10669 libSceAudioIn link. The earlier numbers in the description came from a tree that also had #11551 and #11705; they are replaced below.

Same focused tests on both arms with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation: the same 10 tests fail on both arms with the same messages (the aborts are "deferred destruction found two different device resolvers", fixed by #11705, which is not in this base), all the others pass, both storage_flush_ordering registrations pass on the change, no VUID- in either log.

God of War (PPSA28997), Cyclops fight, one pair on these two branches, same machine, APS5_TRACE_SUMMARY_EVERY=10:

fight frame time per 10 s scene before the fight [recorder] pending-write syncs per 160 s run
base 23.6-23.7 ms 16.9-17.3 ms 5,427
change 22.1-22.3 ms 16.7 ms (vsync) 0

This is one pair only; I will add a matched trace capture of these two branches with trace_digest output and update the description.

#8887. I measured it on macOS and proposed there that it lands first; once SleglJan agrees I will set Depends on: #8887 and rebase this PR on it. Until then the checklist stays at none.

@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-reviewed exact head 156bbaa. The new guard and the no-import/multi-image regression cases address the broad skip outside the existing single-image, host-import case. I found three remaining blockers:

  1. When ownStoresSkipped is true, PendingUnstampedWriterSerial ignores every overlapping note in batch.stampedNotes, not only this flush's ownNotes. NoteStampedWrites is used both for storage write-backs and UnitShadow::recordCopies. The single-image/host-import guard does not establish that every overlapping stamped note belongs to the image being flushed; a pending unit-shadow publish or another overlapping stamped writer can still be skipped. Please narrow the exception to the flush's own writes, or document the invariant that rules out unrelated stamped writers and add a regression with an unrelated stamped publish overlapping a single imported image.

  2. The current heads of #8887 and #6186 both conflict with this head in shared AGC code. Pairwise merge-tree reports conflicts for #8887 in Recorder.hpp, Recorder.cpp, Texture.cpp and StorageFlushOrdering.cpp; for #6186 it reports conflicts in CMakeLists.txt, Recorder.hpp, UnitShadow.cpp and the debt file. The PR describes these overlaps but still says Depends on: none. CONTRIBUTING requires agreement on order and a declared dependency for same-line overlaps. Please coordinate and declare the agreed order. I checked #11694 as well; its current head merges cleanly with this PR, so I do not see a required ordering there.

  3. The updated report includes matched traces and digests, but its baseline is e5d3b00 from a custom tree with macOS boot PRs and #11705, rather than current upstream main d13b3d7. The perf contribution rule requires a current-main/change comparison on the same machine and scene. Please provide that comparison on a supported platform or remove the performance claim and label.

The current-main merge preview for this head is clean. I am requesting these specific corrections; this is not an approval of the performance or ordering claims.

@imellag
imellag marked this pull request as draft October 27, 2026 19:41
@imellag imellag changed the title perf(agc): do not wait for writes stamped when recorded before flushing a storage image fix(agc): do not wait for a storage image's own stamped writes before flushing it Oct 27, 2026
@imellag

imellag commented Oct 27, 2026

Copy link
Copy Markdown
Contributor Author

@MarcZX10 thanks. Updated what I can without new test runs:

  1. Narrowing the skip to the flush's own writes: agreed. I will replace the batch-wide stampedNotes with the image's own notes (its write-backs and the publishes of its own flushes, through the existing ownNotes path), which also drops the UnitShadow.cpp change, and add a regression with an unrelated stamped publish over a single imported image. That needs a test run, so it comes in a later commit; the description lists it as open.
  2. Order: agreed with SleglJan on perf(agc): wait on a flush only for writers over the image layers it stores #8887: perf(agc): wait on a flush only for writers over the image layers it stores #8887 lands first, and I will rebase this PR on it (Depends on: #8887 is set). With item 1 this PR no longer touches UnitShadow.cpp or the recorder's batch fields, which removes most of the overlap with fix(agc): keep CPU stores made after a deferred DCC key store was recorded #6186; I will check the remaining overlap against fix(agc): keep CPU stores made after a deferred DCC key store was recorded #6186 then.
  3. Performance: I removed the performance claim, the numbers and the perf title; the PR is now fix(agc). main cannot run titles on macOS, so there is no main-vs-change capture from me. The Tested section names the support branch the tests ran on.

@maskym

maskym commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

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

This PR's own tests: agc_driver_storage_flush_ordering (0.54 s) and the new agc_driver_storage_flush_ordering_no_import (0.69 s), all passed.

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

Same focused run on main f759e165d9 and on this tree, same machine and GPU: VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation, ANYPS5_NO_SHADER_CACHE=1, ANYPS5_REQUIRE_VULKAN=1, VK_LOADER_DEBUG=error,warn,layer, ctest --test-dir build -R '^(agc|guest_video_out|video_out)' --parallel 4 -V.

The tree also carried the open PRs #11948, #12062, #12132, #11680, #11679, #11678, #11677, #11675, #11674, #11605, #11604, #11600, all merged cleanly, so the run is a joint one. Not run on a PS5.

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

@github-actions github-actions Bot added build agc libSceAgc* labels Oct 28, 2026
@imellag

imellag commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

@MarcZX10 point 1 is in 5b34b1d: the skip is now limited to the flushed image's own writes.

  • PendingUnstampedWriterSerial is gone. FlushPending keeps the stamped notes that each flush records (the image's write-backs and its publish, from Recorder::StampedNotesSince) in the image's flushNotes, and the next flush adds them to the existing ownNotes exception. That exception still clears them unless the flush stores a single image with a host import, exactly as on main.
  • A stamped write that is not one of the image's own flushes (another image's write-back or publish over the range) now makes the flush wait like any other writer.
  • StorageFlushOrdering.cpp gains the regression: a single imported image stored again with another stamped write noted over it in the open batch must wait. It fails on 156bbaa ("the second store did not wait for a stamped write that is not the image's own") and passes on this head.

The UnitShadow.cpp change stays: recordCopies must tag its notes as stamped so they can be told apart from the staged publish, which writes guest memory only at completion and is never skipped.

Tested on the support branch named in the description (main d13b3d7 + only the macOS boot PRs, change = that branch + the three commits): both storage_flush_ordering registrations pass, the same 10 focused tests fail with the same messages as on the base, no VUID-. No new GoW run on this head.

Point 2: Depends on: #8887 is declared; I will rebase on it once it lands, as agreed with SleglJan. @maskym thanks for the Windows + NVIDIA run on the previous head.

@imellag
imellag marked this pull request as ready for review October 28, 2026 06:53
@mashingaan

Copy link
Copy Markdown

On head 5b34b1ddeca280b0ce05b21cfc3ab6880b18fc96, could you check note ownership across a multi-image flush followed by a single-image flush?

StampedNotesSince(firstNote) collects all stamped notes generated by the whole flush and the same notes vector is appended to every texture in flush. Clearing ownNotes for the first multi-image flush does not prevent those notes from being saved. If image A is subsequently dirtied and flushed alone with a host import, its flushNotes can contain image B's earlier write-back or publish note. If that note overlaps the new flush range and is still pending, PendingWriterSerial can treat B's write as A's own and skip the wait.

Could the stored exemptions be associated with the texture that produced each note, including any shared shadow publish, or be conservatively omitted for multi-image flushes? A focused sequence with a joint A+B flush followed by an overlapping single-image A flush would exercise this separately from the existing test, which inserts an unrelated note outside the first flush. This is a source-level observation and I have not reproduced it at runtime.

AI-assisted: Codex

@adriadam10 adriadam10 self-assigned this Oct 28, 2026

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for narrowing the skip to the image's own notes. One ownership problem remains, confirmed in the source (same point @mashingaan raised):

  • core/libs/prx/libSceAgcDriver/Graphics/src/Texture.cpp:4319-4328: StampedNotesSince(firstNote) returns every stamped note recorded during the whole flush, and the same vector is appended to flushNotes of every texture in flush. After a joint A+B flush, A's flushNotes contain B's write-back/publish notes. A later flush of A alone with a host import adds them to ownNotes (line 4253-4254), so B's write over an overlapping range is treated as A's own and the flush does not wait. Keep per-texture notes (collect them around each texture's own write-back, and decide how a shared unit-shadow publish is attributed), or only record flushNotes when the flush stored a single image.
  • tests/execution/StorageFlushOrdering.cpp: add the sequence joint A+B flush, then A dirtied and flushed alone with a host import, with B's range overlapping A's flush range. It must wait. The current "unrelated" case notes the foreign write after the first flush, so it does not cover this.
  • Depends on #8887, which is still open; rebase on it once it lands (the same lines in FlushPending and in this test will conflict).
  • The perf label asks for the main vs change summary lines and tools/trace_digest.py output (or drop the label/perf framing if this is a correctness-only change, as the body says "No performance claim").

@imellag

imellag commented Oct 28, 2026

Copy link
Copy Markdown
Contributor Author

@adriadam10 @mashingaan thanks, confirmed: after a joint A+B flush, A's flushNotes held B's write-back and the shared publish.

Fixed in 3b3cd73: FlushPending now records flushNotes only when the flush stored a single image, so every note kept belongs to that image's own write-back or to the publish that this single-image flush made. A joint flush records nothing, and a later flush of A alone waits for everything the joint flush wrote.

StorageFlushOrdering.cpp gains the sequence you describe: A and B stored together, A written again and flushed alone (host import where the device has one), and the flush must wait. It fails on 5b34b1d ("the second store did not wait for the joint store, whose writes are not the image's own") and passes on this head. Tested on the same support branch as in the description: macOS now 27.2. Both storage_flush_ordering registrations pass. The other failures are among the 10 that fail on the base with the same messages; one of them, agc_driver_linear_target_direct_write_completion, hits the deferred destruction resolver abort (#11705) only intermittently now (1 of 4 runs alone). No VUID-.

#8887: I will rebase once it lands. The perf label was already removed on 10-27 (the bot dropped it after the title change), and the description makes no performance claim.

@Oscar45397

Copy link
Copy Markdown
Contributor

Windows + NVIDIA run of the current head 3b3cd7377f, merged onto main 2227f13661 (clean merge, local only), compared with that main. The earlier NVIDIA run here used 156bbaaaba, before the two-image, unrelated stamped write and alone-after-joint changes.

Setup: Windows 11 Home 26200, AMD Ryzen 9 5900X, NVIDIA GeForce RTX 3080, driver 610.88, Vulkan SDK 1.4.363 (VK_LAYER_KHRONOS_validation), WinLibs GCC 15.2.0 r7, Release, BUILD_TESTING=ON.

Command: the pattern from the Tested section, 3 runs on each side, with ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1 and VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation:

ctest --test-dir build -R 'storage_flush_ordering|linear_readback_label|linear_target|agc_driver_storage_(cache_budget|one_slice_alias|read_during_idle_store|write_back)|storage_cache_retained|depth_storage_reuse|flip_stores_small' -V
Runs Passed Failed VUID- lines Missing-layer lines
main 2227f13661 3 25 of 25 each none 0 0
this PR on that main 3 26 of 26 each none 0 0
  • The extra test is the new agc_driver_storage_flush_ordering_no_import. Both storage_flush_ordering registrations pass in all 3 runs.
  • The layer was active on both sides: each log has the same 10 validation warnings (WARNING-VkImageSubresourceRange-layerCount-compatibility, all from agc_driver_storage_write_back_no_window) and no error, and no log has was not found but was requested by env var VK_INSTANCE_LAYERS.
  • Timing: agc_driver_storage_flush_ordering took 1.2 s on main and 11.4, 7.7 and 5.1 s in the three runs with this PR; _no_import took 8.3, 7.7 and 7.1 s. Run again afterwards, both took 2.4 to 2.8 s with and without ANYPS5_NO_SHADER_CACHE=1 and without the layer, so the first runs look like the driver's pipeline cache warming up for the new shaders. That is well under the 30 s limit.

GPUs tested: NVIDIA GeForce RTX 3080, driver 610.88, Windows 11.

AI-assisted: yes (Claude Code)

@Astronaut10

Copy link
Copy Markdown
Contributor

Linux + AMD measurement of the waits this PR removes, from a title. I did not run this PR itself: these numbers come from counters on the code it changes, so they show what is there to save, not a main-vs-change result.

Setup: CachyOS, Linux 7.3.0, Ryzen 7 5700X3D, RX 9070 XT (RADV, Mesa 26.2.5), GPU DPM level forced to high. ASTRO BOT (PPSA21564), scene underwater_aerial_garden, 3840x2160, about 3.5 FPS. Build: main 27ad7244d2 with #6182, #6565, #7844 (needed to reach the scene), #9858 and some local performance changes that do not touch FlushPending or labels; draw pipeline on (APS5_DRAW_PIPELINE=1). Plus a diagnostic-only commit: trace scopes on the label path and counters in FlushPending.

What waits, per present (from a nonserializing trace over 36 presents, and from counters over a 60 s run):

  • Queue 0x48, in Driver::dispatch -> recordDeferredLabels -> WriteLabelOnGpu -> StoreReadbackBeforeLabel -> FlushPending: 20.5 ms. 18.3 ms of that is the writer wait before the write-back: FinishUpTo 12.1 ms and Sync 6.2 ms, almost all of it in vkWaitForFences. This happens under the GPU mutex. 20.1 ms of it overlaps the 21.4 ms that the draw committer waits for the mutex at its draws.
  • Draw committer, its own label items (commitLabel -> WriteLabelOnGpu, same path): 24.0 ms in WriteLabelOnGpu, of which 21.5 ms is the same writer wait (FinishUpTo 19.6 ms).
  • Together about 42 ms of a 283 ms frame.

Whether the waited writer is the image's own earlier store: for each label flush I recorded the recorder's open serial when it stored the image, and compared it with the writer serial FlushPending then waited for. Per present:

thread label flushes waits (Sync / FinishUpTo) waited for the batch with the image's own last store
queue 0x48 2.4 0.4 / 1.0 1.4 (all)
draw committer 18 2.4 / 3.9 4.7 (75 %)
queue 0x40 8.0 7.0 / 0.0 6.0 (86 %)

All of these flushes store a single image: either linear and host-imported (the layers-recorded write-back) or small (blocks). That is your stored-again case. One caveat: the check is by batch. If the same batch also holds another writer over the range, the flush still has to wait, and my counter cannot tell those cases apart. So the 75-100 % is an upper bound on what this PR skips, and the time per wait was not split the same way.

GPUs tested: AMD Radeon RX 9070 XT (RADV, Mesa 26.2.5), Linux.

AI-assisted: yes (Claude Code)

@Gonic0

Gonic0 commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Alternated A/Bs of this PR, of #8887, and of both together, on a title: PPSA21564 01.007.000 (the US release of PPSA21567), in the particle effect that follows the publisher title card.

Apple M5 Pro, macOS 27.2, MoltenVK 1.4.2 (Vulkan SDK 1.4.363), x86-64 Release under Rosetta 2, 3840x2160, ANYPS5_NO_SHADER_CACHE=1, APS5_PROFILE_DRAW=1, APS5_TRACE_SUMMARY_EVERY=10.

The tree is not main alone, because main does not build the driver on macOS. Every build is main 6793a72475 plus:

In that tree, mesh list draws with primitive restart are skipped locally. On top of it:

What this scene waits on in main, from a local diagnostic in the same tree on the previous main (f3eb573e6f):

  • Most of the recorder's sync time is FlushPending under readback before label, where the pending writer is in the open batch, so it is a full Recorder::Sync.
  • The writers I logged over the flushed range covered only the image's own memory. They were linear images of 0x3f4800 and 0x157800 bytes. A writer was either the image's exact extent, or that extent rounded up to whole 64 KiB units as two writes.
  • drawnListed was false on both images and storeNotes was empty, so the fix(agc): store resident linear color targets at completion points, limited to their drawn rectangles #9685 exception did not apply.

That is the case this PR describes.

Each row is one 200 s run:

build round phase (log s) presents per 10 s frame p50 ms recorder sync wait of it pending write of it idle
main 1 104 to 146 19.8 595.2 3.66 s 338 syncs, 2.14 s 1.06 s
main 2 90 to 183 16.4 610.0 5.36 s 191 syncs, 2.72 s 1.47 s
#8887 1 91 to 162 16.6 536.2 4.89 s 319 syncs, 2.62 s 1.46 s
#8887 2 91 to 174 22.0 599.4 4.89 s 252 syncs, 2.94 s 1.12 s
this PR 1 72 to 164 25.3 513.5 1.35 s 145 syncs, 0.18 s 0.35 s
this PR 2 82 to 174 24.1 473.6 1.94 s 95 syncs, 0.22 s 0.48 s
both 1 81 to 164 18.2 567.7 1.22 s 123 syncs, 0.08 s 0.20 s
both 2 90 to 173 23.2 564.7 1.92 s 104 syncs, 0.18 s 0.42 s

This PR's effect:

#8887 alone leaves these waits where main has them. Here the writer covers the image's own blocks, which #8887 still waits for, as its description says. With both PRs, the waits match this PR alone.

Limits:

  • Presents per 10 s vary a lot in this phase between runs of the same build: the entry time ranged from 72 to 104 s.
  • The two sessions were about 30 minutes apart.
  • Every run ended at the 200 s timeout with no device loss.

Not covered:

  • an image comparison of the rendered frames;
  • other scenes;
  • a tree without the macOS prerequisites.

AI-assisted: yes

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* build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants