Skip to content

fix(agc): read a multisampled target's CMASK by its MSAA codes - #6669

Merged
adriadam10 merged 2 commits into
boykopovar:mainfrom
milescoviello:fix/msaa-cmask-codes
Oct 19, 2026
Merged

adriadam10 merged 2 commits into
boykopovar:mainfrom
milescoviello:fix/msaa-cmask-codes

Conversation

@milescoviello

@milescoviello milescoviello commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What

A multisampled color target with a CMASK fast clear (CB_COLOR_INFO.FAST_CLEAR) seeds its CMASK state when it is first used, and fills of its CMASK update it later. main reads those bytes as DCC keys: 0x00 is a fast clear, 0xff is uncompressed, and anything else is refused:

AGC graphics: the CMASK 0x4004600000 of a multisampled color target reads as mixed, which is not modeled

An MSAA CMASK nibble is not a DCC key, though. It is fmask | color << 2 (Mesa CMASK_MSAA_CODE):

Code Mesa name main this PR
0x00 FMASK_CLEAR_0_COLOR_CLEAR_REG fast clear fast clear (unchanged)
0xcc FMASK_CLEAR_0_COLOR_EXPANDED refused no fast clear
0xdd, 0xee 2x / 4x FMASK_UNCOMPRESSED_COLOR_EXPANDED refused no fast clear
0xff 8x FMASK_UNCOMPRESSED_COLOR_EXPANDED no fast clear no fast clear (unchanged)
other codes, or bytes that differ refused refused

So a CMASK whose color is expanded, with no fast clear pending, is refused unless it happens to be the 8x code. The host image of a multisampled target starts cleared to zero. A uniform fill of its color memory can give it a clear value, but it never reads other guest color data. "No fast clear" is therefore what 0xff already does, whatever the FMASK code.

Ruffle (PPSA68091) clears its target's CMASK to 0xcc every frame, with a compute dispatch. On main that dispatch is address-based, so the target's first use usually reads the CMASK before the clear has landed, sees zeros and runs. When the clear landed first, it stopped on the line above, in 2 of my 6 earlier sweep runs on older main builds (11 to 12 s in). #6543 runs that kernel as a buffer fill, so the clear is always ordered before the read, and Ruffle then stops there every time.

The seed now reads the uniform CMASK byte, after pending writes as the key read does (CurrentUniformMetadataByte). The seed and CMASK fills both classify it with MultisampledCmaskKeys.

Tested

Linux (Ubuntu 24.04, GCC 13.3, Release), RTX 4070 Ti, NVIDIA 595.99.

  • agc_driver_graphics: seeds and whole-CMASK fills of 0xcc, 0xdd, 0xee and 0xff give no fast clear, 0x00 still clears once, and 0x11, 0x33, 0x44, 0x88, 0xcd, an uneven pattern and an unknown (mixed) seed are refused. With main's fill classification the test fails on a fill of the CMASK ... with 0xcccccccc leaves its tiles mixed. It passes 3 of 3 with this change.
  • ctest (^agc_driver_|^agc_vertex_barrier|recompiler|shader_cache|request_serializer|spirv, 469 tests), with this change plus a local match of the same fill kernel, which I'm not opening since fix(agc): take RADV's fill-memory kernel as a buffer fill #6543 has it: 468 pass. agc_driver_store_over_untracked fails the same way on main 5d9dcc4; test(agc): keep waiting for the neighbour copy case's result #6385 addresses it.

Ruffle (PPSA68091), 60 s with scripted pad input, three runs each:

Build Result
main 5d9dcc4 runs to the limit (3 of 3)
+ #6543 (2fc4fe5e0) stops 11 to 12 s in on the CMASK line above (3 of 3)
+ #6543 + this PR runs to the limit (3 of 3)

@Amoenus checked this PR together with #5939 and #6543 on main 5d9dcc4. The one conflict, in MultisampleTarget::acquire, resolves by keeping #5939's guard argument and DCC exclusion along with this PR's raw CMASK reader. On Windows they ran the MSAA DCC, fill and sample tests on an RTX 5080 (610.62) and an AMD Radeon iGPU, 70 processes passing, and on Linux with llvmpipe 20 processes passing. Their test commit, c8950bb (cherry-picked with their authorship), enumerates all 256 uniform CMASK bytes: 00 is a clear, cc/dd/ee/ff are expanded, and the other 251 are unmodeled. It passes 3 of 3 here.

@screamm reproduced the Ruffle table on Windows (RTX 4070, NVIDIA 596.21) with #6543 on main 5d9dcc4. main runs to the 125 s limit 3 of 3. With #6543, Ruffle stops 2 of 2 on the CMASK line as the demo starts. With #6543 and this PR, 2 of 2 run to the limit. vkQuake (PPSA99010) with both also runs to the limit 2 of 2. The tests pass 3 of 3, and fail with MultisampledCmaskKeys set back to main's codes.

Update after #5939 merged: Ruffle's target carries DCC, so on current main the title stops earlier, as the demo starts, on #5939's multisampled DCC CMASK compression or register clear is not implemented. That happens with or without this PR, and the seed this PR changes (dccAddress == 0) is not reached. @screamm's recheck on Windows shows this. So the Ruffle table above predates #5939 and no longer applies; the agc_driver_graphics and agc_driver_covered_centroid_state checks above still cover the change, and pass 3 of 3 there.

Not checked on Intel or on a console.

#5939 changes the seed line, adding a DCC condition, and #6215 changes the lines next to it. Whichever lands second needs a small rebase.

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: none on current main (Ruffle stops earlier since feat(agc): import uniform multisampled DCC fast clears #5939; see Tested)
  • GPUs tested: NVIDIA GeForce RTX 4070 Ti, driver 595.99, Ubuntu 24.04; NVIDIA GeForce RTX 5080 and AMD Radeon iGPU on Windows, llvmpipe (by @Amoenus, tests only); NVIDIA GeForce RTX 4070, driver 596.21, Windows 11 (by @screamm)
  • AI-assisted: yes

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

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 1395 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 4040 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 5441 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 5499 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 5500 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 5925 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6146 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6186 core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp
PR 6188 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6194 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6235 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6292 core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp
PR 6457 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6479 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6548 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6615 core/libs/prx/libSceAgcDriver/Graphics/src/DccMetadata.cpp
PR 6628 core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
PR 6636 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6683 core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6730 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6762 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6804 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6808 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6970 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6971 core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
PR 6972 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 6973 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
PR 6987 core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
PR 6989 core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
PR 6995 core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
PR 7002 core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
PR 7085 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 7254 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 7256 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 7310 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 7332 core/libs/prx/libSceAgcDriver/tests/Graphics.cpp
PR 7650 core/libs/prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp
core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp
PR 7800 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp

@Amoenus

Amoenus commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Independent component check of #5939 + #6543 + this PR, on main 5d9dcc42d2ec5401aebd089028b8d4d7410a77b4:

Merge: composition 13ed9df uses #5939 3851b712c, #6543 2fc4fe5e0, this head 5fb0245a6, and separate #6452 for Windows test-build alignment. There was one conflict in MultisampleTarget::acquire. The combined resolution preserves both the fourth guard argument/DCC exclusion from #5939 and the raw CMASK reader/SeedCode from this PR:

guard(context, use, created.active, created.metadataValid);
if (use.cmaskFastClear && use.dccAddress == 0) created.cmask.SeedCode(CurrentUniformMetadataByte(use.cmaskAddress, static_cast<std::size_t>(use.cmaskBytes)));

Windows: full application/libs build, library/convention/test-registration checks pass. Five repetitions each of CPU groups agc_driver_graphics and agc_driver_msaa_dcc_state, plus five repetitions per physical GPU of agc_driver_msaa_dcc_clear_2, _4, _8, agc_driver_clamped_global_fill, agc_driver_multisample_fill_host_only, and agc_driver_sample_multisample_target: 70 passing processes, no skips/timeouts, maximum 5.02 seconds. All six runtime groups' device-selection logs confirm NVIDIA RTX 5080 (10de:2c02, driver 610.62) and AMD Radeon(TM) Graphics (1002:13c0, driver 32.0.21036.18), Windows 11 Enterprise 10.0.26200. CPU groups are not GPU coverage.

Small optional test addition: 036b7f0 adds only seven lines to the existing graphics fixture. It enumerates all 256 byte values, expecting 00 as clear, cc/dd/ee/ff as expanded and the other 251 inputs as unmodeled. Its patch also applies cleanly to your original 5fb0245a6 head, independently checked with a temporary index; you can cherry-pick the test commit without the composition commits.

The expanded fixture passed five Windows processes, failed five times at input 204/0xcc with a narrowed-classification control, then passed five times after restoring the exact positive head. The control retains the new reader/API and only narrows expanded recognition back to ff; it is not unmodified main. This checks the proposed classifier contract, not hardware semantics for every possible CMASK state.

Linux: at test head 036b7f0, selected targets build on Ubuntu 24.04/WSL2, GCC 13.3. graphics, msaa_dcc_state, msaa_dcc_clear_4, and clamped_global_fill each pass five repetitions: 20 processes, no skips, maximum 3.29 seconds. Runtime selections confirm Mesa 25.2.8 llvmpipe, LLVM 20.1.2; 2x/8x hardware coverage remains the Windows result. This is not a full Linux application build.

Cppcheck 2.20.0 then clang-tidy 23.1.3 completed for MultisampleTarget.cpp, DccMetadata.cpp, and tests/Graphics.cpp (53.13 seconds; no timeouts/parser failures). Clang-tidy has no selected diagnostics. Cppcheck retains four warnings at locations unchanged by this PR: recorder/batch null-check reasoning and three fixture pass-by-value advisories. The recorder-null branch constructs its batch before dereference; no unrelated source rewrites or suppressions were made.

This confirms the component combination and preserves the DCC exclusion; it does not independently reproduce Ruffle, Rush 2, Wave Race or Sonic. The resident-target/metadata-write refusal is unchanged. AI-assisted validation/test/report: yes.

@milescoviello

Copy link
Copy Markdown
Contributor Author

Thanks @Amoenus, for the combination check and for the test. I cherry-picked 036b7f0 as c8950bb with your authorship, and it passes 3 of 3 here. Your Windows and llvmpipe runs are in the description now.

AI-assisted: yes

@screamm

screamm commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Windows cross-check on main 5d9dcc4 with #6543 (2fc4fe5e0) and this PR (5fb0245a6) merged on top, no conflicts. Windows 11 Pro 26220, RTX 4070, NVIDIA 596.21, WinLibs GCC 15.2.0, Release. Titles relinked per run, 125 s limit.

Ruffle (PPSA68091), Cross pressed at 8, 11 and 14 s ("Play the demo"):

Build Result
main runs to the limit, 3 of 3
+ #6543 stops 2 of 2 (12.6 s and 23.2 s, exit 0xC0000409) as the demo starts: what(): AGC graphics: the CMASK 0x4004600000 of a multisampled color target reads as mixed, which is not modeled
+ #6543 + this PR runs to the limit, 2 of 2 (783 and 873 frames), no CMASK line in the log

So your Linux table reproduces on Windows/NVIDIA. With both, the demo's presents match main's (on this busy host the demo runs at about 4 fps and presents black on main as well, unrelated to either PR). vkQuake (PPSA99010) with both runs to the limit 2 of 2 with 0 of 500 black dumps, against 48 and 53 on main.

Tests: agc_driver_graphics and agc_driver_covered_centroid_state 3 of 3. With MultisampledCmaskKeys changed back to main's codes (only 0xff as expanded) both fail on the CMASK 0x30000 of a multisampled color target reads as code 0xcc, which is not modeled, and pass again restored. 12 related agc_driver_ tests (fills, DCC keys, CMASK, multisample) pass 3 of 3 on the stack; agc_driver_store_over_untracked is flaky here on main too (46 of 65 on main, 23 of 45 on the stack, 13 vs 12 of 20 interleaved). Non-GPU suite on the stack: 834 of 837, 2 skipped, hardware_oracle_compare timed out once at its 20 s limit and passed on rerun. check_conventions.py and git diff --check clean.

The head moved to c8950bb7c while I ran this; the only new commit is the test check all uniform MSAA CMASK byte codes, which I did not run.

Not checked: this PR without #6543 on a title, AMD or Intel, Linux, the full GPU agc_driver_ suite, the merge with #5939 or #6215.

Written with Claude Code assistance (validation by a subagent, reviewed in this session).

@milescoviello

Copy link
Copy Markdown
Contributor Author

Thanks @screamm, good to have the Ruffle result on Windows too. I added it to the description.

AI-assisted: yes

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

Copy link
Copy Markdown
Contributor Author

Rebased onto main 40255d7 (e54508d, 8455a5c). #5939's a0c51d5778 (uniform multisampled DCC fast clears) changed the same line in acquire. I kept its form: the CMASK seed now runs only when the target has no DCC (use.cmaskFastClear && use.dccAddress == 0), and passes created.metadataValid to guard. MultisampledDccClear already reads the CMASK byte with the same expanded codes (0xcc, or 0xdd/0xee/0xff by sample count), so the DCC path is unchanged. agc_driver_graphics passes. The 905 area tests pass except agc_driver_store_over_untracked, which fails on main as well.

AI-assisted: yes (Claude Code)

@screamm

screamm commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

Rechecked on head 8455a5cce (rebased over #5939), Windows 11 Pro 26220, RTX 4070 (596.21), WinLibs GCC 15.2. Stacked as main 5f61879a9 + #6530 + #6639 + this PR, merged cleanly. Full build: no failures.

  • agc_driver_graphics and agc_driver_covered_centroid_state pass 3 of 3, and the latter prints All 256 MSAA CMASK byte classifications passed. The GPU pass -R '^agc_driver_' passes 484 of 487 with 2 skipped. The one failure, agc_driver_f16_fma_denormal_modes, also fails on plain main here. agc_driver_store_over_untracked passed this time.
  • Ruffle (PPSA68091), Cross to start the demo, 120 s: the reads as mixed, which is not modeled stop from my earlier run did not appear in any of 8 runs. However, Ruffle now stops on current main itself, as the demo starts. Main 3b10c7ef0 stops 2 of 2 (13.9 s, 17.9 s), and the stack stops 2 of 2, on AGC graphics: multisampled DCC CMASK compression or register clear is not implemented. With fix(agc): take RADV's fill-memory kernel as a buffer fill #6543 added, it stops 2 of 2 on AGC graphics: writes to a resident multisampled DCC target or its metadata are not implemented. I got the same result with fix(agc): take RADV's fill-memory kernel as a buffer fill #6543 and this PR's four source files reverted. Both lines come from feat(agc): import uniform multisampled DCC fast clears #5939's DCC path. Ruffle's target has DCC, so after the rebase the seed this PR changes (dccAddress == 0) is not reached in Ruffle. On current main this PR therefore shows no effect in Ruffle either way, and the title check I posted earlier no longer applies.

Not checked: this PR alone, AMD/Intel, Linux.

Written with Claude Code assistance (validation by a subagent, reviewed in this session).

@milescoviello

Copy link
Copy Markdown
Contributor Author

Thanks @screamm for rechecking after the rebase. You are right that the Ruffle evidence no longer applies now that #5939 gives its target DCC; I marked the table as predating #5939 and changed Unblocks to none.

AI-assisted: yes (Claude Code)

milescoviello and others added 2 commits October 17, 2026 18:59
A multisampled color target with a CMASK fast clear seeds its state from
the CMASK when it is first used, and later fills of the CMASK update it.
Both read the bytes as DCC keys: 0x00 is a clear, 0xff uncompressed, and
anything else refuses ("reads as mixed"). But an MSAA CMASK nibble is
FMASK code | color code << 2 (Mesa ac_descriptors.h CMASK_MSAA_CODE), so
0xcc (FMASK cleared to fragment 0, color expanded), and the 2x and 4x
"FMASK uncompressed, color expanded" codes 0xdd and 0xee, are states with
no pending fast clear that were refused. Ruffle (PPSA68091) clears its
target's CMASK to 0xcc every frame and stopped on the refusal whenever
that clear landed before the target's first use.

The seed now reads the uniform CMASK byte (after pending writes, like the
key read) and both paths classify it by those codes: 0x00 (register color
clear, FMASK cleared) is the fast clear as before, any color-expanded code
(0xc to 0xf in both nibbles) is "no fast clear", and other codes, or bytes
that differ, are still refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adriadam10 adriadam10 self-assigned this Oct 19, 2026
@adriadam10
adriadam10 merged commit 5de7c09 into boykopovar:main Oct 19, 2026
8 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Checked the MSAA CMASK nibble classification against Mesa's CMASK_MSAA_CODE (0x00 register clear, 0xcc/0xdd/0xee/0xff color-expanded, everything else refused), the seed's read after pending writes, and the test (256-byte sweep plus seed/fill cases; it pins values only the new classification produces). Unmodeled codes still throw. Merged.

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

Labels

agc libSceAgc*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants