Repository navigation
An indirect dispatch no longer joins a copy encoder that holds work: Ocarina of Time's flower bushes no longer show holes in Hyrule Castle's courtyard - #2226
Open
humanperzeus wants to merge 1 commit into
Conversation
…Ocarina of Time's flower bushes no longer show holes in Hyrule Castle's courtyard The foliage cull is a chain of compute dispatches in which a later one is indirect, its group counts written by an earlier one. Dispatches share the copy encoder, each after a barrier inside it, and that barrier did not order the indirect dispatch's counts after the earlier dispatch: in about 40 % of frames (M1 Max) the cull left 16 of its 276 leaf clusters out, and the leaf and flower meshes drew a short list, the bush's dark inner body showing through. - dispatch (ind_split): an indirect dispatch does not join a copy encoder that already holds work; the encoder ends, the next begins with a queue barrier. Direct dispatches keep sharing it. DM_NVN_IND_SPLIT=0 brings the old order back (to measure). - docs/reference/rendering.md: the entry, with the measurements and what is not known; the game note; the symptom index regenerated. Measured on Ocarina of Time standing still in the courtyard, the leaf list's count read when each submission completed, 240-300 frames a setting, flipped live in one session by lab switches not in this change: old order 39 %, 41 %, 47 % of frames short; this change 0 of 600; an encoder for every dispatch 0; for the direct ones only 41 %. Every uniform and small storage input of the five cull shaders, dumped for 662 frames, followed none of the drops. 24 shots: 0 with holes, 9 with DM_NVN_IND_SPLIT=0. Which read misses the encoder barrier (the indirect-argument fetch or the copy of the counts into the driver segment) is not known; a synthetic writer / indirect reader pair did not fail with the old order (0 of 100, also through two chunk buffers of a large pool). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fasen24-AI
pushed a commit
to Fasen24-AI/swordboiMacNative
that referenced
this pull request
Nov 10, 2026
…Ocarina of Time's flower bushes no longer show holes in Hyrule Castle's court (PR seedds#2226) The code of pull request seedds#2226 by humanperzeus, placed by hand: its notes (the game's note, the symptom index, the rendering reference) conflict with this branch's and are left for the next update on main. From the pull request: the game's foliage is GPU-driven, a chain of compute dispatches culls the clusters and builds the list the leaf and flower mesh shaders draw, and a later dispatch of the chain is indirect, its group counts written by an earlier one. Inside one Metal 4 compute encoder the barrier between two dispatches did not order the indirect dispatch's counts after the dispatch that wrote them: on an M1 Max the cull came out short in 39 % of frames, 16 of its 276 clusters missing at random; with the encoder ended before an indirect dispatch, 0 of 600 frames. DM_NVN_IND_SPLIT=0 is the old behaviour. In this branch: a row for the switch in docs/env_switches.tsv. On our M5 the holes were not looked for before; the walk through Castle Town with this commit is in the notes of the commit that follows it, if one does. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014eD437mxrMnW2UwLZhpwc7
Fasen24-AI
added a commit
to Fasen24-AI/swordboiMacNative
that referenced
this pull request
Nov 10, 2026
…t is measured and what is not Five changes since the last update of the page: three fixes of pictures (PR seedds#2219's change for Switch 2 uniform reads, PR seedds#2226's indirect dispatches, main's 6d9edb0 read mark), the hold over its bound that a long session found, and the Memory setting with its review. The first three ran in a short walk; the memory changes build and have not run in the game. The page says so, and gives the switch that avoids the long-session storm on the commit before this push. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014eD437mxrMnW2UwLZhpwc7
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 and why
Ocarina of Time (
0400486024DEC000, v131072) on an M1 Max: the flower bushes in Hyrule Castle's courtyard showed their dark inner body through holes in about 40 % of frames, standing still, at random.The foliage is GPU-driven: a chain of compute dispatches culls the clusters (
83df46a0b1b379b4,60dd0e6eac91f712,cee08c634ef1f343,58f47abfa0a84773,e855420a39e60874) and builds the list the leaf and flower mesh shaders draw (d98076e4148db34f,eebdb9347d09b344). A later dispatch of that chain is indirect, its group counts written by an earlier one. Dispatches share the copy encoder, each after a barrier inside it (dispatch_enc), and that barrier did not order the indirect dispatch's counts after the dispatch that wrote them: in the frames that went wrong the cull kept 260, 268 or 250 of its 276 clusters, and the leaf list held 1,584 / 1,680 / 1,488 leaves instead of 1,749.dispatch(ind_split): an indirect dispatch does not join a copy encoder that already holds work. The encoder ends, and the dispatch goes into the next one, which begins with a queue barrier (blit_open) that waits for everything before it. Direct dispatches keep sharing the encoder.DM_NVN_IND_SPLIT=0brings the old order back, to measure.How it was found, in short: the leaf list's count read once each submission completed gave one number a frame; it dropped in 39 % of frames, the same share as the holes in screenshots, and each frame independently of the one before. The cull's own list dropped with it. Every uniform and small storage input of the five cull shaders, dumped each frame for 662 frames, followed none of the drops (|r| ≤ 0.12), so the inputs were the same and the result was not: an ordering problem, not the shaders. Lab switches flipped live in one session (not part of this change) then gave: an encoder of its own for every dispatch 0 %, for the indirect ones only 0 %, for the direct ones only 41 %.
Checked
0400486024DEC000v131072, standing still in Hyrule Castle's courtyard, on our build (base641636e2plus our changes, the samedispatchcode asmain), M1 Max, macOS 27.2:DM_NVN_IND_SPLIT=0.DM_NVN_GPUPROF=every:30, frame-rate target 60, M1 Max): 21.08 ms mean / 22.36 ms median over 43 frames with this change, 20.50 / 21.95 ms over 44 frames withDM_NVN_IND_SPLIT=0; 141 against 131 encoders a frame. One run each, so about +0.4 to +0.6 ms (2 to 3 %); how much of that is run-to-run spread is not measured.make -C dm/gfx/tests GAME=0400486024DEC000 runon this branch: exit 0, 75,079 checks in 158 result lines, 0 failed, the Python groups OK. Left out:t_gl_imports, which does not link onmainitself (f3ec8fc8:_g_key18, referenced fromhle.c, defined inloader.c, which the test does not link); built on a clean checkout ofmainit fails the same way. No test reproduces the race: a synthetic pair (a dispatch writes the counts, an indirect one reads them, in a small pool and through two chunk buffers of a large one) passed with the old order too (0 of 100 runs each), so it is not part of this PR; what in the game's chain lets the old order fail is not pinned down.python3 dm/tools/tests/test_docs.py: OK. Host builds (make -C dm/host GAME=0400486024DEC000 dmhost).scripts/regress.py:impactreaches every NVN game (with manifests: Breath of the Wild, Pokémon Scarlet, Tears of the Kingdom; Hades not affected).check --gameswas not run: only Ocarina of Time is set up here, and its library is of an older chain thanmain's.main's chain (the change is host-only; the run above is on our build with the samedispatchcode).Docs
docs/reference/rendering.md: "An indirect dispatch read its group counts before the dispatch that wrote them" (symptom, cause, fix, check, with the measurements and what is not known).docs/agents/symptoms.mdregenerated.🤖 Generated with Claude Code