Skip to content

fix(agc): clear a multisampled target on a uniform fill of its color memory - #2132

Merged
oneandonlydean merged 2 commits into
boykopovar:mainfrom
Raghav314:fix/agc-msaa-fill-clear
Oct 13, 2026
Merged

oneandonlydean merged 2 commits into
boykopovar:mainfrom
Raghav314:fix/agc-msaa-fill-clear

Conversation

@Raghav314

@Raghav314 Raghav314 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

What

Follow-up to #419, as discussed there. A multisampled target's samples only live in its host image, so a fill of its color memory never reached them. A title that clears its MSAA targets that way, with no CMASK, kept drawing over the previous frame's contents.

  • A uniform fill over the whole color memory of a multisampled target clears its host image to that value at the target's next use, the same way a CMASK fast clear is applied. A pending CMASK clear still takes precedence, since the CB reads a cleared tile's color from the clear registers whatever the memory holds.
  • The color memory is a single mip and slice sized with addrlib's block dimensions for the sample count (ComputeThinBlockDimension): a block holds width × height × samples elements, so a 4x 8-byte target is 64x32 pixels per 64 KiB block, and 2x/8x lay the block out height first.
  • The clear value comes from ClearColorForTexel. It has no layout for sRGB formats, so those go through their UNORM layout and are accepted only when every color channel is 0 or 1, the two codes whose sRGB decode is exact (alpha is linear).
  • Refused at the next use, like a mixed CMASK: a fill over part of the color memory, a fill reaching the start of a target whose footprint is not computed (linear), and a fill value with no exact clear value in the format.
  • The fill state is a small MultisampledColorFill next to MultisampledCmask, so it is tested without a device.
  • TechnicalDebt gets an entry, including what is not seen: a fill before the target's first use (the host image starts at 0), and copies or shader writes into the color memory.

PPSA36082

Hades II clears its two 4x targets by filling their color memory with 0: 0x2400000 bytes over a 1536x1536 sRGB 8_8_8_8 target and 0xff0000 bytes over a 960x540 16_16_16_16 float target. Both match the footprints above exactly. On main f145139 with the fixes it needs to get that far plus #419, #2121 and the follow-up for its sample positions, the character was drawn with ghost copies around her and her shadow was broken; with this change both render correctly in gameplay. I tested that in game before moving the fill state into its own class; the behaviour did not change.

Tested

Linux (CachyOS), x86-64, GCC, 7800X3D / RTX 4090.

  • agc_driver_graphics (new checks):
    • Footprints: the game's two targets, 2x and 8x on a 130x60 target (where width-first and height-first blocks give different sizes), 4 KiB blocks, and 0 for a linear target.
    • A whole fill clears once; fills next to the color memory are ignored; a fill one block short is refused; a later fill over and around it clears again with its value; a fill at the start of a linear target is refused.
    • Clear values: half-float 1.0, sRGB 0, sRGB white with alpha 0x80, and an sRGB channel of 0x80 refused.
  • Mutations, each failing a check: sample count left out of the block, always width first, never width first, a partial fill taken as whole, any sRGB value accepted, linear targets ignored, a refusal never cleared.
  • The linux job of current build.yml run locally on this head, rebased on main d1fd937 (SPIRV tools on, test registration check, build, libs, ctest): 1003/1004. The one failure, guest_kernel_fcntl, came from my script running ctest with no terminal on stdin; it passes when run from a terminal.

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
  • AI-assisted: yes

@github-actions github-actions Bot added needs rebase Conflicts with main, rebase needed labels Oct 9, 2026
@Raghav314
Raghav314 marked this pull request as draft October 9, 2026 12:51
@Mouhand-Kaddo

Copy link
Copy Markdown
Contributor

Additional platform validation on standalone PR head a41f1d7899d976fcfce3e2009c11c82979643a42:

Windows: passed — Windows 11, supported WinLibs GCC 15.2.0 UCRT/POSIX/SEH r7; CMake 4.3.1 / Ninja.

Release / BUILD_TESTING=ON: built the selected test targets; 1 CTest cases, 0 failures, 0 CTest skips.

Tests: agc_driver_graphics.

Reproduce after building the test targets: ctest --test-dir <build> --verbose --no-tests=error --timeout 30 -R "^agc_driver_(graphics|cb_resolve)$".

Vulkan: native AMD RX 6900 XT, driver 32.0.21045.5002.

Integration limitation: a local merge against main 948a526c2 has source conflicts; these results validate the standalone PR head, not a resolved merge.

@Amoenus

Amoenus commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

Inherited CMASK state model: source-backed validation (2026-10-11)

At current head a41f1d78, the CMASK helper inherited from #419 still uses CurrentDccKeys/DccKeys and accepts uniform fills only as zero or 0xffffffff. Pinned PS5 Mesa descriptors distinguish C (FMASK clear zero / color expanded) from D/E/F (FMASK uncompressed / color expanded for 2/4/8 samples). In particular, CC must not be interpreted as a color-register clear.

A proposed correction with state tests separates those states, scans the complete CMASK range, checks mode/clear eligibility, and consumes a pending host clear once while preserving guest metadata classification. It depends on our experimental MSAA foundation; it is not a standalone upstream cherry-pick. This is feedback on the inherited CMASK model; I have not independently reproduced this PR's Hades II color-fill gameplay result.

Windows 11 / RTX 5080: full build/120 libraries, 30 final relevant test runs, six caught implementation mutants, ten native positives using two complete captured DCC/CMASK pairs and sixteen exact-diagnostic negative controls. Our PPSA99902 before/full-after/AGC-only startup still stops at the DCC multisample refusal; no startup or console/coherence equivalence claim. Full validation and limits on #419; tracking issue #3262.

Fork only, no new PR. AI-assisted implementation and investigation: Codex.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

#419 is merged. Please rebase onto main so this carries only its own commit, and mark it ready.

@Raghav314
Raghav314 force-pushed the fix/agc-msaa-fill-clear branch from a41f1d7 to 0e2219b Compare October 11, 2026 23:48
@Raghav314
Raghav314 marked this pull request as ready for review October 11, 2026 23:49
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 12, 2026
@Amoenus

Amoenus commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Current conditional MSAA reset finding — PPSA99902 /01.003.000

Our preserved fac106b2b integration, with validated #4822 libc and original #3644 pipe-poll fixes, reaches the existing private MSAA residency guard. The newest replay ends after 801.006s, exit0xC0000409, no timeout. Six inputs are confirmed consumed/released; sparse captures retain prompt/opening-movie scope, not interactive gameplay or unique-frame FPS. Original dump/modules, baseline runtime, converted guest module and user data are restored/hash-verified.

Confirmed first rejection: the 510MiB color range changed according to UnchangedSince against its initial snapshot. The serial, DCC UnchangedSinceCollected, DCC StoredOver, and CMASK UnchangedSinceCollected checks passed before it. Uniform-metadata byte validation and native clear import were not reached.

[msaa-color-stamp-v2] predicate=color-unchanged-since-initial address=0x405fc00000 dcc=0x40819f4000 dccBytes=0x87000 cmask=0x40819e0000 cmaskBytes=0x14000 dirty=1 clearGeneration=1943905 clearCmaskGeneration=1943906 writeSerial=44112 clearWriteSerial=44112
[msaa-color-stamp] reason=newer-block-stamp initialGeneration=869030 firstChanged=0x405fc00000 newestBlock=0x405fc00000 newest=1943351 cpu=8160 driver=0 importWindow=0 fresh=0
[msaa-color-stamp-origin] block=0x405fc00000 currentStamp=1943351 cpuStamp=1943351 writtenStamp=1943351 generation=1943351 kind=cpu site=windows-collect-page event=0x405fc0f000+0x1000 priorGeneration=37566 priorKind=cpu priorSite=windows-collect-page

All 8,160 64KiB color blocks have newer latest stamps of kind Cpu, made by the existing Windows dirty-page collection path. The first/newest block is0x405fc00000; its recorded generation1943351 equals the tracker stamp, CPU stamp and written stamp. Its previous recorded stamp37566 predates initialGeneration869030. The trace distinguishes this from a coverage/generation refusal. It records the kind and creation site of the latest general stamp for every color block; it does not identify the backend event that made the pages dirty or prove guest CPU stores.

The newest color stamp predates the active clear certificate1943905 (CMASK1943906), with matching write serials44112. Newer DCC/CMASK general stamps after the clear attempt do not mean their CPU-side certificate checks rejected. This run's target/use XOR is0/0x8000 and FMASK pointer0x407fa08000/0x407fa04000; the refusal occurs before XOR conversion.

Validation scope: the observer preserves the original check/collect calls and refusal; it adds only bounded stamp records and scalar logging, without extra guest-byte reads. Its libraries and affected agc_write_tracking_tests, agc_driver_graphics_tests, guest_memory_tests targets were explicitly rebuilt. The instrumented tracking executable's marker was verified. All three registered selected groups (agc_write_tracking, agc_driver_alpha_to_coverage_state, guest_memory) pass20 repetitions. Source is restored byte-for-byte, ordinary libraries/test targets rebuilt, and the frozen diagnostic profile used for the game. There is no new full-suite pass claim or direct unit coverage of the hardcoded color-address logging; title execution supplies that trace. Diagnostics change timing.

Shared-backend follow-up (787.785s, 2026-10-13)

The same frozen fac106b title stack reaches the private guard again, no timeout, baseline restored/hash-verified. Rebuilt controls pass three groups×20. Temporary libc/driver observers preserve the mapping, protection and collection operations and add scalar provenance only; source/ordinary binaries are restored before replay. No actual guest store or whole-color overwrite is proved.

The first dirty color views are fresh shared-map records at the same virtual addresses, rather than a pin/unpin, host-write or handled-fault invalidation. The first view appears twice with seen0/generation2 and no recorded prior event, but at later map event serials:

[msaa-shared-dirty] request=0x405fc00000+0x21e7c000 view=0x405fc00000 offset=0x0 seen=0 generation=2 pins=0 armed=0 hostWrites=0 aliases=1 cause=map-shared event=0x405fc00000+0x4000 eventSerial=483530 priorCause=initial priorEventSerial=0
[msaa-shared-dirty] request=0x405fc00000+0x21e7c000 view=0x405fc00000 offset=0x0 seen=0 generation=2 pins=0 armed=0 hostWrites=0 aliases=1 cause=map-shared event=0x405fc00000+0x4000 eventSerial=2570294 priorCause=initial priorEventSerial=0

Three views at the start of each of two large collection chunks were sampled. Each has one recorded alias (its own address), offset0/0x4000/0x8000 or0x10000000 onward, and cause=map-shared. The request spans0x21e7c000, larger than the color's0x1fe00000. The tracker subsequently records all8160 color blocks as newer Cpu stamps, initial853462/newest1916585; the color check rejects after accepted certificate1917110/CMASK1917111 and matching serial43238. This confirms a new mapping record at the reused address, not the identity/content of its physical backing or coverage of an unmap notification. The diagnostic samples do not give a complete per-view event history.

#4474 already addresses retirement after successful full allocation removal/unmap. It is now the focused validation candidate; its current026c198e head has no Windows/title validation from this investigation yet. Verify which registry removal/unmap covers this transition, then test its notification/retirement on Windows and adapt it to the private integration's color-plus-metadata guarded ranges. Retain partial-unmap and live-data refusals. This is stronger lifetime evidence than the older CPU-flag/pinning hypotheses, but not a pass/fail verdict on unchanged #4474. No duplicate retirement PR or new current-main issue.

What this does and does not validate for #2132

The target is3840x2160 RGBA16F/eight-sample, color span0x1fe00000, matching the32x32 fragment-block sizing. Existing fill logs recognize FMASK0x76543210, CMASK0xffffffff/0xcccccccc and DCC0; none of3,074 completely parsed records targets the color range, with one interleaved DCC continuation retained. Requests are not completed stores or uniform-overwrite proof. This is not a pass/fail test of unchanged #2132 or confirmation that its uniform whole-color-fill path solves this guard. Main has no equivalent private reuse check, so no new unchanged-main title defect is established.

Preserved earlier scopes

Replay/control Evidence and limit
Older786.028s and785.353s integrations Same private refusal. Shared views were clean/unpinned at capture; no overlapping pin/host-write or handled-fault site was logged. That does not exclude earlier events or events through physical aliases. Pinned no-store synthetic controls set CPU flags; unchanged unpinned controls do not. Those controls do not attribute the live color stamps.
Refreshed801.118s diagnostic Active certificate1951852, matching serial44350, newer metadata stamps logged after the import attempt. First predicate was not observed.
795.601s first-predicate trace Color initial-snapshot check rejects after accepted preliminary certificate checks. Initial878765/newest1945436; clear1945980. It did not attribute the color stamp. Three existing groups passed×5; the libraries-only build did not rebuild the standalone tracker, so that run did not unit-validate its tracking instrumentation. The dynamic driver compiled and the predicate is direct title evidence.

Next: validate #4474's full-unmap retirement and determine whether its successful registry notifications cover this remapping; the sampled backend cause is now map-shared. The latest color stamps are not directly ImportWindow, so #3874 is not confirmed as the solution; an earlier import effect remains possible. Map/unmap retirement (#4474) and fresh-import tracking (#4535) are existing candidates only if the corresponding provenance is established. Preserve the guard until actual contents/lifetime are understood. No duplicate issue or speculative fix. Bring-up #4443 tracks progress; private binaries and captures remain local. AI-assisted: Codex.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 12, 2026
@Amoenus Amoenus mentioned this pull request Oct 13, 2026
9 tasks done
…memory

A multisampled target's samples live only in its host image, so a fill
of its color memory never reached them: a title that clears its MSAA
targets that way (with no CMASK) kept drawing over the previous frame.

A uniform fill over the whole color memory now clears the host image to
that value at the target's next use, the same way a CMASK fast clear
does, and a pending CMASK clear still takes precedence. The color memory
is sized with addrlib's block dimensions for the sample count
(ComputeThinBlockDimension): a 64 KiB block holds width x height x
samples elements, laid out width first for 1x and 4x. A fill over part
of it, over a target whose footprint is not computed, or with a value
that has no exact clear value in the format throws at the next use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Raghav314
Raghav314 force-pushed the fix/agc-msaa-fill-clear branch from 0e2219b to 1baf077 Compare October 13, 2026 02:18
@oneandonlydean oneandonlydean self-assigned this Oct 13, 2026
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 13, 2026
… fill

ApplyFastClear took the color fill before the CMASK clear, so a partial
fill of a multisampled target's color memory threw at the next use even
when a CMASK fast clear was pending over the whole target. The CB reads
a cleared tile's color from the clear registers whatever the memory
holds, so the CMASK clear is applied and the recorded fill dropped.
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Pushed one commit on top (e322c6e): ApplyFastClear took the color fill before the CMASK clear, so a partial fill threw at the next use even with a CMASK clear pending. The CMASK clear now goes first and drops the recorded fill, with a check for it in agc_driver_graphics. Build and ctest pass on main with this and with #2121.

@github-actions

github-actions Bot commented Oct 13, 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
#4820 core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp L8, L32-33

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.

4 participants