Skip to content

fix(agc): depth fast clears through the HTILE metadata - #374

Merged
adriadam10 merged 4 commits into
boykopovar:mainfrom
MichonGoddijn231849:fix/depth-htile-fast-clear
Oct 11, 2026
Merged

adriadam10 merged 4 commits into
boykopovar:mainfrom
MichonGoddijn231849:fix/depth-htile-fast-clear

Conversation

@MichonGoddijn231849

@MichonGoddijn231849 MichonGoddijn231849 commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

What

A title can fast-clear a depth surface by filling its HTILE metadata instead of drawing a clear. A ZMASK of 0 marks every tile as holding DB_DEPTH_CLEAR / DB_STENCIL_CLEAR.

  • Depth surfaces were only cleared when they were made, so every later frame kept the previous frame's depth and stencil. Most draws then failed the depth test.
  • The depth target now records DB_HTILE_DATA_BASE (0x005, high byte in 0x01e). Both registers join the draw key.
  • Uniform DMA_DATA immediate fills and buffer-fill kernels whose ZMASK is 0 and that cover a depth surface's HTILE mark the surface cleared.
  • The surface's next use, as a depth target or as a texture, clears it to that use's clear registers.

GTA V clears its G-buffer depth this way every frame: DMA_DATA fills of 0 over 0x48000 bytes of HTILE. Without this, only the first frame renders. From the second frame on, the G-buffer keeps the first frame's contents and the scene goes black.

Tested

Windows 11 Pro, AMD Ryzen 5 7600, NVIDIA GeForce RTX 5080 (driver 32.0.16.1074), 32 GB DDR5-5600, MinGW-w64 GCC 15.2.0.

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
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes. Written with Claude (Anthropic) under my direction; I reviewed and tested it.

🤖 Generated with Claude Code

@oneandonlydean

Copy link
Copy Markdown
Collaborator

This looks right for depth-only surfaces. With a stencil plane, though, any fill whose ZMASK is 0 clears both aspects. In the depth plus stencil HTILE layout the stencil state is in its own SR0, SR1 and SMEM fields: radv's radv_get_htile_mask (radv_meta_clear.c) only touches zmask/zdelta/zbase for a depth clear and only sr0/sr1/smem for a stencil clear. So a fill that clears depth only would also reset the stencil to DB_STENCIL_CLEAR here. Could the stencil aspect be cleared only when the fill's stencil fields say cleared too, and the depth aspect alone otherwise? I'd also only take DB_HTILE_DATA_BASE when TILE_SURFACE_ENABLE is set. A small test for NoteDepthMetadataFill (depth only, depth plus stencil, a fill that doesn't cover the whole HTILE) would pin it down.

@MichonGoddijn231849

MichonGoddijn231849 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks, both points are fixed in e384824 (rebased onto main too):

  • Aspects: a fill covering the whole HTILE now clears the depth when ZMask (bits 0..3) is 0. When the HTILE holds the stencil state (DB_STENCIL_INFO.TILE_STENCIL_DISABLE clear), it also clears the stencil when SMem (bits 8..9) is 0. That matches radv's depth plus stencil layout and its fast-clear value (radv_get_htile_fast_clear_value, SMem 0, SR0/SR1 3). A fill that writes an aspect's fields as not cleared cancels that aspect's pending clear, so a depth-only clear no longer resets the stencil.
  • TILE_SURFACE_ENABLE: DB_HTILE_DATA_BASE is taken only when DB_Z_INFO.TILE_SURFACE_ENABLE is set.
  • Tests: the decode with and without TILE_SURFACE_ENABLE and TILE_STENCIL_DISABLE, plus HtileFillClears and HtileFillCovers. The cases are depth only, depth plus stencil, stencil only, an expanded 0xffffffff fill, a partial fill, a fill starting past the HTILE base, and no HTILE.

@MichonGoddijn231849
MichonGoddijn231849 force-pushed the fix/depth-htile-fast-clear branch from 086e8b9 to e384824 Compare October 4, 2026 08:36
@oneandonlydean

Copy link
Copy Markdown
Collaborator

Thanks, that matches radv's depth plus stencil HTILE layout (ZMask in bits 0 to 3, SMem in bits 8 and 9, and HTILE_ZS_CLEAR_REG writes SMem 0), and bit 29 of DB_Z_INFO and of DB_STENCIL_INFO are the right TILE_SURFACE_ENABLE and TILE_STENCIL_DISABLE bits. Cancelling an aspect's pending clear when a fill marks it as not cleared is a nice touch. Merged with current main it builds and passes the full ctest here. Since depth fast clears change what PPSA21564 renders, I'll run it there on my integration branch first; after that it's ready to merge from my side.

@github-actions github-actions Bot added the needs rebase Conflicts with main, rebase needed label Oct 4, 2026
@MichonGoddijn231849
MichonGoddijn231849 force-pushed the fix/depth-htile-fast-clear branch from e384824 to b504800 Compare October 4, 2026 10:12
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 4, 2026
@MichonGoddijn231849
MichonGoddijn231849 force-pushed the fix/depth-htile-fast-clear branch 2 times, most recently from 62040ff to f7de926 Compare October 6, 2026 15:43
@github-actions

github-actions Bot commented Oct 7, 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
#454 core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClear.cpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearHarness.hpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearSubmit.cpp
#2076 core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClear.cpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearHarness.hpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearSubmit.cpp
#2121 core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClear.cpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearHarness.hpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearSubmit.cpp
#2132 core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClear.cpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearHarness.hpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearSubmit.cpp
#2169 core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClear.cpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearHarness.hpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearSubmit.cpp
#2490 core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClear.cpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearHarness.hpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearSubmit.cpp
#2491 core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClear.cpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearHarness.hpp
core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearSubmit.cpp

@adriadam10 adriadam10 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Built merged onto current main (e79c971): full ctest passes. The new checks cover the register decode and the two pure helpers. A few things before merging:

  • No test reaches the clear itself. Nothing calls NoteDepthMetadataFill, so ApplyFastClear and both triggers, the DMA_DATA immediate (PacketMemory.cpp:59-63) and the fill kernel (BufferFill.cpp:173), run in no test. Removing them leaves ctest green. Could you add an execution test (the Visual.cpp harness can draw with a depth target)? It would cover: a DMA_DATA fill of 0 over the HTILE, then a depth-tested draw that sees DB_DEPTH_CLEAR; a depth-only fill on a depth plus stencil HTILE that keeps the stencil; a fill shorter than the HTILE that clears nothing; and sampling the surface after a fill.
  • TechnicalDebt (Unknown function info). Two choices here are inferred, not measured, and should each get a line:
    • HtileFillCovers treats ceil(w/8) * ceil(h/8) * 4 bytes from the HTILE base as the whole surface. The real gfx10 HTILE is pipe-aligned and larger (addrlib), so a fill of only its first part would clear every tile.
    • The sampled path clears to DB_DEPTH_CLEAR. For a depth-only HTILE, the clear word also carries the value: radv writes minz = maxz = round(depth * 0x3fff) (HTILE_Z_CLEAR_REG, src/amd/common/ac_descriptors.h), and only allows TC-compatible fast clears to 0.0 or 1.0. Which of the two the texture unit returns for a cleared tile wasn't measured.
  • Reuse: PacketMemory.cpp:60-61 repeats dmaSource/dmaDestination from Pm4.cpp:82-88. Please expose those (or decode through Pm4) instead of a second copy of the bit layout.

@oneandonlydean you planned a PPSA21564 run on your integration branch before calling this ready. Did it go through?

@MichonGoddijn231849
MichonGoddijn231849 force-pushed the fix/depth-htile-fast-clear branch from 94d93e0 to 6d0fe18 Compare October 10, 2026 22:38
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 10, 2026
@orishamir

Copy link
Copy Markdown
Contributor

Replica run of the new head 6d0fe18c (same setup as above: the CI image rebuilt locally, ubuntu:24.04, GCC 13.3, llvmpipe LLVM 20.1.2, SPIRV-Tools on, CI env, userfaultfd write watch active; the tree as pushed on main 379d8b4).

  • The rebase is clean: the patch content is identical to 94d93e00 (interdiff of added and removed lines empty; 14 files, +488/-15).
  • Build and libs clean. Full suite 729/729 three times at --parallel 4. agc_driver_depth_fast_clear_submit 30/30 serial.

AI-assisted: yes (Claude Code Fable 5.1 xhigh)

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

Copy link
Copy Markdown
Collaborator

Conflicts with main again in DepthSurface.hpp and DepthSurface.cpp after #1430; please rebase (and #425 on top). I'll run the manual build on the new head, which covers the re-run.

@Amoenus

Amoenus commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Independent validation found a concrete false positive in the documented HtileFillCovers approximation. Current #374 (6d0fe18c) and #419 (b75dc179) contain the same helper, byte-for-byte.

Reproducer: HtileFillCovers(0x10000, {16, 16}, 0x10000, 16) returns true unchanged. For GFX10 non-RB+, revision 0x82, GB_ADDR_CONFIG=0x00100044, pipe-aligned SW_64KB_Z_X, unchanged AMD PAL places a valid HTILE word at offset 260 (end 264). A 16-byte fill misses it. PAL reports a 32,768-byte physical extent. Checked for 1/2/4/8 samples and D16/D32. The current 16x8 unit example has contiguous valid words; 16x16 exposes the assumption.

Reference: pinned PAL HTILE info/address routines. This is an unchanged source reference, not a PS5 measurement. TechnicalDebt already acknowledges the approximation; this supplies a concrete counterexample.

Local correction tested: require the complete physical extent for the supported layout (32 KiB per 1024x512 metadata block). A partial overlapping fill makes the resident surface throw on its next use; a complete known fill permits recovery. This is conservative, does not model partial tile updates or other layouts, and any general adoption still needs its layout assumptions enforced.

Validation:

  • Unchanged PAL: 40 configurations/run, all logical tiles enumerated, five repetitions (1/2/4/8 samples, D16/D32, even/odd/large extents).
  • Exact extracted helper: original false positive and correction each confirmed five times; five controls restoring the linear bound failed as expected.
  • Adapted local feat(agc): multisampled color and depth targets with CB resolves #419/fix(agc): depth fast clears through the HTILE metadata #374 stack on main 7f1dc965: all 120 libraries/full build and conventions passed; 102 relevant groups x5 = 510 passes, no skips, max 4.06 s, Vulkan/MSAA required and shader caching disabled.
  • Integrated draw tests check partial refusal/recovery through direct notification and DMA/compute submissions. The compute fixture uses 32 workgroups and X group ID; after FlushGpuWrites, every one of the 32,768 guest bytes is checked. This exercises the existing optimized fill route, not native execution of the AMD shader.

Windows 11 / RTX 5080, driver 32.0.16.1062, GCC 15.2.0. The aggregate includes previous local snapshot prerequisites, conflict repairs and the independently verified standard-8x table correction; this is not an unchanged full PR build or Linux/lavapipe validation. Controlled PPSA99902 runs still stop at raw PA_SC_AA_CONFIG=0x2030e003 (31.471/31.004 s, no timeout), so the title has not exercised the HTILE correction. No frame/gameplay or console-equivalence claim. Original dump/baseline runtime preserved. No new PR/push. AI-assisted investigation/local code: Codex.

Fork code and validation update (2026-10-11)

Proposed correction (fork only): partial HTILE-fill handling and regressions. Parent a55955fa is the adapted #374/#419 stack and includes the separate HTILE geometry helper. This is an extracted correction tested in the aggregate, not an isolated full topic build. It remains a conservative proposal for the pinned pipe-aligned layout; other metadata layouts still need enforced decoding guards before general adoption. Independent HTILE helper topic on main 0e90079f has no local MSAA prerequisites; 240 PAL configurations/run, 2,252,352 HTILE checks/run x5 and 15 mutation controls passed. It is source-backed, not a console measurement. AI-assisted: Codex.

MichonGoddijn231849 added a commit to MichonGoddijn231849/AnyPS5-fork that referenced this pull request Oct 11, 2026
…and boykopovar#374

- DepthSurfaceTexture keeps main's rule (9d65003): a view of another extent or an unrelated format over
  a depth surface's memory is reused memory (nullptr). The checks run once, before the plane copy paths.
- A raw depth-bits view (DepthBitsTextureWidth) samples the depth plane directly, as on main; the
  integer plane copy is only for an R16_UINT view that is not one.
- The surface's storage transfer, fast clear and plane copy record on their own device (boykopovar#374's
  RecorderFor).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@MichonGoddijn231849

Copy link
Copy Markdown
Contributor Author

@Amoenus thanks, confirmed and fixed in 90e3c40d (credited): HtileFillCovers now requires the fill to span the pipe-aligned metadata extent, 32 KiB per 1024x512 block (HtileMetadataBytes), and the test has your 16x16 counterexample (a 16- or 264-byte fill covers nothing). The fast clear fixtures fill that extent; the compute fixture now dispatches one group per KiB with TGID_X. A partial fill is still ignored rather than refused (the surface keeps its contents); the TechnicalDebt line says so and names the layout it assumes.

@oneandonlydean #425 is re-stacked on it (847cccc6, 326/326 agc tests on Windows). Main is merging a PR every minute or two this morning, so I'll rebase both onto main once it has quietened down later today rather than now and again in an hour.

MichonGoddijn231849 and others added 4 commits October 11, 2026 10:19
Rebased onto main as one commit (the six commits of boykopovar#374). A uniform fill over a depth surface's whole
HTILE (DMA_DATA, the fill kernels or a CPU store) clears the depth and stencil aspects its ZMask and
SMem fields say, to DB_DEPTH_CLEAR / DB_STENCIL_CLEAR, when the surface is next bound or sampled.

Main's RADV clear_htile_mask recognition (boykopovar#2229) now goes through the same pending clear: an HTILE clear
marks the matching surfaces' depth aspect, and one for a surface not created yet is applied when it is
first bound. Main's HTILE address decode and this one are merged into one (TILE_SURFACE_ENABLE, a
nonzero DB_HTILE_DATA_BASE and a depth format).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`Recorder::Active()` is process-wide. With two devices (agc_driver_depth_fast_clear_submit draws on its
own device; its compute-fill DCB makes the driver create a second one, whose recorder becomes the
active one) a surface's fast clear was recorded into the other device's command buffer, unordered with
the draw: the compute-fill case failed about once in 60-90 runs. A surface now records into the active
recorder only when that recorder is its own device's, and submits on its own device otherwise.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… DepthSurface include

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pe-aligned metadata

A fill was taken to cover the surface when it spanned ceil(w/8)*ceil(h/8)*4 bytes, a linear HTILE layout. In
the pipe-aligned SW_64KB_Z_X layout a surface's HTILE words lie across a 32 KiB metadata block per 1024x512
pixels (AMD PAL gfx10 addrlib: a 16x16 surface has a valid word at offset 260), so a fill of the linear size
cleared tiles it never wrote. HtileMetadataBytes gives that extent; the fast clear tests fill it (the compute
fixture now dispatches one group per KiB with TGID_X).

Reported with a PAL reproducer by @Amoenus.

Co-Authored-By: Amoenus <5391396+Amoenus@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@adriadam10

Copy link
Copy Markdown
Collaborator

Rebased onto current main: the only conflicts were adjacency in DepthSurface.hpp/DepthSurface.cpp (main's DepthSurfaceHolds next to this PR's HtileFill* helpers). I kept both sides, no logic changed.

  • Build + ctest of libSceAgcDriver on the rebased tree: 371/371 pass; agc_driver_graphics, agc_driver_depth_fast_clear and agc_driver_depth_fast_clear_submit run (not skipped), and the last two pass 20 repeats in a row.
  • Without the change the new tests don't build.
  • Third-party runs: PPSA21564 and Dreaming Sarah by @oneandonlydean, the CI image replica by @orishamir, WSL2 and Windows by @Mouhand-Kaddo, and the HtileFillCovers counterexample by @Amoenus (fixed in the head).

Merging once the manual full Build (linux and windows) is green on this head.

@adriadam10
adriadam10 force-pushed the fix/depth-htile-fast-clear branch from 90e3c40 to 56ea14f Compare October 11, 2026 08:23
@github-actions github-actions Bot removed the needs rebase Conflicts with main, rebase needed label Oct 11, 2026
@adriadam10
adriadam10 merged commit 0c617ba into boykopovar:main Oct 11, 2026
14 checks passed
SP4C3B4R-8 pushed a commit to SP4C3B4R-8/AnyPS5 that referenced this pull request Oct 11, 2026
Rebased onto main as one commit (the six commits of boykopovar#374). A uniform fill over a depth surface's whole
HTILE (DMA_DATA, the fill kernels or a CPU store) clears the depth and stencil aspects its ZMask and
SMem fields say, to DB_DEPTH_CLEAR / DB_STENCIL_CLEAR, when the surface is next bound or sampled.

Main's RADV clear_htile_mask recognition (boykopovar#2229) now goes through the same pending clear: an HTILE clear
marks the matching surfaces' depth aspect, and one for a surface not created yet is applied when it is
first bound. Main's HTILE address decode and this one are merged into one (TILE_SURFACE_ENABLE, a
nonzero DB_HTILE_DATA_BASE and a depth format).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MichonGoddijn231849 added a commit to MichonGoddijn231849/AnyPS5-fork that referenced this pull request Oct 11, 2026
…and boykopovar#374

- DepthSurfaceTexture keeps main's rule (9d65003): a view of another extent or an unrelated format over
  a depth surface's memory is reused memory (nullptr). The checks run once, before the plane copy paths.
- A raw depth-bits view (DepthBitsTextureWidth) samples the depth plane directly, as on main; the
  integer plane copy is only for an R16_UINT view that is not one.
- The surface's storage transfer, fast clear and plane copy record on their own device (boykopovar#374's
  RecorderFor).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MichonGoddijn231849 added a commit to MichonGoddijn231849/AnyPS5-fork that referenced this pull request Oct 11, 2026
…and boykopovar#374

- DepthSurfaceTexture keeps main's rule (9d65003): a view of another extent or an unrelated format over
  a depth surface's memory is reused memory (nullptr). The checks run once, before the plane copy paths.
- A raw depth-bits view (DepthBitsTextureWidth) samples the depth plane directly, as on main; the
  integer plane copy is only for an R16_UINT view that is not one.
- The surface's storage transfer, fast clear and plane copy record on their own device (boykopovar#374's
  RecorderFor).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MichonGoddijn231849 added a commit to MichonGoddijn231849/AnyPS5-fork that referenced this pull request Oct 13, 2026
With APS5_PIPELINE_DMA (on by default since the previous commit) a DMA_DATA was enqueued before the
packet path's metadata notes ran, so a fill of 0 over a depth surface's HTILE (boykopovar#374) or a color
target's CMASK (boykopovar#419) never reached the surface: agc_driver_depth_fast_clear_submit failed. The
queued commit now notes the fill in order before it stores it, and a DMA copy is checked against
multisampled targets at enqueue, as on the packet path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MichonGoddijn231849 added a commit to MichonGoddijn231849/AnyPS5-fork that referenced this pull request Oct 13, 2026
With APS5_PIPELINE_DMA (on by default since the previous commit) a DMA_DATA was enqueued before the
packet path's metadata notes ran, so a fill of 0 over a depth surface's HTILE (boykopovar#374) or a color
target's CMASK (boykopovar#419) never reached the surface: agc_driver_depth_fast_clear_submit failed. The
queued commit now notes the fill in order before it stores it, and a DMA copy is checked against
multisampled targets at enqueue, as on the packet path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JJenkx pushed a commit to JJenkx/AnyPS5 that referenced this pull request Oct 13, 2026
With APS5_PIPELINE_DMA (on by default since the previous commit) a DMA_DATA was enqueued before the
packet path's metadata notes ran, so a fill of 0 over a depth surface's HTILE (boykopovar#374) or a color
target's CMASK (boykopovar#419) never reached the surface: agc_driver_depth_fast_clear_submit failed. The
queued commit now notes the fill in order before it stores it, and a DMA copy is checked against
multisampled targets at enqueue, as on the packet path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
adriadam10 pushed a commit to MichonGoddijn231849/AnyPS5-fork that referenced this pull request Oct 15, 2026
With APS5_PIPELINE_DMA=1 a DMA_DATA was enqueued before the
packet path's metadata notes ran, so a fill of 0 over a depth surface's HTILE (boykopovar#374) or a color
target's CMASK (boykopovar#419) never reached the surface: agc_driver_depth_fast_clear_submit failed. The
queued commit now notes the fill in order before it stores it, and a DMA copy is checked against
multisampled targets at enqueue, as on the packet path.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit fda7dbf)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants