Skip to content

The game's compute dispatches with no game barrier between them run side by side (profile key dispatch_overlap, on for Ocarina of Time): Kokiri Forest GPU p50 15.63 → 14.76 ms - #1901

Closed
FifteenPen wants to merge 1 commit into
seedds:mainfrom
FifteenPen:pr/dispatch-overlap

Conversation

@FifteenPen

Copy link
Copy Markdown
Contributor

What and why

The Legend of Zelda: Ocarina of Time (0400486024DEC000, v131072) runs most of its compute at the start of a frame: many dispatches back to back, with no nvnCommandBufferBarrier between them. dispatch_enc puts a barrier inside the shared copy encoder before every dispatch (Compute dispatches share the copy encoder), so each one waits for the one before it to finish.

That is more ordering than NVN gives. NVN orders a dispatch after earlier work only through a barrier. The draw passes of a game with draw_barriers game already follow the same rule (dm/games/README.md, draw_barriers).

The change is a profile key, dispatch_overlap (DM_NVN_DISPATCH_OVERLAP overrides it), in dispatch_enc:

  • Two of the game's dispatches with nothing else between them in the copy encoder, and no game barrier between them, run side by side.
  • The barrier stays when a copy, fill or inline update sits between them, when either is a dispatch of the host's own, or when the game issued a barrier between them.
  • Whatever follows a dispatch, other than another dispatch, still waits for it.

The key is off by default, so every other profile keeps exactly the barriers it has now. Ocarina's profile sets it on.

Docs: a new reference entry, Compute dispatches ordered by the game's own barriers, the key's row in dm/games/README.md, the switch's row in docs/env_switches.tsv, and a line under the game note's "Found on this game".

Related: #1076 and #1887 skip the same barriers by proving the dispatches' buffer ranges disjoint (opt-in). This one follows the game's own barriers instead, as draw_barriers game does for draw passes. The two can coexist: range tracking for a game whose barriers cannot be trusted, this key for one whose can.

Checked

  • Tests:
    • make -C dm/gfx/tests GAME=0400486024DEC000 check_t_dispatch_overlap (Metal debug layer, on this branch's base 7479adf): 7 checks, 0 failed. It counts the copy encoder's barriers:

      • none before the first dispatch, or between two game dispatches with nothing between them;
      • one after a game barrier;
      • one on each side of a copy;
      • one before a host dispatch, and one before the game dispatch after it.

      It also checks that the submission completes.

    • make -C dm/gfx/tests GAME=0400486024DEC000 run (Apple silicon, Metal; on base d8b2b64): every test passes except t_feedback's timestamp check "two reports in one command buffer read two times". That check is flaky on unchanged main too: 1 failure in 4 runs of d8b2b64 here, 2 in 4 on this change, and A draw that samples the depth attachment its render pass already wrote ends the pass first (Red Dead Redemption's sky blocks) #1866 reports 2 in 6.

    • python3 -m unittest dm/tools/tests/test_docs.py dm/tools/tests/test_game_profile.py, dm/tools/tests/test_env_switches.py, python3 dm/tools/docs_symptoms.py.

    • dm/tools/lint_c.py tidy (LLVM 23.1.4) on the changed files: 0 file/check pairs above the baseline.

  • Games run: The Legend of Zelda: Ocarina of Time (0400486024DEC000, v131072).
    • Setup: this change's host on a --platform mac build of main d8b2b64 (the change was rebased onto 7479adf since; host only, no chain change). scripts/bench.py, route kokiri-deck, docked, from a Kokiri Forest save. The pipeline archive was warmed first, so every run built 0 bases. Three runs a side, DM_NVN_DISPATCH_OVERLAP=0 against the profile's on, same binary.

      GPU p50 GPU p95 fps frames over budget
      on 14.76 ms (14.61–14.89) 15.66 ms 58.5 36 (5–124)
      off 15.63 ms (15.57–15.70) 16.43 ms 57.7 63 (31–93)
    • The screenshots after the window are alike. Both sides have the same single red line (c1_full.tsv beside the bench's profile copy).

  • Devices and OS: Apple M4 Pro (20-core GPU, 24 GB), macOS 27.2.
  • regress.py impact: Breath of the Wild, Pokémon Scarlet and Tears of the Kingdom, through nvn_mtl.m. Their profiles leave dispatch_overlap off, and with the key off dispatch_enc places the same barriers as before. So Ocarina is the only game whose behaviour changes, and regress.py check was not run on others. No other NVN game on this Mac has a smoke route except Mario Kart World, which the key does not reach.
  • Not checked: iOS, and Ocarina areas beyond the Kokiri route.

Docs

  • The finding is in its game note or its docs/reference/<subsystem>.md entry, and python3 dm/tools/docs_symptoms.py was run (the symptom index)
  • A chain tool changed: not applicable (host only)
  • No game files, keys or data extracted from a game in the change or the attachments

…ide by side (profile key dispatch_overlap, on for Ocarina of Time): Kokiri Forest GPU p50 15.63 -> 14.76 ms

Ocarina of Time runs most of its compute at the start of a frame, many dispatches back to back with no nvnCommandBufferBarrier between them. dispatch_enc put a barrier inside the shared copy encoder before every dispatch, so each one waited for the one before it. NVN orders a dispatch after earlier work only through a barrier, as the draw passes of a game with draw_barriers game are ordered.

With dispatch_overlap on (DM_NVN_DISPATCH_OVERLAP overrides it), two of the game's dispatches with nothing else between them in the copy encoder and no game barrier between them run side by side. A copy, fill or inline update between them, a dispatch of the host's own, or a game barrier keeps the barrier, and whatever follows a dispatch other than another dispatch still waits for it. The key is off by default, so every other profile keeps the barriers it has.

Measured with scripts/bench.py on a --platform mac build of main d8b2b64, the kokiri-deck route, docked, Apple M4 Pro, three runs a side with a warm pipeline archive: GPU p50 15.63 -> 14.76 ms, p95 16.43 -> 15.66 ms, frames over budget 63 -> 36, the pictures alike. t_dispatch_overlap checks where the barriers go.
@github-actions github-actions Bot added nvn NVN on Metal and its tests (dm/gfx/nvn, dm/gfx/tests) docs Documentation game: 0400486024DEC000 A game labels Nov 9, 2026
@FifteenPen
FifteenPen marked this pull request as ready for review November 9, 2026 03:27
@JDSavvy

JDSavvy commented Nov 10, 2026

Copy link
Copy Markdown

Confirmation on a second M4 Pro (20-core GPU, 24 GB, macOS 27.0.1), main 04811a6, chain 39eb2f781d07, docked, Kokiri Forest route with scripted inputs, DM_FPS_HOLD=off for every run, 10 runs in palindrome order (base, on, three PR hosts mirrored), stats from 60–120 s.

GPU p50 with this PR: 14.41 and 14.63 ms. Without it (base and two other PR hosts, 6 runs): 15.20–15.49 ms. So about -0.7 to -0.9 ms, in line with your 15.63 -> 14.76.

Frame time did not drop with it on this route: frame p50 18.38 / 18.50 ms against 17.07 / 17.68 for the base, but the PR's runs sat in the middle of the series where every variant was slower (~18.5 ms), so I would not read a regression into that. The frames here are CPU-bound.

Note: on top of 04811a6 the nvn_mtl.m hunk needs a 3-way apply (context moved); it applies cleanly that way and builds. No crashes, no [RED] lines in the runs.

FeReuss pushed a commit to FeReuss/nxnative-oot that referenced this pull request Nov 10, 2026
…tches with no game barrier between them run side by side (dispatch_overlap, on for Ocarina of Time)

Conflict with our pass-overlap branch resolved by hand: the barrier handler keeps our fragment-aware logic (g_game_barrier_full) and adds the PR's g_cs_gbar = 1 for any barrier bits; game.tsv keeps both sides.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
AmateurWizrd pushed a commit to AmateurWizrd/nxnative that referenced this pull request Nov 10, 2026
…f its between them run side by side (on for Ocarina of Time)

Net change of PR seedds#1901 at 5b7b307 (seedds/nxnative), merged by hand with this tree's g_blit_nops and g_game_bar_bits. DM_NVN_DISPATCH_OVERLAP=0 turns it off.
Fasen24-AI added a commit to Fasen24-AI/swordboiMacNative that referenced this pull request Nov 10, 2026
…nds once to half), seedds#1909 (fragment uniform reads bound to the binding length), seedds#1901 (dispatch_overlap on for Ocarina), seedds#1459 (MetalFX takes agl_ndsr_vr, no-curve variants linear), seedds#1903 (render scale stubs for Ocarina; replaces the seedds#1497 ceiling we carried)

Hand-resolved: the mesh draw keeps the task isolation of seedds#1842 and the copy paths our nvn_tex_level_range / DM_SHARPEN; seedds#1497's stub, test and BINDING entry go; the build-keyed rule names the render scale function (main+0x31d3200). Chain e906c7467690.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014eD437mxrMnW2UwLZhpwc7

Upstream pull requests applied in this commit (their authors' work, brought onto this tree with a three-way merge; the description is in each PR):
- seedds#1459 by PromethOps (open): DM_UPSCALER=metalfx takes agl's agl_ndsr_vr.pck networks too, and a variant with no curve and no power is drawn as one colour space: Ocarina of Time's upscaler is MetalFX's
- seedds#1497 by givethesourceplox (open): Add a render-scale ceiling for Ocarina v131072
- seedds#1842 by ycarogabriel-lgtm (open): Ocarina of Time: opt-in task isolation (DM_NVN_ISOLATE_TASK), the Hi-Z task a5288 in command buffers of its own, a workaround for its GPU hangs on an M1
- seedds#1897 by Know-It-All-Brother (closed): HMMA with an f16 accumulator rounds once per 8 products, as Ampere does (chain change)
- seedds#1901 by FifteenPen (open): The game's compute dispatches with no game barrier between them run side by side (profile key dispatch_overlap, on for Ocarina of Time): Kokiri Forest GPU p50 15.63 → 14.76 ms
- seedds#1902 by fxd0h (open): HFMA2 rounds once to half, as Ampere does (chain change)
- seedds#1903 by FifteenPen (open): Ocarina of Time takes the launcher's Render scale: the 3D views at that fraction of the game's own size, the dynamic resolution held at its top level (Kokiri Forest docked at 0.75: GPU p50 15.77 → 14.31 ms, 60 fps held)
- seedds#1909 by oz-rinkliff (open): Bound translated fragment uniform reads to the NVN binding length
Fasen24-AI added a commit to Fasen24-AI/swordboiMacNative that referenced this pull request Nov 10, 2026
seedds#1901 seedds#1459 seedds#1928 seedds#1942 seedds#1940 seedds#1952 seedds#1611 seedds#2065 seedds#2083 seedds#2008 seedds#2079 seedds#2022 seedds#2075 seedds#2077 seedds#2020 seedds#2021 onto main 04811a6

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014eD437mxrMnW2UwLZhpwc7

Upstream pull requests applied in this commit (their authors' work, brought onto this tree with a three-way merge; the description is in each PR):
- seedds#1459 by PromethOps (open): DM_UPSCALER=metalfx takes agl's agl_ndsr_vr.pck networks too, and a variant with no curve and no power is drawn as one colour space: Ocarina of Time's upscaler is MetalFX's
- seedds#1611 by Esteban-V (open): An argument block whose only change is a uniform buffer is patched instead of encoded again
- seedds#1774 by uncraftablexd (closed): Compute threads that exit before a barrier keep arriving at it, and every shader loop has a cap (GPU hang defences), chain change
- seedds#1824 by cocoman93 (open): Clamp texel fetches past a sampled 2D texture's edge: no hard edge in Ocarina of Time's mist
- seedds#1901 by FifteenPen (open): The game's compute dispatches with no game barrier between them run side by side (profile key dispatch_overlap, on for Ocarina of Time): Kokiri Forest GPU p50 15.63 → 14.76 ms
- seedds#1928 by FeReuss (open): Ocarina of Time: enc_thread on, the replay encodes on a thread of its own
- seedds#1929 by FeReuss (open): Unused border corrections are compiled out of pipelines that bind no sampler with a twin (function constant DMSH_BORDER_FC)
- seedds#1930 by FeReuss (open): A float add to storage memory with no result read is one native atomic add instead of a compare-and-swap loop
- seedds#1940 by uncraftablexd (open): A prewarm list's lines after its #@after_first_frame line are built after the first frame, in the background, instead of in the prepare step (issue seedds#1747)
- seedds#1942 by uncraftablexd (open): Frame step: late frames with the GPU idle do not step up, and a longer hold the GPU does not explain is probed
- seedds#1952 by AdyaGMD (open): Compute moves ahead of scene passes past occlusion queries and conditional rendering; clears wait across dispatches
- seedds#2008 by bquilty25 (open): nvn: reactive memory watchdog, 16-way view cache, and texture upload batching
- seedds#2020 by padsiwono (open): Ocarina of Time v131072: Output Native draws the full output size instead of the game's 80 % frame (`DM_OUTPUT`)
- seedds#2021 by padsiwono (open): Ocarina of Time v131072 renders at the launcher's Resolution (`DM_RENDER_SIZE`), docked
- seedds#2022 by sleepyeyedjoe (closed): Ocarina of Time: large models no longer go black for one frame twice, 4–5 frames apart, when the game streams new textures into them (a texture ID a shader has read keeps its read mark when it gets another texture)
- seedds#2030 by humanperzeus (open): A NaN colour at alpha 0 is returned as 0, as NVIDIA's blender takes a zero factor: Ocarina of Time's white clouds over the ferns are gone
- seedds#2035 by Know-It-All-Brother (open): The shader build runs as worker processes, and no shader waits for a batch (Ocarina of Time's cold Mac map: 3,002 s to 1,298 s)
- seedds#2065 by Raymon82777 (closed): A vertex library with layer entry points of its own gets bases for its main0 pairs: Ocarina of Time's 230 ms camera-turn hitch in Hyrule Field goes to 39 ms
- seedds#2075 by sleepyeyedjoe (closed): Ocarina of Time: a patch of scenery no longer draws dark for one frame when the game gives it a sharper texture (a pool texture the walk does not make holds a stand-in of its own colours until its first read)
- seedds#2077 by roandegraaf (closed): Ocarina of Time: fix the "plaid" terrain / missing texture tiles (GPU-drawn textures now reach the storage the game copies)
- seedds#2079 by uncraftablexd (open): Render pipelines that share a vertex function are taken from the archive or compiled one at a time: the AGX driver crashes in createVertexProgramVariant's completion block
- seedds#2083 by FifteenPen (closed): Ocarina of Time's prewarm list gets 41 more task / mesh pipelines from later play
@fheitzer

Copy link
Copy Markdown

Measured on an M1 Max (32-core GPU, applegpu_g13s, 32 GB, macOS 27.0.1), Ocarina of Time v131072 on main f02d047, handheld, 60 fps target, scripts/bench.py kokiri-deck, 2 runs per variant alternating in one invocation:

fps frame p50 GPU p50 (summed) GPU span p50
main 46.3 / 46.4 21.23 ms 19.31 ms 25.8 ms
main + #1901 (DM_NVN_DISPATCH_OVERLAP=1) 47.4 / 46.7 20.97 ms 18.51 ms 24.6 ms

So about −0.8 ms of GPU here; screenshots match. Note: it no longer builds on current main. Main added a forward declaration static id<MTL4ComputeCommandEncoder> dispatch_enc(void); and a host dispatch e = dispatch_enc(); (around nvn_mtl.m:5051 and :5091), which need dispatch_enc(int game) and dispatch_enc(0) (the host's own dispatch). With #1970 there is also a one-line conflict in OP_BARRIER: if (bits) { g_game_barrier |= bits; g_fb_bar++; g_cs_gbar = 1; }.

seedds added a commit that referenced this pull request Nov 12, 2026
Compute dispatches are ordered by the game's own barriers, for every game (PR #2250): dispatch_enc put a barrier before every dispatch in the shared copy encoder, so the GPU ran them one at a time, while NVN makes a dispatch's writes visible to later work only when the game issues nvnCommandBufferBarrier. Now a dispatch (dispatch_enc_cs) gets a barrier when the game issued one since the dispatch before it, or when one of the host's copies meets its ranges in either direction (uniform updates and uploads keep their order; copies are checked against what dispatches wrote and read, g_dw / g_dr); a dispatch with a binding whose reach is no memory range (an image, a buffer texture, a handle in a uniform slot, untraced global memory) and the host's own dispatches keep the full order on both sides. Kirby Air Riders' particle systems, about 230 dispatches a frame that share the buffers they write (91 % meet an earlier one's range, so ranges alone gain nothing): the encoder 6.6-9.8 → 0.4-1.2 ms, GPU p50 over alternating race phases 9.0 → 5.3 ms, the heaviest phase's frame p90 19.36 → 16.75 ms (PR author, M4 Pro, with PR #2254's loop guard). The PR made it a profile key for Kirby; it is the host default here, as the rule is NVN's: DM_NVN_DISPATCH_BARRIER=full brings back the barrier before every dispatch (diagnostic), ab alternates with the DM_AB phases. It replaces PR #1901's dispatch_overlap for Ocarina of Time. Also fixed on top of the PR: a new copy encoder empties the dispatch range sets (ranges from a closed encoder put needless barriers before its copies; t_dispatch_barrier's new case 6 fails without it), and bufsnap's copy is recorded as writing everything, so a later dispatch cannot overwrite what it still reads. The periodic [nvn] line counts dispatches after a game barrier, after a copy's range, in the full order and with none. Reference entry Compute dispatches that wait for each other (frame-rate.md), rendering.md's two dispatch entries point to it, Kirby's note lists it, env switch row, symptom index regenerated. Checked: t_dispatch_barrier in both orders with the encode thread on and off (27 / 31 checks), t_dispatch_own, t_copy_alias, t_tg_spill, t_cpu_tex, test_docs, test_env_switches, test_game_profile, lint_c.py tidy (at the baseline); regress.py check against 8a5c5e7 (M4 Pro): Mario Kart World PASS, 69,367 of about 1.02 M dispatches with no barrier, 59.8 / 60.0 fps, no new red line; OCTOPATH TRAVELER II PASS, but all its dispatches have a rangeless binding and keep the full order. Not run: Kirby Air Riders and Ocarina of Time in this form (no files / no route here), the iPhone

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

seedds commented Nov 12, 2026

Copy link
Copy Markdown
Owner

Superseded by 547c5db, from #2250: compute dispatches are now ordered by the game's own barriers for every game, Ocarina of Time included, so dispatch_overlap is no longer needed. #2250's rule also lets dispatches overlap when a copy sits between them but does not touch their ranges, which this PR kept ordered. Thank you for the Kokiri measurements (−0.8 ms GPU p50, confirmed on a second M4 Pro and an M1 Max): they are cited in the reference entry. Ocarina has not been run with the landed form yet.

@seedds seedds closed this Nov 12, 2026
Kayyluhh added a commit to Kayyluhh/nxnative that referenced this pull request Nov 14, 2026
…ier between them run side by side (profile key dispatch_overlap, on for Ocarina of Time): Kokiri Forest GPU p50 15.63 -> 14.76 ms
Kayyluhh added a commit to Kayyluhh/nxnative that referenced this pull request Nov 14, 2026
…e forward declaration and in gsb_swz (the host's own dispatch, 0), which main added after seedds#1901's base
Kayyluhh added a commit to Kayyluhh/nxnative that referenced this pull request Nov 14, 2026
…2514d7 evaluates conditional rendering for every game)

Ocarina of Time's profile drops the key and keeps seedds#1714's gpu_ts_hz; game.py keeps seedds#1901's dispatch_overlap and seedds#2341's replay_ahead beside main's warning for an old render_cond; t_nvn takes main's counter field (0) and seedds#1714's report rate. Checked: t_nvn 311, t_serial_owner, t_cpu_tex, t_view_cache, t_replay_ahead, all dm/gfx/tests programs build, test_docs, test_env_switches, test_game_profile, test_nsp_chain, the Mac host for 0400486024DEC000.
Kayyluhh added a commit to Kayyluhh/nxnative that referenced this pull request Nov 14, 2026
…losed, superseded by seedds#2250, on main as 547c5db (compute dispatches ordered by the game's barriers for every game)

Ocarina of Time's profile drops dispatch_overlap; the barrier op records the game's bits without g_cs_gbar; dispatch_enc is main's as it was before seedds#2250. seedds#2226's ind_split and seedds#2341's replay_ahead stay.
Kayyluhh added a commit to Kayyluhh/nxnative that referenced this pull request Nov 14, 2026
…ered by the game's barriers (547c5db, seedds#2250) in place of seedds#1901

Kirby's note, hle_nvn.c's periodic lines (seedds#2261's GPU share and main's dispatch counts) and the tests Makefile keep both sides. Checked: t_dispatch_barrier (full and game, encode thread off and on), t_dispatch_own, t_copy_alias, t_tg_spill, t_replay_ahead, t_cpu_tex 107, t_view_cache 44, t_serial_owner 167, all dm/gfx/tests programs build, test_docs, test_env_switches, test_game_profile, test_nsp_chain, the Mac host for 0400486024DEC000.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation game: 0400486024DEC000 A game 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.

4 participants