Skip to content

fix(agc): take a later slice's HTILE clear when every word has ZMask 0 - #7194

Merged
oneandonlydean merged 1 commit into
boykopovar:mainfrom
XairRrR:fix/agc-htile-later-slice-clear
Oct 19, 2026
Merged

oneandonlydean merged 1 commit into
boykopovar:mainfrom
XairRrR:fix/agc-htile-later-slice-clear

Conversation

@XairRrR

@XairRrR XairRrR commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

What

When a depth target is bound, storeBackClearedHtile reads its HTILE if the HTILE was written since the last read. If every word has ZMask 0, the surface takes the fast clear to DB_DEPTH_CLEAR and the words are stored back as expanded. For slice 0 this holds whatever the words' Min/Max Z are. For slice 1 or later of a depth array, main takes the clear only when all words are equal (cc022df, from #5627). Otherwise it keeps the previous depth.

Uncharted: Legacy of Thieves Collection (PPSA05684, #4001) has a sun cascade array: four 2048x2048 D16 slices at 0x1492ab0000 + 0x800000 per slice. Their HTILE starts at 0x14929b0000, 0x40000 per slice (HtileMetadataBytes, as #5111 lays it out). The title resets the whole array every frame by rewriting the HTILE itself. It uses no fill or kernel the driver recognizes. It writes depth-only clear words with ZMask 0 and either Min/Max Z 0 (0x00000000) or Min/Max Z 1.0 (0xfffffff0), mixed within each slice. Under main's rule:

  • slice 0 takes its clear;
  • slices 1 to 3 are refused and keep the previous frame's depth;
  • the volumetric light pass samples those cascades and finds them occluded.

The main menu loses the diagonal sun shaft and the sunlit foliage (side-by-side menu frames, left with later slices taking the clear, right with main's rule, will be added in a comment). The orphanage chapter is a night scene and looks the same either way.

Why the slice layout does not matter here

@AlxcBTW kept the equal-words requirement after my comment on #2053. That comment showed that slices 1 to 3 need X8 and Y8 to also take Z0 and Z1, which no GFX10_HTILE_SW_PATTERN entry has, so a per-tile decode of those slices would put tiles in the wrong place. That is still true, and per-tile decoding of slices 1 and up stays unmodelled. But the path this PR changes does no per-tile decoding:

  • The decision is "every word in [DB_HTILE_DATA_BASE + slice * HtileMetadataBytes, + HtileMetadataBytes) has ZMask 0". It is a check over the set of words, so it is the same under any permutation of tiles to words.
  • The store-back (expandedHtileWord) rewrites each word in place from that word's own bits. A depth-only word becomes 0xfffc000f; a depth+stencil word keeps its own SMem/SR bits. No word moves and no tile position is computed.
  • The value the surface takes is DB_DEPTH_CLEAR, one register for the whole surface. It does not come from the words.

The only slice-dependent input is the range's address. That comes from #5111's per-slice stride, which the equal-words case already trusts. So a slice whose words differ but are all cleared is in exactly the position of slice 0, which main already clears. The equal-words test would only matter if the clear value were taken per tile, and this path does not do that.

On hardware, the DB's depth test reads DB_DEPTH_CLEAR for a ZMask-0 tile whatever its Min/Max Z. Mesa src/amd/common/ac_descriptors.h (main @ 14a19dd6c167d0d5fd0126688318ed5461022588), HTILE_Z_CLEAR_REG: "This only clears HiZ and sets the Z/S state to "cleared". The DB register contain the full clear values." PAL gfx9MaskRam.cpp Gfx9Htile::GetClearValue and RADV (radv_cmd_buffer.c, which writes DB_DEPTH_CLEAR on every fast clear) encode the same way.

What this does not model, and the debt entry now says so: the HiZ test runs before the DB test and rejects against each tile's own Min/Max Z range (HTILE definitions in the same header). A LESS draw over a tile cleared with Min/Max Z 0 is therefore HiZ-rejected on the console, but here it is tested against DB_DEPTH_CLEAR. That gap already exists for slice 0 on main ("taken as a whole-surface clear"). This PR does not widen it beyond extending the slice-0 rule to later slices.

Changes

  • storeBackClearedHtile: the starting aspect mask no longer depends on firstSlice == 0 || uniform. A later slice takes the clear and the store-back when every word clears the aspect, like slice 0.
  • docs/dev/debt/libSceAgcDriver.md, "HTILE written by other kernels":
    • the slice sentence now says why the unmodelled layout is not needed;
    • the per-tile sentence cites the DB rule and records the HiZ difference.

Tested

Arch Linux, kernel 7.2.9-zen, NVIDIA RTX 4070 Ti SUPER (driver 615.78.08), GCC 16.2.1, Release, BUILD_TESTING=ON, on main 84b0d2f.

  • agc_driver_depth_fast_clear_submit:
    • New case MixedClearedSliceClears. Slice 1's HTILE is filled with words alternating 0x00000000 and 0xfffffff0 in runs of 7. The case checks that a LESS draw at 0.5 passes (the clear to 1.0 was taken), that the words are stored back as 0xfffc000f, that slice 0's HTILE is untouched, and that the depth drawn afterwards is kept. On main it fails with "slice 1 did not take the clear from HTILE words that all have ZMask 0 but different Min/Max Z"; with the change it passes.
    • NonUniformSliceIsRefused is renamed ExpandedTileKeepsSlice, unchanged otherwise. Its words (0xffffffff then zeros) have one tile left expanded, so they are still refused and stored back untouched.
    • UniformSliceDecodes is unchanged.
  • All 654 agc_ tests pass (ctest -R ^agc_ -j4). agc_driver_stencil_ref_export skips on this device, as on main.
  • Uncharted: Legacy of Thieves Collection (PPSA05684): the menu frame comparison compares the local integration branch (main + open PRs + local patches) with a local HTILE reset that took these slices' clears, against the same branch with that reset removed, i.e. main's rule. The light shafts come back with the clear taken. This branch replaces that local reset with the change above; it has not yet been run in the title on its own.

Overlap with open PRs

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: PPSA05684 Uncharted: Legacy of Thieves Collection (PPSA05684) on Linux / NVIDIA #4001
  • GPUs tested: NVIDIA RTX 4070 Ti SUPER, driver 615.78.08, Linux
  • AI-assisted: yes

A bind of slice 1 or later of a depth array read the slice's HTILE but took
its clear only when all its words were equal, because the HTILE word layout
of those slices is not modelled. Neither step needs the layout: the clear
is taken when every word of the range has ZMask 0 (a check over the set of
words), and the store-back rewrites each word in place from its own bits.
The value the surface is cleared to is DB_DEPTH_CLEAR, which the DB's depth
test reads for every cleared tile whatever its Min/Max Z (HTILE_Z_CLEAR_REG
in Mesa's src/amd/common/ac_descriptors.h), the same rule slice 0 already
follows.

PPSA05684 resets the four slices of its 2048x2048 D16 sun cascade array
every frame by writing ZMask 0 words with Min/Max Z 0 or 1 into their HTILE.
Slices 1 to 3 kept the previous frame's depth, and the main menu lost its
volumetric sun shafts and sunlit foliage.

agc_driver_depth_fast_clear_submit: slice 1 with words alternating 0x0 and
0xfffffff0 takes the clear, stores them back expanded and leaves slice 0's
HTILE alone. The refusal case keeps a word left expanded and is renamed.

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 5372 docs/dev/debt/libSceAgcDriver.md L123-124
PR 6457 docs/dev/debt/libSceAgcDriver.md L123-124
PR 6479 docs/dev/debt/libSceAgcDriver.md L123-124
PR 6505 core/libs/prx/libSceAgcDriver/tests/execution/DepthFastClearSubmit.cpp L230, L257-258
PR 6543 docs/dev/debt/libSceAgcDriver.md L122-123
PR 6730 docs/dev/debt/libSceAgcDriver.md L123-124

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
PR 1395 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 2434 docs/dev/debt/libSceAgcDriver.md
PR 4448 docs/dev/debt/libSceAgcDriver.md
PR 5058 docs/dev/debt/libSceAgcDriver.md
PR 5499 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
docs/dev/debt/libSceAgcDriver.md
PR 5925 docs/dev/debt/libSceAgcDriver.md
PR 6194 docs/dev/debt/libSceAgcDriver.md
PR 6218 docs/dev/debt/libSceAgcDriver.md
PR 6223 docs/dev/debt/libSceAgcDriver.md
PR 6273 docs/dev/debt/libSceAgcDriver.md
PR 6280 docs/dev/debt/libSceAgcDriver.md
PR 6300 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6423 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6465 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6472 docs/dev/debt/libSceAgcDriver.md
PR 6481 docs/dev/debt/libSceAgcDriver.md
PR 6498 docs/dev/debt/libSceAgcDriver.md
PR 6529 docs/dev/debt/libSceAgcDriver.md
PR 6548 docs/dev/debt/libSceAgcDriver.md
PR 6549 docs/dev/debt/libSceAgcDriver.md
PR 6563 docs/dev/debt/libSceAgcDriver.md
PR 6571 docs/dev/debt/libSceAgcDriver.md
PR 6612 docs/dev/debt/libSceAgcDriver.md
PR 6615 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 6636 docs/dev/debt/libSceAgcDriver.md
PR 6683 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6786 docs/dev/debt/libSceAgcDriver.md
PR 6808 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6867 docs/dev/debt/libSceAgcDriver.md
PR 6868 docs/dev/debt/libSceAgcDriver.md
PR 6926 docs/dev/debt/libSceAgcDriver.md
PR 6941 docs/dev/debt/libSceAgcDriver.md
PR 6946 docs/dev/debt/libSceAgcDriver.md
PR 6947 docs/dev/debt/libSceAgcDriver.md
PR 6977 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
docs/dev/debt/libSceAgcDriver.md
PR 6995 docs/dev/debt/libSceAgcDriver.md
PR 7002 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7008 docs/dev/debt/libSceAgcDriver.md
PR 7085 docs/dev/debt/libSceAgcDriver.md
PR 7201 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7233 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7250 docs/dev/debt/libSceAgcDriver.md
PR 7254 docs/dev/debt/libSceAgcDriver.md
PR 7256 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7258 docs/dev/debt/libSceAgcDriver.md
PR 7332 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
docs/dev/debt/libSceAgcDriver.md
PR 7371 core/libs/prx/libSceAgcDriver/Graphics/src/DepthSurface.cpp
PR 7390 docs/dev/debt/libSceAgcDriver.md
PR 7432 docs/dev/debt/libSceAgcDriver.md
PR 7505 docs/dev/debt/libSceAgcDriver.md
PR 7604 docs/dev/debt/libSceAgcDriver.md
PR 7642 docs/dev/debt/libSceAgcDriver.md

@akkadbakkad89

Copy link
Copy Markdown

Second NVIDIA datapoint plus first Intel, head ac840ec7d, Windows 11, MinGW (WinLibs GCC 15.2.0), Release, serial fresh processes:

  • NVIDIA RTX 3070 (driver 617.42): agc_driver_depth_fast_clear_submit (incl. MixedClearedSliceClears) and agc_driver_depth_fast_clear pass.
  • Intel UHD 630 (ANYPS5_GPU=UHD, igvk64): both pass.

@oneandonlydean

Copy link
Copy Markdown
Collaborator

@AlxcBTW, could you take a look at this one? It drops the equal-words condition you kept on #2053/#5627 (your 2026-10-13 comment: keep the per-tile decode to slice 0 and refuse slices >= 1). The argument is that storeBackClearedHtile does no per-tile decode: it ANDs over all words and rewrites each word from its own bits, so a slice >= 1 whose words are all ZMask 0 can take the fast clear the way slice 0 does. Reviewing the code, that holds for this path. The only slice-dependent input is the per-slice HTILE stride from #5111, which main's equal-words case already trusts. The new agc_driver_depth_fast_clear_submit case passes, and it fails with main's DepthSurface.cpp. An ack from you, or a reason to keep the rule, would settle it.

@TheSovietWaffle

Copy link
Copy Markdown
Contributor

AMD data point, adding to the NVIDIA and Intel runs: head ac840ec7d as-is, Release, Windows 10 Pro 22H2 (19045), WinLibs GCC 15.2.0 r7, AMD Radeon RDNA 2 iGPU (1002:164e, driver 32.0.21045.5002) chosen with ANYPS5_GPU.

  • agc_driver_depth_fast_clear_submit: 5 of 5 pass.
  • Negative control: only the runtime sources reverted to the merge-base (test kept) fails with slice 1 did not take the clear from HTILE words that all have ZMask 0 but different Min/Max Z, matching the Linux result.

Not checked: other tests, titles.

AI-assisted: yes (Claude Code).

@github-actions github-actions Bot added docs agc libSceAgc* labels Oct 17, 2026
@XairRrR

XairRrR commented Oct 17, 2026

Copy link
Copy Markdown
Contributor Author

Checked this branch in the title. On the local integration build, I replaced the old local HTILE reset with this change: reverted the three local commits and applied ac840ec, which applied cleanly. The main menu's sun shaft and sunlit foliage are back and match the earlier reference frames. With main's rule they are missing. No crashes or device losses in the run. The side-by-side frames (with this change / earlier reference / main's rule) will follow as an image.

AI-assisted: yes

@XairRrR

XairRrR commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Menu frames from PPSA05684, as promised in the description. Left: this change (ac840ec) on the local integration build. Middle: the earlier local HTILE reset (the reference). Right: main's rule. With main's rule the diagonal sun shaft and the sunlit foliage are missing.

Main menu: this change, earlier local reset, main's rule

AI-assisted: yes

@oneandonlydean
oneandonlydean merged commit a373e85 into boykopovar:main Oct 19, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs unblocks title Names a title ID on the Unblocks line

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants