Skip to content

fix(agc): consume the write-watch faults of the driver's own CPU stores - #462

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
oneandonlydean:fix/agc-own-store-write-watch
Oct 4, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
oneandonlydean:fix/agc-own-store-write-watch

Conversation

@oneandonlydean

@oneandonlydean oneandonlydean commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

What

A store the driver makes with the CPU into write-watched guest memory left its own write-watch faults behind. Those pages were later read as a new guest write and uploaded again over GPU results.

The stores concerned are GuestMemory::Write, the changed-bytes write-back (WriteChanged), a completion action's label store (Recorder::AfterCompletions) and the CPU path of the copy HLE (VulkanDevice::CopyBuffer). Each one stores, which faults its pages dirty in the write watch, and then stamps the range with MarkWritten as a driver store. The faults stayed in the watch. Collects are memoized per packet, so the next walk over those pages came later and stamped them again, this time as a CPU write made after every check in between. A resident image or buffer over that range then reads as changed by the guest and is refreshed from guest memory, which drops whatever the GPU wrote there since the driver's store.

How PPSA21564 hits it, on my integration branch once the occlusion dumps stopped draining the device:

  • the title fast-clears its normal G-buffer every frame with a register DCC key fill, recorded on the GPU;
  • while that fill is still pending, the first draw into the target materializes the clear with a CPU store of the clear texels (16 MiB) and a refresh of the resident image;
  • a dozen draws later a walk found that store's faults, the target read as changed, and the image was uploaded again from guest memory, dropping the G-buffer draws recorded in between;
  • the lighting then sampled cleared normals, so the moon and rocks of the intro level went unlit in about half the frames.

On main every occlusion counter dump drains the device, so the key fill has usually landed by that draw, the clear goes to the GPU, and no CPU store happens. That hides the bug in this title, but any CPU store by the driver into watched memory takes the same path.

The fix: a driver store now consumes its own faults.

  • New GuestMemory::StoreOwnBytes(address, bytes, store), used by all four stores above, under the tracker lock:
  • A CPU write made after the store faults again and is seen by the next walk as before.
  • Memory that is not watched, or a walk that fails, falls back to store-then-stamp as before.
  • On Windows the guest arena's range also holds host regions that were already in use when it was reserved (heaps, thread stacks); GetWriteWatch refuses them. A store into one of them falls back as well, through a new GuestArenaHostRegionOverlaps_nid_postfix in libc's GuestArena.

This builds on #441: it uses the driver pieces (noteDriverStore), and its test extends #441's agc_write_tracking. #441 is merged and the branch is rebased onto it.

Tested

  • Linux (CachyOS, x86-64): full build with BUILD_TESTING=ON and ctest pass (146/146).

  • Windows (MinGW, CI on my fork): build and ctest pass.

  • New CheckOwnStore in agc_write_tracking (tests/WriteTracking.cpp) checks that:

    • a Write into a collected block is stamped;
    • an uncached walk after it does not report the store again as a newer write, and the store's pages are not stamped as a CPU write;
    • a CPU write made just before a driver store in the same page is still stamped as a CPU write;
    • a CPU write after the store is seen.

    Without the change it fails with "a walk after a driver store reports the store again as a newer write".

  • PPSA21564 on my integration branch, with the occlusion dump drains removed: the intro level's moon is dark in 104 of 241 frames (every frame kept) without this change, and in 0 of 241 with it. It has also passed repeated runs of my visual regression check there (checkpoint frames, flicker, shimmer, video tearing, FPS).

  • No other open PR touches these functions. perf(agc): query guest page access with PROCMAP_QUERY on Linux #157 also edits GuestMemory.cpp (the maps cache) and already conflicts with main. My fix(agc): describe the pages after a gap in a Linux host page query #442 (also GuestMemory.cpp) and fix(libSceFiber): pin fiber contexts writable under write tracking on Windows #283 (also libc's GuestArena) merge cleanly with this branch.

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 in TechnicalDebt
  • 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 (fix(agc): keep a surface's results in the 64 KiB block it shares with its neighbour #441 is merged)
  • AI-assisted: yes

@github-actions github-actions Bot added libc build agc libSceAgc* needs rebase Conflicts with main, rebase needed labels Oct 3, 2026
A store the driver makes with the CPU into watched guest memory (a
GuestMemory::Write, a changed-bytes write-back, a completion action's label
store, the copy HLE's CPU path) faults its pages dirty in the write watch and
stamps the range with MarkWritten. The faults stayed behind: collects are
memoized per packet, so the next walk over those pages came later and stamped
them again, as a CPU write made after every check in between.

PPSA21564 fast-clears its normal G-buffer every frame with a register DCC key
fill on the GPU. While that fill is still pending, the first draw into the
target materializes the clear with a CPU store of the clear texels and a
refresh of the resident image. A dozen draws later a walk found the store's
faults, the target read as changed, and the image was uploaded again from
guest memory, dropping the draws recorded in between. Lighting then sampled
cleared normals and the level's moon and rocks went unlit in about half the
frames.

The driver's stores now consume those faults themselves: a walk before the
store stamps earlier CPU writes as such, a walk after it takes the store's
own faults as a driver store, and the range is stamped as before.

On Windows the arena's range also holds host regions that were already in
use when it was reserved (heaps, thread stacks). They are not write-watched,
so a store into one is stamped as before, without a walk.
@oneandonlydean
oneandonlydean force-pushed the fix/agc-own-store-write-watch branch from 95477e7 to bb1531f Compare October 3, 2026 22:36
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 3, 2026
@oneandonlydean
oneandonlydean merged commit e84b558 into boykopovar:main Oct 4, 2026
3 checks passed
@oneandonlydean
oneandonlydean deleted the fix/agc-own-store-write-watch branch October 4, 2026 02:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant