Skip to content

fix(agc): run the unwatch hook when a watched import leaves the write watch - #6308

Closed
LeandroLP23 wants to merge 2 commits into
boykopovar:mainfrom
LeandroLP23:fix/agc-unwatch-hook-settled-import
Closed

LeandroLP23 wants to merge 2 commits into
boykopovar:mainfrom
LeandroLP23:fix/agc-unwatch-hook-settled-import

Conversation

@LeandroLP23

@LeandroLP23 LeandroLP23 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What

Fixes #6301. Since #6060, ProsperoPuzzles (PPSA99006) stops 5 s in on Windows:

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

GuestMemory::ImportWatched with staysWatched still drops the range from the write watch when the range was only partly watched (before is false) or the import window can't be resolved. That fallback called unwatchLocked without beforeUnwatch, so the storage-image hook (StorageTexture::SaveBeforeUnwatch) never saved the guest bytes of results pending there. The !staysWatched path always runs the hook first.

Before #6060, Windows direct imports never took this path. Now settled private imports do. In ProsperoPuzzles an 8 MiB import at 0x21f800000 took the fallback with the range only partly watched, and unwatched the storage image at 0x21fc00000 with results pending.

The fallback now releases the tracker lock, runs the hook over the page range, takes the lock again and unwatches. The hook takes the pending-texture mutex and GpuMutex (and, with #5671, the tracker lock), so it can't run under the tracker lock.

agc_write_tracking gets CheckImportWindowOverUnwatchedPart. It unwatches the middle block of three, imports all three with staysWatched, and requires the hook to have covered the range.

This complements #5671, which widens the span the hook gets to whole 64 KiB blocks. The two touch different lines.

Tested

Windows 11 (26300), Radeon 860M (RDNA 3.5, driver 32.0.13058.8001), WinLibs GCC 15.2.0, Release, on main e49ec591d.

Fork CI (run): Windows 1264 of 1265 and Linux 1241 of 1242, the one failure in each being the fork-only conventions_ci_gate. The first Linux attempt also failed agc_driver_preparation_scope_settle (the link did not wait for the vertex shader's registration preparation), a shader-registration timing test that uses no imports. It passed on the job rerun and 10 of 10 locally.

Not tested: NVIDIA, Intel, other titles.

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: ProsperoPuzzles (PPSA99006) on Windows, AGC: since #6060, ProsperoPuzzles (PPSA99006) stops at 5 s with the storage-image write-back throw on Windows #6301
  • GPUs tested: AMD Radeon 860M (RDNA 3.5), driver 32.0.13058.8001, Windows 11 26300
  • AI-assisted: yes (Claude Code)

… watch

An import that should stay watched still drops its range from the watch
when the range was only partly watched or its import window cannot be
resolved. That fallback skipped the unwatch hook, so a storage image with
results pending in the range lost the watch without its guest bytes
saved, and its next store threw "storing storage image results pending
over memory outside the write watch without the guest bytes saved before
them is not implemented".

Since boykopovar#6060 settled private imports on Windows take this path, and
ProsperoPuzzles (PPSA99006) stopped 5 s in (boykopovar#6301). The fallback now
releases the tracker lock, runs the hook over the page range, and
unwatches it.

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

Copy link
Copy Markdown
Contributor

NVIDIA run, for the "Not tested: NVIDIA, Intel, Linux, other titles." line. Windows 11 (26200), RTX 3060 Laptop GPU (NVIDIA 617.42), WinLibs GCC 15.2.0 r7 (UCRT), Release, BUILD_TESTING=ON. Head 93df5c42a merged onto main 8856f180c (clean); ANYPS5_GPU=3060, and a verbose test log shows the RTX 3060 selected.

  • agc_write_tracking: passes.
  • With GuestMemory.cpp from main and the PR's test (2 of 2 runs), agc_write_tracking fails: an import that left the write watch did not run the unwatch hook over its range.
  • ctest -R "agc|shader|guest_memory|write_watch" -j4: 591/591 pass with the PR, and 591/591 on main. Both skip the same 3 tests (agc_host_import_sparse, agc_driver_sparse_dma_buf_import, agc_driver_stencil_ref_export).
  • agc_driver_quad_pixel_mask and agc_driver_layered_volume_vertex_no_gpl pass here, with the PR and on main.

Not run: ProsperoPuzzles or any other title, Intel, Linux.

AI-assisted: yes (Claude Code)

This was referenced Oct 15, 2026
MarcZX10 pushed a commit to MarcZX10/AnyPS5 that referenced this pull request Oct 18, 2026
… watch

A MarkWritten whose fresh collect fails (a no-access or unwatchable
resident page inside the 64 KiB block span) took the span out of the
write watch without running the unwatch hook, so pending storage-image
results over it kept no saved guest bytes and their write-back refused:
storing storage image results pending over memory outside the write
watch without the guest bytes saved before them is not implemented
(boykopovar#7017, Palworld PPSA23126 among others).

The hook now runs over the block span before the blocks leave the
watch, as Unwatch and the import fallback do, with the tracker lock
released for it like boykopovar#6308 does for imports. The debt entry drops the
failed collect from the unsaved cases.

The check makes a collect fail over a no-access arena page and requires
the hook to have covered the block; it needs the WindowsMappings
protection failure, so it runs in the Windows-only section of the write
tracking test.
@LeandroLP23

Copy link
Copy Markdown
Contributor Author

Still needed on current main 1c523bdb4, after #6455 merged. ProsperoPuzzles (PPSA99006 01.000.010), Radeon 860M (driver 32.0.13058.8001, Windows 11 26300), WinLibs GCC 15.2.0, Release, two 60 s runs each (first with empty AnyPS5 shader cache, second warm), tools/title_run.py from #7264:

Build Cold Warm FPS
main 1c523bdb4 crashed crashed 0.0
main 1c523bdb4 + this PR (48a1d3453) rendering rendering 29.2

Both main runs stop on AGC graphics: storing storage image results pending over memory outside the write watch without the guest bytes. The merge of this PR onto main is clean.

AI-assisted

MarcZX10 pushed a commit to MarcZX10/AnyPS5 that referenced this pull request Oct 18, 2026
… watch

A MarkWritten whose fresh collect fails (a no-access or unwatchable
resident page inside the 64 KiB block span) took the span out of the
write watch without running the unwatch hook, so pending storage-image
results over it kept no saved guest bytes and their write-back refused:
storing storage image results pending over memory outside the write
watch without the guest bytes saved before them is not implemented
(boykopovar#7017, Palworld PPSA23126 among others).

The hook now runs over the block span before the blocks leave the
watch, as Unwatch and the import fallback do, with the tracker lock
released for it like boykopovar#6308 does for imports. The debt entry drops the
failed collect from the unsaved cases.

The check makes a collect fail over a no-access arena page and requires
the hook to have covered the block; it needs the WindowsMappings
protection failure, so it runs in the Windows-only section of the write
tracking test.
@github-actions github-actions Bot added the unblocks title Names a title ID on the Unblocks line label Oct 18, 2026
@ionut27001

Copy link
Copy Markdown
Contributor

Marvel's Wolverine (PPSA03671) stops on this Require on current main, and every run with this PR got past it.

Setup: Windows 11 Pro 26300, RTX 4080 SUPER 16 GB (NVIDIA 617.42), Ryzen 7 5800X3D, 32 GB RAM, WinLibs GCC 15.2.0 r7, Release. Content 01.001.005; eboot.bin and its three modules unwrapped with tools/unwrap_self.py and converted with relinker --windows --module-dir .. Base: main f305942 + #6901 (8cd400a), which the title needs to load. Every run is the same scene, the "Sony Interactive Entertainment presents" card, with no input, a shader cache from earlier runs and APS5_PROFILE_DRAW=1.

Not checked: anything past the splash card, Linux.

AI-assisted: yes

adriadam10 pushed a commit to MarcZX10/AnyPS5 that referenced this pull request Oct 19, 2026
… watch

A MarkWritten whose fresh collect fails (a no-access or unwatchable
resident page inside the 64 KiB block span) took the span out of the
write watch without running the unwatch hook, so pending storage-image
results over it kept no saved guest bytes and their write-back refused:
storing storage image results pending over memory outside the write
watch without the guest bytes saved before them is not implemented
(boykopovar#7017, Palworld PPSA23126 among others).

The hook now runs over the block span before the blocks leave the
watch, as Unwatch and the import fallback do, with the tracker lock
released for it like boykopovar#6308 does for imports. The debt entry drops the
failed collect from the unsaved cases.

The check makes a collect fail over a no-access arena page and requires
the hook to have covered the block; it needs the WindowsMappings
protection failure, so it runs in the Windows-only section of the write
tracking test.
@oneandonlydean

oneandonlydean commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Thanks for this and for the title reports. #8172 (the fix for #8103) has now merged and rewrites the staysWatched path of ImportWatched: a range that is only partly watched is now split into runs of covered 64 KiB blocks, those blocks stay watched, and only the gaps plus any span whose walk fails are unwatched. That removes the most common way this fallback was reached (a partly watched range used to unwatch the whole range), so the stop you saw may already be gone on #8172. Could you rerun the titles you reported on current main (which has #8172) without this PR and say whether the Require still triggers?

Either way, the hook is still missing on the path that remains: on #8172, a covered span whose CPU walk fails before the import (dropped before runImport) or whose window walk fails after it is unwatched by unwatchLocked without beforeUnwatch, so pending storage-image results over it aren't saved. Could you rebase onto main and reduce this PR to that:

  1. For spans dropped because the walk before the import failed, run beforeUnwatch on them before runImport (with the tracker lock released, as you do now). fix(agc): run the unwatch hook before an import whose range leaves the write watch whatever the import does #7505's "save early" idea fits here: the bytes are saved before the import can change them.
  2. For spans whose window walk fails after the import, run beforeUnwatch on them before they're unwatched (same lock release).
  3. Rewrite the test to fix(agc): keep resident DCC targets watched under aligned block imports #8172's semantics: a partly watched import now KEEPS its watched blocks watched, so the old assertion that they get unwatched no longer holds. Instead, make a covered block fail its walk (a CPU write before the import, or a write inside the import window) with a storage result pending over it, and check that the hook ran and the bytes were saved.

#7505 (@AlxcBTW) contains this PR plus the save-early change, so it'd be simplest to land one combined PR. Please agree between you which branch carries it.

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

Copy link
Copy Markdown
Contributor Author

Rechecked on current main e9cc4a8. That tree includes #7432, which fixes another path to the same refusal. This branch (48a1d34) still merges cleanly.

ProsperoPuzzles (PPSA99006):

Tree Runs alive at 60 s
main 0 of 2: stops at about 4 s with the write-back refusal
main + this PR 3 of 3

main + #7048 still stops at 3 s (0 of 3), so this path isn't covered elsewhere. Same setup as the description: AMD Radeon 860M, Windows 11, Release, new run folder each time, 60 s, no input.

@LeandroLP23

Copy link
Copy Markdown
Contributor Author

Superseded by #8172, so I'm closing this. Data is in #6301: ProsperoPuzzles stops at 4 s on #8172's parent (e9cc4a8, 0 of 2 alive at 60 s), and runs with #8172 (2 of 2) and on current main d50ec76 (3 of 3).

With #8172, a stays-watched import keeps the spans that were watched before it. A span leaves the watch only when the walk after the import fails (walkWrites with the window), and that unwatchLocked still runs without beforeUnwatch. That's the same kind of gap this PR closed, but I have no title or test that reaches an incomplete collection there. If you'd like the hook on that path anyway, say so and I'll rebase this onto #8172's structure: collect the spans to unwatch, run the hook outside the tracker lock, then unwatch them, as MarkWritten does.

AI-assisted: yes (Claude Code)

@screamm

screamm commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

ProsperoPuzzles (PPSA99006) on main 1d4164552 (includes #8172, d9df34d08), without this PR: Windows 11 Pro 26220, RTX 4070 (596.21), WinLibs GCC 15.2.0 r7, Release, --windows, new run folder each run, no input. 3 of 3 runs were still alive at the 150 s kill (7581, 8281 and 7575 frames, about 50-55 fps at the end), and none logged the write-watch Require refusal or terminate called. On the earlier main c9e6c45de the same title stopped at about 7 s with 0xC0000409 on that refusal (reported above on 93df5c42a). So on current main the Require no longer triggers for this title here.

Written with Claude Code assistance.

@LeandroLP23
LeandroLP23 deleted the fix/agc-unwatch-hook-settled-import branch October 22, 2026 13:05
TheBlenDed pushed a commit to TheBlenDed/AnyPS5 that referenced this pull request Oct 25, 2026
oneandonlydean pushed a commit that referenced this pull request Oct 26, 2026
…lready fails

An import that should stay watched runs the unwatch hook only when its range
leaves the write watch, after the import. When the range is partly outside the watch
or the walk before the import fails, the range leaves the watch whatever the import
does, so the hook can run first, as it does for the import that does not stay
watched.

After the import, the walk that tells an import's own page marks from CPU writes
(the window compare) is skipped in this case, so the hook collects those marks as CPU
writes. A driver that marks the pages of an import written (the NVIDIA driver on
Windows does, see Pages under an import window) makes the hook keep the blocks they
cover for the CPU unless their bytes are proven unchanged, and those blocks do not
receive the GPU results. A failure of the walk after the import still runs the hook
after it.

The check of #6308 also requires the hook to have run before the import.
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 unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AGC: since #6060, ProsperoPuzzles (PPSA99006) stops at 5 s with the storage-image write-back throw on Windows

9 participants