Repository navigation
fix(agc): start a new depth surface from the storage writes over its plane - #5446
Merged
adriadam10 merged 2 commits intoOct 14, 2026
Merged
adriadam10 merged 2 commits into
adriadam10 merged 2 commits into
Conversation
This was referenced Oct 13, 2026
11 tasks done
This was referenced Oct 13, 2026
RINNECODER
added a commit
to RINNECODER/AnyPS5
that referenced
this pull request
Oct 14, 2026
#484) * fix(agc): sample 10_10_10_2 SNorm and SInt textures Guest texture formats 51 (SNorm) and 55 (SInt) of the 10-bit packed group were missing from the texture lookup and the descriptor format table, so a descriptor using them threw 'unsupported guest texture format' while the UNorm (50) and UInt (54) siblings worked. Map them to A2B10G10R10_SNORM_PACK32 / A2B10G10R10_SINT_PACK32, classify them for sampling (float / signed integer), and list SINT among the integer formats. TextureFormat.cpp and RdnaDescriptorFormat.cpp are shared with our Metal path. Metal has no signed RGB10A2 pixel format, so MetalTexture still refuses these two formats, now by name ("no supported pixel format for guest format 51") instead of failing the shared lookup. Recorded in TechnicalDebt. Test: metal_utility_tests (TextureResource.mm) checks that 51 and 55 resolve to the signed packings, are four bytes and not block compressed, and that a native texture refuses them by name. The upstream RunTextureFormatTests cases (tests/TextureFormat.cpp) are carried too; they build in the Vulkan graphics tests, not on macOS. Ported from upstream PR boykopovar#5149 (cherry picked from commit c6a4223) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj * fix(agc): share a depth plane's contents across its stencil pairings on Metal A title can bind one depth plane with several stencil planes (or one stencil plane with several depth planes). MetalDepthSurfaceCache keys a surface by both plane addresses, so each pairing got its own textures, cleared on creation: depth written through one pairing never reached the others, and later draws tested against stale depth. The cache now records, per depth and per stencil plane address, the surface that bound it last. Binding a single-sample surface without DB_DEPTH_VIEW slices takes each of its planes from that surface when both have the same format and extent: a blit copies the texture layer, or, for a combined Depth32Float_Stencil8 texture, only that plane's aspect through a buffer. The blit is committed on the backend's queue, after the passes that wrote the other surface and before the draw that binds this one. Multisampled surfaces and depth-array slices keep their own planes, as before. Clear() forgets the owners. Upstream does this in the Vulkan DepthSurface.cpp (vkCmdCopyImage, plus a pending fast clear carried over); our Metal path has no HTILE fast-clear state, so only the copy applies. Test: metal_utility_tests (DepthResource.mm, sharedPlanes) binds a depth plane with a second stencil plane, binds the same pairing again, goes back to the first pairing, binds the stencil plane with another depth plane and binds a pairing of another extent, for D16S8 and D32S8. It fails without this change ("Sampled depth plane returned incorrect GPU values"). Ported from upstream PR boykopovar#4683 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj * fix(agc): retire a Metal depth surface once another writer covers its memory A depth surface lives only in its Metal textures: nothing writes it back to guest memory. When a color target or another depth target is later bound over its memory, the surface still answered every view of that address, so a texture of another layout there threw ("resident depth view is incompatible") and a matching one read the dead depth. The surface is now retired by the last writer: - binding a color target drops the depth surfaces whose depth or stencil plane it overlaps from the draw's resident surfaces (the ones views and depth-array gathers search); - binding a depth target moves its surface last and retires the other surfaces its planes overlap: another stencil pairing of its depth plane, or a surface of another extent at its address. The lookup then finds the surface bound last, not the one bound first. The cache keeps a retired surface; binding its target again brings it back with the texels it held (with boykopovar#4683's change, a plane another pairing wrote since is copied over first). Not ported: upstream also retires a surface on a written storage image over its planes and, with write watching, on CPU or driver stores. Our Metal path has no storage view of a depth plane (a storage image at its address throws) and no write tracking for this cache, so those cases keep throwing. Recorded in TechnicalDebt. Test: metal_utility_tests (DepthResource.mm, retiredDepth): writes next to the planes retire nothing; the surface being bound is kept; a writer inside the depth or stencil plane retires it; the cache still returns it; binding a depth target of another extent at the address retires the other surface. Ported from upstream PR #454 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj * docs(agc): record the Metal depth-plane views and storage writes not modeled Upstream boykopovar#4722, boykopovar#4873, boykopovar#5054, boykopovar#5113 and boykopovar#5446 fix the Vulkan depth-plane copy (padded detiled rows and slices, a cube with a mip chain) and the storage writes over a depth plane. Our Metal path has neither: a resident depth surface is served only as a single-level 2D view or a 2D array of its extent, and a storage image over its plane is bound to guest memory. Record that in TechnicalDebt instead of porting the fixes. Refs upstream-pr-batch-1 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DgW4n3qYgi5jAAohEmXRQj --------- Co-authored-by: xXPBSXx <20614131+xXPBSXx@users.noreply.github.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Contributor
|
NVIDIA run at this head (
|
Nesyth
force-pushed
the
fix/agc-depth-surface-from-storage-writes
branch
from
October 14, 2026 18:46
284011b to
2cf6b09
Compare
Collaborator
|
Checked: the merged tree builds and |
13 tasks done
9 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A depth surface the driver creates for a depth target starts from
DB_DEPTH_CLEAR: theDepthSurfaceconstructor clears the new image and never reads guest memory. Storage writes made to the plane's memory before any surface existed at that address therefore do not reach it. At that time the view is an ordinary storage image, since a plane's own storage view (#4500) exists only once its surface does. The first depth test against the plane reads the clear value instead of what the kernel wrote.DepthSurfaceViewnow gives a surface it creates for a target without HTILE (DB_HTILE_DATA_BASE0), and without an earlier surface whose planes it takes over (#4683), the depth those writes left (Graphics/src/DepthSurface.cpp):StorageTexture::FindPending), then a cached one of exactly the plane's size (FindLive).UINT,UNORM,SINT,SNORMorSFLOAT) for a 16-bit plane (D16, D16S8) or an R32 format (SFLOAT,UINTorSINT) for a 32-bit one (D32, D32S8).Refresh). Its layer 0 is then copied into the surface's depth aspect through the plane's staging buffer: in the active recorder, which keeps the image alive until the copy has run, or else in a batch of its own that is submitted and waited for.DB_STENCIL_CLEAR.DB_DEPTH_CLEAR, as onmain:D16_UNORM_S8_UINT, fix(agc): store D16 depth with stencil as D24 or D32 where the device lacks it #3846);DepthSurfaceViewfinds again for the same target. fix(agc): store a written storage image over a depth plane into the surface #5113 keeps such a surface up to date when a storage image over its plane is stored back.DepthSurfaceViewnow takesGuestMemory::GpuMutex()(recursive) before the surfaces' mutex, the same orderDepthSurfaceTextureuses, because the lookup andRefreshrun under the GPU mutex. Its only caller in the driver is the draw path.docs/dev/debt/libSceAgcDriver.mdrecords the rule. On the console the plane is the memory the kernel wrote, so there is nothing to seed; none of this is measured there.Ready or Not (PPSA18605) clears its shadow maps this way. Traced with temporary logging on my bring-up build (run 205): six 1024x1024 D16 planes were each written through a storage image, then bound as a depth target for the first time and created from
DB_DEPTH_CLEAR0.0 without HTILE. Onmainthe two operators in the main menu are black silhouettes, while the PS5 shows a red rim light on them. With this change they are lit (see Tested).Test: new
tests/execution/DepthSurfaceFromStorage.cpp(ctestagc_driver_depth_surface_from_storage). A compute kernel stores row 0 of a 64x32 plane whose memory holds 0.25, 32 values from 0 to 0.94, through a storage view. A full-screen draw at depth 0.5 withLESSthen tests against a new surface over the plane, whose clear value is 1.0. The draw passes 15 of the 2048 pixels when the surface took the storage image (the stored values above 0.5) and all 2048 when the surface started from its clear value:FindLive): 15 each;FindPendingfinds it; the case requiresFindLiveto miss it;NoteHtileDepthClear) after the storage write: 2048;EQUAL 0draw without the depth test then passes all 2048 pixels (the stencil plane kept its clear value).Not covered by a test:
FindPendingcomes first, so the one with pending results.ctest -R depth, 10 tests) still pass. It keeps an image that is still cached at the address from overwriting depth drawn after the image was written.Related, both merged:
main32e810d, which has fix(agc): store a written storage image over a depth plane into the surface #5113, this PR's test fails its five new-surface cases ("2048 pixels passed, expected 15"). Earlier, onmain18c438b, Ready or Not's menu operators stayed black with fix(agc): store a written storage image over a depth plane into the surface #5113 alone (run 702) and were lit with both (run 709).DepthSurfaceViewthat creates a surface, so that a new surface takes its planes from their last owner. TheTakeStoragecall now sits in that block and runs only when there is no such owner.Tested
Windows 11 Pro 10.0.22631, AMD Ryzen 7 7800X3D, AMD Radeon RX 7900 XTX, WinLibs GCC 15.2.0, Release,
BUILD_TESTING=ON, this head onmain32e810d:ctest -R "^agc_" -j 2: 539 of 544 pass, with 4 skips. The failure,agc_driver_layered_volume_vertex_no_gpl("vertex draw: slice 1 texel (0, 0) channel 0 is 64, expected 255"), also fails onmain4ae78dc without this change on this GPU.agc_driver_depth_surface_from_storage:main32e810d without this change, it fails in the five new-surface cases ("a new D32 depth surface over a pending R32F storage write: 2048 pixels passed, expected 15", and the same for D16, the flushed image, the two-layer view and D32S8);target.htileAddress == 0): "a new D32 depth surface whose HTILE was cleared after a storage write: 15 pixels passed, expected 2048";FindLive: "a new D32 depth surface over a flushed R32F storage write: 2048 pixels passed, expected 15";FindLivealone, withoutFindPending: "a new D32 depth surface over a pending R32F storage write through a two-layer 2D array view: 2048 pixels passed, expected 15".tools/check_conventions.py --base origin/mainpasses.Ready or Not (PPSA18605), main menu, measured before this rebase on
main18c438b, whichmaindacd785 differs from only inlibSceAgcDriver(#4949). The runtime'slibsheld the 127 modules built from 18c438b, withlibSceAgcDriverswapped per run, andapp.exeis the title relinked by that commit's relinker.APS5_HOST_IMPORT_MIB=10240, and the runtime'ssaved\ps5\engine.iniraises UE's render fence timeout (g.TimeoutForBlockOnRenderFence=1800000). The red light is measured on the 9 menu frames each run dumped (1280x720), in the 500x380 box around the operators, as the share of pixels with R >= 60 and R at least twice G and B:main(runs 700, 704, 706 and 708): 0.07-0.14%, mean R 14-16: black silhouettes.main+ this change (run 701): 2.9-4.7%, mean R 28-34: lit by the red rim light.main+ fix(agc): store a written storage image over a depth plane into the surface #5113 (run 702): 0.07-0.13%, mean R 16: black silhouettes.main+ this change + fix(agc): store a written storage image over a depth plane into the surface #5113 (run 709): 7.1-10.3%, mean R 30-37: lit.libSceAgcDriverbuilt onmaindacd785 (run 710): 3.2-12.0%, mean R 32-41: lit.Main menu, one dumped frame (1280x720) from each session. Top left:
main18c438b (run 700). Top right:main+ this PR (run 701). Bottom left:main+ #5113 (run 702). Bottom right: the PS5, for reference. The two operators stand in front of a red light. On the PS5 it lights their outlines. Onmainand with #5113 alone they are black silhouettes; with this PR they get the same rim light.Linux: not run.
Checklist
main; no other open PR implements the same functionsNotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file.mdfiles or images added to the repository (attach them to this PR)