Skip to content

fix(agc): sample a multisampled texture no draw rendered to from its uniform color memory - #5445

Merged
adriadam10 merged 3 commits into
boykopovar:mainfrom
nicolasabosch:fix/agc-sample-undrawn-msaa
Oct 14, 2026
Merged

adriadam10 merged 3 commits into
boykopovar:mainfrom
nicolasabosch:fix/agc-sample-undrawn-msaa

Conversation

@nicolasabosch

Copy link
Copy Markdown
Contributor

What

Follow-up to #4820. Sampling a multisampled texture (T# type 14) whose surface no draw has rendered to threw sampling the WxH multisampled texture ..., which no draw rendered to, is not implemented. No host target exists for such a surface, so its samples are whatever the guest colour memory holds.

Now, when that memory repeats one 32-bit value over the surface's footprint (MultisampledColorBytes), the host multisample target is created from the T# (address, extent, format, samples, tile mode), cleared to the colour the value encodes (MultisampledFillClearColor, through the same pending MultisampledColorFill a fill of an existing target uses) and bound. A later draw into the surface finds that target (sameTarget) and keeps its contents. Recorded GPU writes over the range are flushed before the memory is read (GuestMemory::FlushGpuWrites).

Still thrown, each with its own message:

  • colour memory that does not repeat one 32-bit value over the footprint (CPU-written, non-uniform samples);
  • a tile mode or element size with no modeled multisampled footprint (anything but Standard4KB, Standard64KB, RenderTarget);
  • unmapped colour memory;
  • a value with no exact clear colour in the surface's format;
  • a range overlapping another multisampled target (the existing guest-access guard).

Limit worth knowing: the footprint includes the padding up to whole tile blocks. A title that stores a non-zero colour only into the pixels inside the extent leaves the padding different, and that still throws.

Where the case comes from: ProsperoPuzzles clears a 4x canvas on the CPU and resolves it with a shader without drawing into it. Its GL driver is public (blackbearreloaded/ps5-opengl, ps5_clear_msaa4_color in src/gallium/ps5/ps5_screen.c, read at a519561): it stores the packed colour into every sample of every pixel through the mapped memory. In the run that stopped, no fill HLE, no DMA packet and no draw touched the surface before the sample, and the first 0x40000 bytes of its colour memory were all zero.

Tested

Windows 11, MinGW-w64 GCC 15.2, NVIDIA GeForce RTX 4070 Laptop GPU. Not tested on Linux or on a console.

  • Branch at 556890f (feat(agc): sample a multisampled color target through its 2D MSAA texture #4820's branch e7cf856 merged with main dacd785): ctest -j 8, 1106 tests, 0 failed, 8 skipped (agc_driver_stencil_ref_export and seven video-out tests). python tools/check_conventions.py --base origin/main passes.
  • agc_driver_sample_multisample_target (extended): a 16x16 RGBA16F 4x surface no draw rendered to loads zeros from zeroed memory and ones from memory storing half-float ones, at every sample of every pixel, through a guest compute shader; a triangle drawn into the second surface afterwards leaves ones in the samples it does not cover; memory with one differing word at the end of the footprint is refused. With the change reverted the test fails at the first load with the old refusal. Removing only the pending fill makes the load of ones fail (zeros loaded); disabling only the uniformity check makes the refusal check fail. The check after the later draw was not mutated separately.
  • agc_multisample_guest_access: the refusal of a linear multisampled texture no draw rendered to now expects the tile-mode message.
  • ProsperoPuzzles (PPSA99006), which needs perf(agc): present and keep pending only the drawn slice of an array color target #4935 to run, so every run is on a local merge with it. Before the change (feat(agc): sample a multisampled color target through its 2D MSAA texture #4820 + perf(agc): present and keep pending only the drawn slice of an array color target #4935 + main 0214069), from a save with a 2048 game in progress, it stopped at the refusal above in three runs (after 19 s and 24 s in the two that were timed). With this branch merged with perf(agc): present and keep pending only the drawn slice of an array color target #4935 (2e1ef43c0, local only) it ran 45 s from that save until killed in two runs, with no refusal in its log; its driver logged clear-msaa4-color twice in each and the title created 48 canvases. From a clean state it ran 40 s with no refusal.
  • Picture: a frame dumped from the save run shows the menu with every visible thumbnail drawn. The thumbnail of the 91x145 canvas that was cleared on the CPU was not identified in it, and there is no console capture to compare with.
  • Frame times on the menu from a clean state, same local merge (2e1ef43c0): p50 16.68 ms, p99 17.36 ms over 300 presents. No speed claim; this path only runs where the refusal used to throw.

Not covered: CPU writes to a multisampled surface after its host target exists are still not seen by the host (as on main).

Checklist

🤖 Generated with Claude Code

@billythemenace2

Copy link
Copy Markdown

A title case for this PR: SONIC SUPERSTARS (PPSA06888) samples a multisampled texture no draw has rendered to, and with this PR it stops on the tile mode:

AGC graphics: sampling the 1920x1080 multisampled texture 0x269450000 of 2 samples, which no draw rendered to and whose tile mode 4 has no modeled multisampled color footprint, is not implemented

Setup: Windows 11, MinGW-w64 GCC 15, Release, AMD Radeon RX 5700 XT. Base: main 5a6ad0ed3, plus #4820 at 84f636494, this PR at 556890fb3, and #5865, #5375, #5456 and #5882 (the title's earlier stops). With #4820 alone, the title stops one step earlier on sampling the 1920x1080 multisampled texture 0x269250000 of 2 samples, which no draw rendered to, is not implemented.

Merging this PR onto the current #4820 conflicts in MultisampleTarget.cpp, because #4820 now passes the target's active flag to guard (84f636494). The fill path here needs the same change:

    targets().push_back(std::make_unique<MultisampleTarget>(context, use));
    auto& created = *targets().back();
    guard(context, use, created.active);

The lookup loop also needs #4820's if (!target->active->load(std::memory_order_acquire)) continue;.

AI-assisted (Claude Code).

@Gashrod1

Copy link
Copy Markdown
Contributor

Another title for the same stop: DAVE THE DIVER (PPSA19115, 01.041.000) stops here too, with tile mode 4 (kZ64KBX):

AGC graphics: sampling the 1920x1080 multisampled texture 0x231590000 of 2 samples, which no draw rendered to and whose tile mode 4 has no modeled multisampled color footprint, is not implemented

Composition: main 956269a + #4820 (84f6364) + #5496 (213050f) + this PR (556890f), merged by hand. The only code change was the createFilled call adapted to the 3-argument guard(context, use, created.active). Windows 11, MinGW-w64 GCC 15.2.0, RTX 5060.

kZ64KBX is a depth swizzle. To go further I mapped the Z/S/D 64 KiB XOR modes to the Standard64KB footprint locally. That got the title in game, but it is a workaround, not a model of the depth footprint. Bring-up thread: #5963.

AI-assisted: yes

@adriadam10 adriadam10 self-assigned this Oct 14, 2026
nicolasabosch and others added 3 commits October 14, 2026 21:33
…uniform color memory

A multisampled texture whose surface no draw has rendered to was refused.
Its samples are the guest color memory, so when that memory repeats one
32-bit value over the surface's footprint, the host target is created and
cleared to the color the value encodes, and a later draw into the surface
reuses it. Memory that does not repeat one value, an unmodeled footprint
and a value with no exact clear color keep throwing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rge with main

Co-Authored-By: Claude <noreply@anthropic.com>
@adriadam10

Copy link
Copy Markdown
Collaborator

#4820 is on main now, so I replayed this PR's own commit (0f3f690) on top of it and pushed the result. What I added:

  • createFilled called guard(context, use); guard takes the target's active flag on main now, so it did not compile. The target is created first and created.active is passed.
  • A line under docs/dev/debt/libSceAgcDriver.md for the inferred uniform-memory behaviour and what still throws.

Checked on the rebased tree: build and the AGC tests (agc_driver_sample_multisample_target, agc_driver_recorder, agc_multisample_guest_access, guest_texture_resource) pass, the three that exercise the new path fail with the change reverted, and the conventions check passes. agc_driver_tessellation crashes inside RADV here and does not touch this code.

Not covered, as the PR says: titles that stop on tile mode 4 (kZ64KBX, reported above for PPSA06888 and PPSA19115) still throw with the tile-mode message. Merging once the full Build is green.

@adriadam10
adriadam10 force-pushed the fix/agc-sample-undrawn-msaa branch from 556890f to f093cf4 Compare October 14, 2026 19:43
@github-actions

github-actions Bot commented Oct 14, 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
#5496 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp L2
#5524 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp L2
#5554 core/libs/prx/libSceAgcDriver/Graphics/src/MultisampleTarget.cpp L2

@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 14, 2026
@adriadam10
adriadam10 merged commit 03f49fc into boykopovar:main Oct 14, 2026
8 checks passed
@nicolasabosch
nicolasabosch deleted the fix/agc-sample-undrawn-msaa branch October 14, 2026 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* build shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants