Skip to content

A draw that samples the depth attachment its render pass already wrote ends the pass first (Red Dead Redemption's sky blocks) - #1866

Closed
robandstuff wants to merge 1 commit into
seedds:mainfrom
robandstuff:fix/depth-sampled-after-write
Closed

robandstuff wants to merge 1 commit into
seedds:mainfrom
robandstuff:fix/depth-sampled-after-write

Conversation

@robandstuff

Copy link
Copy Markdown
Contributor

What and why

Red Dead Redemption showed light 8×8 blocks in the sky over distant hills and trees, flickering from frame to frame. Its cloud fog (rdr2_clouds_fog, c0ab3c051753e36d) fades the clouds against the scene's depth. It samples the depth attachment of the pass it draws in, through a uniform-buffer handle, after the billboards, low-detail terrain and props of that pass have written depth. No game barrier separates the two. On Apple's GPUs a depth attachment sampled in the pass that writes it returns whole compressed blocks of what the tiles have stored so far, so the fog faded the clouds out over the trees and bare sky showed through.

fb_prepare already ends the pass for the reverse order (a depth write after a depth read) and snapshots colour attachments. This order wasn't covered. Now a draw that samples the depth attachment, bound or named by a uniform-buffer handle (fb_texu_reads), after a draw of the same Metal pass wrote depth or stencil, ends the pass first. A draw that itself writes depth is left as before. DM_NVN_FB_DEPTH=0 turns it off.

Reference entry: docs/reference/rendering.md, "Depth sampled after a draw of its pass wrote it". The game's profile isn't upstream yet. It is carried in LNPANDY/NX2Apple and will come in its own PR (tracked in LNPANDY/NX2Apple#999).

Checked

  • Tests: make -C dm/host/tests GAME=0100000000010000 run: 49 tests, 2,355 checks, none failed. make -C dm/gfx/tests GAME=0100000000010000 run on Apple silicon (Metal): 146 tests, 71,870 checks. The failures are t_render_counter_overlap (1) and t_render_enable_queue (9), which fail the same way on unchanged main (7f50222); t_feedback's check "two reports in one command buffer read two times" (timestamp reports) fails now and then on unchanged main too (2 of 6 runs). t_feedback, which covers fb_prepare, passed in this branch's run.
  • Games run, with title ID and version: Red Dead Redemption 01007820196A6000 1.0.4, on the Mac from a save in Armadillo:
    • Played by hand with the fix: the blocks are gone. Before the change they showed in every replay of this route.
    • A recorded input replay: about one extra pass end a frame; GPU p50 14.3 → 14.7 ms; frame time unchanged at the game's 30 fps; no [RED].
  • Devices and OS: Apple M4 (32 GB), macOS 27.0.1. Not tested on an iPhone or iPad.
  • scripts/regress.py check --games was not run: no other game's saves or recordings were at hand. The rule only applies when a draw samples a depth attachment written earlier in its pass, which before this change read undefined tile contents.

Docs

  • The finding is in its docs/reference/rendering.md entry, and python3 dm/tools/docs_symptoms.py was run (the symptom index)
  • No chain tool changed
  • No game files, keys or data extracted from a game in the change or the attachments

🤖 Generated with Claude Code

…e depth attachment its render pass already wrote ends the pass first, so it reads what was written

Red Dead Redemption's cloud fog (rdr2_clouds_fog) fades the clouds against the scene's depth, sampling the depth attachment of the colour pass it draws in, after the tree cards, low terrain and props of that pass wrote depth (no game barrier between them). On Apple's GPUs an attachment sampled within the pass that writes it reads whole compressed blocks of what the tiles stored so far: the fog took 8x8 blocks above the trees for geometry, faded the clouds out there and left bare sky, different blocks every frame (a flicker on distant hills and trees in cutscenes and play).

fb_prepare already ended the pass for the other order (a depth or stencil write after a draw that read the attachment) and snapshots colour attachments a pass wrote; this order was not covered. Now a draw that samples the depth attachment, bound or named in a uniform buffer, after a draw of its pass wrote depth or stencil ends the pass before it (not when the draw itself writes depth). On Red Dead Redemption's Armadillo route about one pass end a frame: GPU p50 14.3 -> 14.7 ms, frame time unchanged. DM_NVN_FB_DEPTH=0 turns it off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@robandstuff
robandstuff force-pushed the fix/depth-sampled-after-write branch from becaaab to 495dc78 Compare November 9, 2026 12:58
robandstuff added a commit to robandstuff/NX2Apple that referenced this pull request Nov 9, 2026
…ing fixes found on it, and a test for rpf6.py

Red Dead Redemption on the current pin (c78d737). The movies' second audio renderer plays through upstream's support for several renderers, so the fork carries no audio patch for it.

0102: the shader split reads Rockstar RPF6 archives, RAGE rgxd effects and Double Eleven .pkdswitch packs.
0103-0105: the profile and note, 1.0.4's archive key (read from its key setup), and the registration of 1.0.4.
0106: a draw that samples the depth attachment its render pass already wrote ends the pass first: the cloud fog read partly stored tiles and left flickering blocks in the sky over distant hills. DM_NVN_FB_DEPTH=0 turns it off. Forwarded as seedds/nxnative#1866.
0107: upstream's fix for the samples-passed counter (cfbdd93c, from seedds/nxnative#1867) as a backport: the game never resets the counter, and its distant characters were drawn as hats and props only.
0108: the game note after the two fixes.
0109: test_rpf6, synthetic bytes for the archive header, the entry table and key_from_setup.
0102-0105, 0108 and 0109 are forwarded as seedds/nxnative#2005.

Checked: python3 tools/engine.py check (75 patches apply to c78d737). The gfx suite on this pin with 0102-0108: 136 groups, 71,647 checks, 10 failed in t_present_crop, t_render_counter_overlap and t_render_enable_queue; the same 10 fail without 0107, which adds 6 passing checks. Red Dead Redemption 1.0.4 built from the dump and replayed from a save in Armadillo: no [RED], frame time p50 33.3 ms, the distant characters have bodies and the sky is clean.

Issue: LNPANDY#999
seedds added a commit that referenced this pull request Nov 9, 2026
… a draw that writes depth and samples it reads a copy, one after a depth write of its pass ends the pass first, in depth-only passes too. Ocarina of Time's light-blue dashes over the rock at Death Mountain Trail's cave entrance and Red Dead Redemption's 8x8 sky blocks over the trees are gone (PR #1917, PR #1866)

On Apple's GPUs a depth attachment sampled in the pass that writes it reads whole compressed blocks of undefined content, and Metal has no tile read for depth. fb_prepare only ended the pass before a depth write that followed a read, and only in passes with a colour attachment. fb_depth_rule, run before the colour snapshots and tile reads:

- A draw that writes depth or stencil and samples the attachment through a texture slot reads a copy (fb_depth_snap, the fb_snap cache), reused until the game's next barrier. Ocarina's full-screen 25a8adc977ff813d and its five decals sample the scene depth while writing it with LEQUAL.
- A draw that samples it after an earlier draw of its pass wrote depth or stencil (g_fb_dwrote), and writes none itself, ends the pass first, whether the depth is bound in a slot or named by a uniform-buffer handle. Red Dead Redemption's cloud fog c0ab3c051753e36d reads it through a handle after the billboards and terrain.
- It runs in depth-only passes too, counting only reads it can name: pool shaders, dynamic tables and handle tables in c2 (TEXA) stay possible readers in colour passes only, as before, so shadow passes are not cut at every draw.

DM_NVN_FB_DEPTH=0 reads the attachment in the pass, as before. A draw that writes depth and reads it through an image slot, a handle, a pool or a dynamic table still reads it in the pass (open, in the reference entry).

Unlike the PRs: one rule and one switch; the pass ends before the colour bindings are judged instead of in the middle of the binding scan; depth-only passes are covered; the RED key moved to 163.

Checked: check_t_feedback, 251 checks (the copy, its reuse and the barrier, the pass end after a write, and an actual Depth32Float and Depth32Float_Stencil8 attachment copied and read back in a colour and a depth-only pass under Metal validation); the graphics suite (t_nvn passes after the rebase onto 68869cc; t_ubp_running fails as it does on main); test_docs.py, test_env_switches.py; lint_c.py tidy at the baseline. regress.py check on Octopath Traveler II against 1d37720: PASS, both paths taken on its Depth32Float_Stencil8 depth (a copy reused by about 21 draws, about one pass end a frame), no new red line, fps 59.8, frame p99 18.19 -> 18.27 ms, the same screenshots. Fire Emblem Three Houses did not build (stale shader package). Ocarina and Red Dead Redemption were checked by the PR authors, by eye in play; not run on iOS.

Co-authored-by: FifteenPen <354289956+FifteenPen@users.noreply.github.com>
Co-authored-by: robandstuff <20628004+robandstuff@users.noreply.github.com>
Co-authored-by: akratch <70729875+akratch@users.noreply.github.com>
@seedds

seedds commented Nov 9, 2026

Copy link
Copy Markdown
Owner

Landed in 8190693, folded together with #1917 into one depth rule (fb_depth_rule). Your case is kept as it was: a draw that samples the depth attachment, bound or named by a uniform-buffer handle (fb_texu_reads), after a draw of its pass wrote depth or stencil (g_fb_dwrote) ends the pass first. #1917 adds the other half: a draw that writes depth and samples it reads a copy. Differences from your PR: the pass now ends before the colour bindings are judged instead of partway through the binding scan, the rule also runs in depth-only passes (handle tables in c2 count there only when read), and the switch is DM_NVN_FB_DEPTH as you named it, now covering both halves. Red Dead Redemption could not be run here (no profile upstream); a retest on your build would be welcome. You are credited as co-author.

@seedds seedds closed this Nov 9, 2026
@robandstuff

Copy link
Copy Markdown
Contributor Author

Thanks for landing it. Retested on Red Dead Redemption 1.0.4 (Apple M4, macOS 27.0.1): main at 8190693 plus the game's profile from #2005, built from the dump and replayed headless from a save in Armadillo to frame 6,600. No [RED], frame time p50 33.3 ms, the depth rule ends the cloud fog's pass as expected (same-pass depth sampling: pass end, shader 1ea90e4b67b1a788), and at the saloon (frame 2,310) the sky over the distant ridge is clean: no 8x8 blocks. The distant characters keep their bodies (cfbdd93).

The same result on NX2Apple's pin (c78d737) with 8190693 as a backport: no [RED] to frame 6,600, ridge clean, check_t_feedback 251 checks, 0 failed.

Know-It-All-Brother pushed a commit to Know-It-All-Brother/nxnative that referenced this pull request Nov 9, 2026
… a draw that writes depth and samples it reads a copy, one after a depth write of its pass ends the pass first, in depth-only passes too. Ocarina of Time's light-blue dashes over the rock at Death Mountain Trail's cave entrance and Red Dead Redemption's 8x8 sky blocks over the trees are gone (PR seedds#1917, PR seedds#1866)

On Apple's GPUs a depth attachment sampled in the pass that writes it reads whole compressed blocks of undefined content, and Metal has no tile read for depth. fb_prepare only ended the pass before a depth write that followed a read, and only in passes with a colour attachment. fb_depth_rule, run before the colour snapshots and tile reads:

- A draw that writes depth or stencil and samples the attachment through a texture slot reads a copy (fb_depth_snap, the fb_snap cache), reused until the game's next barrier. Ocarina's full-screen 25a8adc977ff813d and its five decals sample the scene depth while writing it with LEQUAL.
- A draw that samples it after an earlier draw of its pass wrote depth or stencil (g_fb_dwrote), and writes none itself, ends the pass first, whether the depth is bound in a slot or named by a uniform-buffer handle. Red Dead Redemption's cloud fog c0ab3c051753e36d reads it through a handle after the billboards and terrain.
- It runs in depth-only passes too, counting only reads it can name: pool shaders, dynamic tables and handle tables in c2 (TEXA) stay possible readers in colour passes only, as before, so shadow passes are not cut at every draw.

DM_NVN_FB_DEPTH=0 reads the attachment in the pass, as before. A draw that writes depth and reads it through an image slot, a handle, a pool or a dynamic table still reads it in the pass (open, in the reference entry).

Unlike the PRs: one rule and one switch; the pass ends before the colour bindings are judged instead of in the middle of the binding scan; depth-only passes are covered; the RED key moved to 163.

Checked: check_t_feedback, 251 checks (the copy, its reuse and the barrier, the pass end after a write, and an actual Depth32Float and Depth32Float_Stencil8 attachment copied and read back in a colour and a depth-only pass under Metal validation); the graphics suite (t_nvn passes after the rebase onto 29a921b; t_ubp_running fails as it does on main); test_docs.py, test_env_switches.py; lint_c.py tidy at the baseline. regress.py check on Octopath Traveler II against 518fc22: PASS, both paths taken on its Depth32Float_Stencil8 depth (a copy reused by about 21 draws, about one pass end a frame), no new red line, fps 59.8, frame p99 18.19 -> 18.27 ms, the same screenshots. Fire Emblem Three Houses did not build (stale shader package). Ocarina and Red Dead Redemption were checked by the PR authors, by eye in play; not run on iOS.

Co-authored-by: FifteenPen <354289956+FifteenPen@users.noreply.github.com>
Co-authored-by: robandstuff <20628004+robandstuff@users.noreply.github.com>
Co-authored-by: akratch <70729875+akratch@users.noreply.github.com>
@robandstuff
robandstuff deleted the fix/depth-sampled-after-write branch November 12, 2026 05:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants