Skip to content

feat(agc): sample multisampled depth through a shadow image - #7318

Merged
adriadam10 merged 4 commits into
boykopovar:mainfrom
MarcZX10:codex/msaa-depth-sampling-shadow
Oct 19, 2026
Merged

adriadam10 merged 4 commits into
boykopovar:mainfrom
MarcZX10:codex/msaa-depth-sampling-shadow

Conversation

@MarcZX10

@MarcZX10 MarcZX10 commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

What

Multisampled depth attachments currently add VK_IMAGE_USAGE_SAMPLED_BIT to the native image when a device supports it. Create a separate same-format, same-sample-count shadow image with sampled and transfer-destination usage instead, and copy the native depth samples into it before each per-use read that needs a refresh. This keeps sampled usage off the depth attachment and copies every sample without resolving it. If a device cannot create that shadow-image usage combination but can sample the original attachment as before, retain the direct path as a fallback.

The native multisample depth regression now resolves the cached sampled view again after each raster write and checks the readback, so stale shadow contents fail the test across the supported D16/D32 and 2x/4x/8x cases.

This is the separate shadow-image prerequisite requested in the review of #5499. It does not implement the DB depth-copy draw; #5499 can build on this after it merges.

Tested

Rebased onto current main at 5229b5b82; current head ff332e7efdb05563ca03265d3511810c9f445a52.

Linux x86-64, GCC 12.2.0, Release, BUILD_TESTING=ON: configured and built agc_driver_native_multisample_depth_tests. GCC 12 stops on an unrelated dependent-template parse error in SrtDescriptorEvaluation.cpp; for the local build only, I applied the template disambiguator in that file and restored it afterward. The changed AGC source and regression test compiled successfully on the rebased head.

The regression now writes the first native multisample depth target, binds a second target, then samples the first while it is no longer the active draw target. It checks the depth readback. The shadow image is also allocated on first sampling, avoiding the extra image for eligible surfaces that are never sampled.

ctest --test-dir /tmp/AnyPS5-7318-review --output-on-failure -R '^agc_driver_native_multisample_depth$' --timeout 120 aborts before Vulkan device selection and assertions at the project CPU-feature guard: this Xeon E5-1620 v2 lacks LZCNT, BMI1, BMI2, MOVBE, AVX2 and FMA. No local GPU runtime or title test is claimed. Conventions and git diff --check pass.

Manual overlap review: #6146 changes the out-of-place depth-expand path; #6557 expands HTILE eligibility coverage in the same test fixture; neither implements shadow refresh. #7256 adds layered single-sample depth views and is a separate behavior, though both changes touch DepthSurface.cpp and will need manual integration if both merge. The overlap tool also cannot compare older conflicting PR bases, so its warning is not treated as proof of no overlap.

Checklist

  • Based on current main; no other open PR implements the shadow-image path
  • 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
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: follow-up work in feat(agc): run DB depth copies into a single-sample R32 float target #5499; no title is unblocked by this PR alone
  • GPUs tested: none; the local test stopped at the host CPU guard
  • AI-assisted: yes

@github-actions

github-actions Bot commented Oct 17, 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
PR 6557 core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp L664

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 1368 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/tests/DepthSurfaceReuse.cpp
PR 1395 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 5499 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6146 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6292 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6300 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp
PR 6615 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6683 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6727 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp
PR 6808 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp
PR 6977 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7201 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7332 core/libs/prx/libSceAgcDriver/tests/execution/NativeMultisampleDepth.cpp
PR 7449 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7800 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp

@github-actions github-actions Bot added the agc libSceAgc* label Oct 17, 2026
@MarcZX10

Copy link
Copy Markdown
Contributor Author

Overlap note after comparing #7256: the features are separate—#7256 adds single-sample layered depth views, while this PR adds a sampled shadow for native multisampled depth. The layered-depth path explicitly rejects multisampled layers, so I found no shared behavior or prerequisite. However, both change DepthSurface construction/usage setup, and a merge preview of the two heads reports a content conflict in DepthSurface.cpp; they will need a manual integration if both remain active. This PR does not include or depend on #7256.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks for splitting the shadow out of #5499. I ran the PR's own test on a device, and the shadow path returns stale depth.

Run: RTX 4090, Linux, main a8581d3 + this PR at ea33567, NativeMultisampleDepth added to ANYPS5_AGC_EXECUTION_TESTS.

multisample_depth depth_retirement depth_clears depth_clear_refusals
this PR Failed Passed Failed Failed
useSampleShadow = false (direct path) Passed Passed Passed Passed
skip in refreshSampleShadow removed (always copy) Passed Passed Passed Passed

Every failure is native depth sampled fetch lost a raster sample or its component mapping (NativeMultisampleDepth.cpp:710).

With the copy forced every time, all four pass. So the copy and its barriers are fine, and the problem is the skip at DepthSurface.cpp:590:

if (!drawn && sampleShadowWritten == written) return;

It treats the shadow as current when it isn't. Two things in the code look like they cause it:

  • A surface that is created without inheriting can keep written == 0: the member starts at 0 (DepthSurface.cpp:751), and :1341 only bumps it for !created || inherits. sampleShadowWritten also starts at 0, and :606 stores 0 after a drawn copy. For such a surface, 0 == 0 reads as "already copied", both before the shadow is first filled and after every drawn refresh.
  • Raster writes into the depth target don't bump written. Only drawn (found == drawnDepth, :1469) covers them, and drawnDepth is reset by ClearDepthSurfaces (:1414) and moves when another depth target is bound. A raster write made before that reset is never copied.

Could you:

  1. Make the skip key on a counter that changes on every write to the depth image, raster writes included, or drop the skip. Don't use 0 as a "copy needed" value that written can also hold.
  2. Add a test case that samples the depth after another depth target has been bound (so drawn is false). The current cases keep the target as drawnDepth throughout, so the written branch never runs.
  3. Post the four agc_driver_native_* results from a device. Linux CI runs them on llvmpipe, which isn't enough for this path on its own.

Not blocking, but worth looking at in the same pass:

  • The shadow is created up front for every sampling-eligible MSAA depth surface, which doubles that surface's memory even if it is never sampled. Creating it on first Sampled() would avoid that.
  • While a target stays drawnDepth, each sampling use copies the whole MSAA image (about 33 MB at 1080p 4x D32). A number for the worst case on a title that samples MSAA depth would help.

@MarcZX10
MarcZX10 force-pushed the codex/msaa-depth-sampling-shadow branch from ea33567 to ff332e7 Compare October 18, 2026 01:36
@MarcZX10

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed reproduction; it exposed a real stale-shadow path. I pushed ff332e7ef on top of current main.

The fix creates the shadow only on first sampling, gives every bound native surface a nonzero write generation, and advances the previous surface generation when another depth target becomes active. That makes a later sample of the no-longer-active target refresh its shadow. The regression now rasterizes one target, binds a second target, then samples and checks the first target\x27s depth readback.

The exact rebased head builds agc_driver_native_multisample_depth_tests on Linux after a temporary, restored GCC 12 template disambiguator in unrelated SrtDescriptorEvaluation.cpp. CTest stops before device selection/assertions at this Xeon E5-1620 v2 CPU feature guard, so I cannot provide the requested physical-GPU results from this host. The PR\x27s Linux/Windows test jobs are skipped by workflow gating. If a GPU-capable reviewer is available, could you rerun the four native depth groups on this head and report the selected device and results? I have not claimed a runtime pass.

I also changed shadow allocation to first sample, addressing the unused-shadow memory cost. The copy remains per use while the surface is the active draw target for correctness; I do not have a title measurement for that cost yet.

@jotitadev

Copy link
Copy Markdown
Contributor

I ran head ff332e7 on an Apple GPU. The new regression still catches a stale shadow, and the cause isn't device-specific. There's also a second failure, in agc_driver_graphics. CI skipped linux/windows on this head, so I don't think anyone has run it on a device yet.

Machine: Apple M2 Max, macOS 27.2, x86-64 build under Rosetta. Method: main 81cb863 + @mugurc's ci/macos-series-2 (fa6d8ae, which has the MoltenVK device bring-up) + the open fixes it uses as stand-ins, then this PR merged on top (clean, DepthSurface.cpp auto-merged). Drivers: MoltenVK 1.4.2 (Vulkan SDK 1.4.363), and MoltenVK main with SPIRV-Cross #2704. ANYPS5_REQUIRE_VULKAN=1, ANYPS5_NO_SHADER_CACHE=1.

multisample_depth depth_retirement depth_clears depth_clear_refusals
tree without this PR Passed Passed Passed Passed
this PR Failed 3/3 Passed Failed 3/3 Failed 3/3
this PR, useSampleShadow = false Passed Passed Passed Passed
this PR, skip in refreshSampleShadow removed Passed Passed Passed Passed
this PR + one-line change below Passed 3/3 Passed 3/3 Passed 3/3 Passed 3/3

Both drivers give the same results. The failure is the same one as in the earlier run: native depth sampled fetch lost a raster sample or its component mapping. The shadow copy itself works here, since always copying passes. The problem is the skip.

Where it goes stale. It fails in the second loop pass (offset 0.25). Every sample reads 0.0625, the depth from the offset-0 raster, where 0.3125 is expected. In that pass:

  1. DepthSurfaceView(context, target) bumps written.
  2. CachedSampledTexture at the top of the loop runs before the raster. It refreshes the shadow and sets sampleShadowWritten = written.
  3. The raster then writes the target, but nothing bumps written after it.
  4. The second CachedSampledTexture and the fetch skip the copy.

The first pass only works because binding otherTarget bumps written again. With the lookup at the top of the loop removed, all four pass.

A one-line fix. In DepthSurfaceTexture, the single-sample path passes found == drawnDepth to Sampled, but the multisample path calls found->Sampled(words, resource, components) without it. So a sample of the bound target never forces a refresh:

         Recorder::CountBarriers(Recorder::CommandClass::Draw, 1);
-        return found->Sampled(words, resource, components);
+        return found->Sampled(words, resource, components, found == drawnDepth);

With that change, all five agc_driver_native_*depth* tests pass 3/3 on both drivers.

Second failure: agc_driver_graphics. It fails on every run with the PR and passes without it:

AGC graphics: a supported MSAA depth image must allow sampled views

That's tests/DepthSurfaceReuse.cpp:339 (TestMultisampleDepthOwnership, mocked Vulkan). It still expects VK_IMAGE_USAGE_SAMPLED_BIT on the attachment when the format supports sampled MSAA. With this PR, that's exactly the case where the attachment no longer gets it. So it should fail on any device. It probably needs to check the shadow image's usage instead, or that the attachment does not get sampled usage.

Full ^agc|^shader suite with the PR and the one-line change (MoltenVK 1.4.2, -j1 --timeout 30, flip/display/lds_atomics64 excluded): the 46 failures of the tree without the PR, plus agc_driver_graphics.

GPUs tested: Apple M2 Max (MoltenVK 1.4.2; MoltenVK main + SPIRV-Cross #2704)

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@MarcZX10

Copy link
Copy Markdown
Contributor Author

I checked your report against the current head ff332e7ef: the multisample path at DepthSurface.cpp:1454 calls found->Sampled(words, resource, components) without found == drawnDepth, while the single-sample path at :1472 passes that flag. The mocked ownership test at DepthSurfaceReuse.cpp:339 also still expects VK_IMAGE_USAGE_SAMPLED_BIT on the native attachment, which conflicts with the shadow-image design.

The reproduced stale-shadow failure is therefore actionable. Please pass found == drawnDepth through the multisample path and update the ownership test to assert the native attachment stays unsampled while checking the shadow image's sampled usage. The reported four-group results on the M2 Max make this a strong validation case; please rerun those after the fix, including agc_driver_graphics.

I have not pushed a change to your branch. My local GCC 12 test build is currently blocked earlier by an unrelated SrtDescriptorEvaluation.cpp dependent-template parse error already tracked by open fixes #6306/#6738.

@MarcZX10

Copy link
Copy Markdown
Contributor Author

Fixed in 132dbd1f0: the multisample lookup now passes whether the owner is the currently bound depth target, matching the single-sample path and forcing shadow refresh for the drawn target. Updated TestMultisampleDepthOwnership to assert sampled usage remains off the native attachment when the sampled shadow path is available.

Validation: agc_driver_graphics_tests built and the isolated --depth-surface-reuse fixture passed. This host normally exits at the CPU-feature guard, so I temporarily bypassed only that guard for the fixture run; GCC 12 also required a temporary dependent-template disambiguator in unrelated SrtDescriptorEvaluation.cpp. Both temporary edits were restored and are not in the commit. No GPU execution result is available from this host. check_conventions.py --base upstream/main passes; GitHub eligibility, conventions, labels, and report checks pass; platform build/test jobs are skipped by workflow gating.

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

Copy link
Copy Markdown
Collaborator

Thanks for the rework. Review and CI both pass on 132dbd1, but the PR now conflicts with main after #6635 (MSAA array descriptor shape) merged, in Graphics/src/DepthSurface.cpp and tests/execution/NativeMultisampleDepth.cpp.

It isn't a mechanical conflict, so it needs your rebase rather than ours. #6635 adds multisampleViewType to the sampled-depth texture key (the key grows from 12 to 13 words), picks the view type from it for multisampled targets, and passes it to Sampled(..., multisampleViewType). This PR moves the texture-cache lookup after the shadow refresh, builds the Texture over the shadow image and calls Sampled(..., found == drawnDepth). Both rewrite the key and lookup order, the view-type choice, the Texture creation and the Sampled() arguments. So the rebase has to decide two things: does the array view sit over the shadow image, and does the cache key cover both the view type and the shadow? The test hunk overlaps the same way (#6635's array-texture checks vs this PR's shadow-view checks).

Could you rebase onto current main, keep #6635's array view for multisampled targets and keep both sets of test checks? After that we'll re-review the delta and run full CI.

@MarcZX10
MarcZX10 force-pushed the codex/msaa-depth-sampling-shadow branch from 132dbd1 to 91be80b Compare October 18, 2026 22:11
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 18, 2026
@MarcZX10

Copy link
Copy Markdown
Contributor Author

Rebased this branch onto current main and resolved the #6635 overlap while preserving the array view type in the sample-view cache key and in texture creation, refreshing the bound depth shadow before cache reuse, and retaining both the existing array-view regression coverage and the new depth-surface reuse checks.\n\nValidation: the agc_driver_graphics_tests target builds successfully with Ninja. I attempted to run it, but this Linux host exits at the emulator CPU capability guard before any tests execute (Xeon E5-1620 v2 lacks AVX2, BMI1/2, LZCNT, MOVBE, and FMA). Hosted full build jobs are skipped by repository policy, and I cannot manually dispatch the workflow because GitHub returns HTTP 403 requiring admin rights. The branch passes the repository conventions and diff checks.

@jotitadev jotitadev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A device run of the rebased head 91be80b44, since the host you rebased on stops at the CPU guard and hosted CI hasn't run on it. Everything passes on an RTX 3070 and on lavapipe, and the new regression is what catches the stale shadow I hit on ff332e7ef.

Machine: Ubuntu 26.04, i7-8700, GCC 15.2, Release, BUILD_TESTING=ON, every execution test built. NVIDIA GeForce RTX 3070 (driver 595.99.02) and lavapipe (Mesa 26.0.3, LLVM 21.1.8) on the same host. PR merged onto main 84c1a4d9a (clean, so #6635 is in). ANYPS5_NO_SHADER_CACHE=1, -j1 --timeout 30.

Tests: agc_driver_native_multisample_depth, _depth_retirement, _depth_clears, _depth_clear_refusals, _depth_bda_clear, agc_driver_native_msaa_vertex_input and agc_driver_graphics (which has TestMultisampleDepthOwnership).

RTX 3070 lavapipe
head 91be80b44 7/7, 3 runs 7/7 (depth_bda_clear skips, as on main)
only the PR's two test files, on main the 6 native tests pass; agc_driver_graphics fails same
PR, early return in refreshSampleShadow removed (always copy) 6/6 native pass
PR, useSampleShadow = false (direct path) 6/6 native pass
PR, drawnDepth->written = ++depthWrites removed from DepthSurfaceView multisample_depth, depth_clears, depth_clear_refusals fail same 3 fail
  • On lavapipe "pass" includes the depth_bda_clear skip.
  • agc_driver_graphics on main fails with a native MSAA depth attachment must keep sampled usage off when the shadow path is available. That is the updated ownership check, so it is expected. With the PR it passes.
  • With the line that advances the previous target's generation removed, the three failures are native depth sampled fetch lost a raster sample or its component mapping, the same three tests and message as the stale shadow on ff332e7ef. So the new "bind another target, then sample the first" case does guard that bump. The 6 native tests pass on main because main samples the attachment directly and has no shadow to go stale.
  • The 2D_ARRAY checks from #6635 in the same loop pass on both devices, so the array view over the shadow image comes back from the cache unchanged across the raster writes.

Full ^agc suite with the PR (731 tests, without display/flip/lds_atomics64): the same result as main on both devices. lavapipe 731/731 with the same 50 skips. RTX 3070 730/731 with the same skip, and the one failure is agc_driver_bda_device, which fails on main here too (#7584: a fence timeout, Vulkan result 2, in this run, and device lost, -4, on main).

Not run: Windows, AMD, any title, and the memory or copy cost of the shadow.

GPUs tested: NVIDIA GeForce RTX 3070 (595.99.02) and lavapipe (Mesa 26.0.3), Ubuntu 26.04

Written with AI assistance (Claude Code). I ran the builds and tests above on my machine.

@adriadam10 adriadam10 self-assigned this Oct 19, 2026
@MarcZX10

Copy link
Copy Markdown
Contributor Author

Thanks for running the rebased head on both the RTX 3070 and lavapipe. The full reported suite passes on both, and the new regression catches the stale shadow image from the earlier head. I’ll record this as current-head device validation, with the tested merged-main composition including #6635.

@adriadam10
adriadam10 merged commit 585075c into boykopovar:main Oct 19, 2026
10 checks passed
@adriadam10

Copy link
Copy Markdown
Collaborator

Read the diff against the earlier findings (stale shadow after switching attachments, drawnDepth propagation, ownership test): all are addressed on this head. The new regression (bind another target, then sample the first) is covered by @jotitadev's RTX 3070 + lavapipe run, including the mutation that fails without the generation bump, and the ^agc suite matches main. Full Linux/Windows build went through the merge queue; 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