Repository navigation
Conversation
…aps are alive and after game barriers that only order fragments: any live resolution family put every pass of every game behind a full barrier at both ends (Ocarina of Time keeps one from its first frame, and Donkey Kong Bananza's latest run saved none of about 85 a frame either), and every game barrier did the same for the next pass, although NVN's ORDER_FRAGMENTS lets later primitives start at once. The relaxed end barrier already orders the previous pass's vertex and fragment stages before the next one's fragment, dispatch and copy stages, with the alias flush while a heap is alive; only ORDER_PRIMITIVES and ORDER_INDIRECT_DATA keep the full one. A new stats line counts why passes still began behind a full barrier. On an M1 in Ocarina of Time's slowest area (a Metal System Trace showed vertex work under the previous pass's fragments 135 times in 10,000 pass changes): passes without the full barrier 0 → about 70%, GPU p50 about 39.5 → 37.2 ms, 20 → 24 fps, picture unchanged. DM_NVN_BARRIER_HEAP=0 and DM_NVN_BARRIER_GAME_BITS=0 restore each Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
iPhone test: it works, with one caveat on the end-barrier stages Setup: iPhone Duo (iPhone19,4, iOS 27.2), Ocarina of Time v131072, docked, 30 fps target, the game's own upscaler (agl_ndsr_vr, no MetalFX), main pass pinned at 2048x1152, encode thread off. Shell built from a fork of main 37f076a (chain
One run each and the starting temperature wasn't controlled ("fair" came at 142 s vs 71 s), so take the thermal line as a trend, not a measurement. Merge note: my base already had the end barrier with Picture: fine overall. One possible artifact: a translucent, blurry, flickering shadow-like patch on the wall facing the door in the Deku Tree basement torch room. No baseline from that room, so it may be unrelated. I didn't A/B it with |
…ges on both sides: a mesh pipeline's pass ends after its task and mesh work, and the next pass's task and mesh shaders wait for this pass's fragments. Without them a pass of mesh-shader work could start before the fragments it reads were done; with more passes now taking the relaxed end, an iPhone run of Ocarina of Time (iOS 27.2, docked, the game's own upscaler) was tested with these masks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the iPhone run, that's very useful: entry barriers 33,169 full / 0 saved → 4,441 / 23,170, and GPU span 29.7 → 27.2 ms. End-barrier stages: you're right. I merged current main and added The Deku Tree torch room patch: could you A/B it in the same room?
If the patch goes away with one of them, that tells me which rule it comes from. If it stays with both set to 0, it isn't this PR. 🤖 Generated with Claude Code |
|
Data point from a MacBook Air M2 (10-core GPU, 16 GB), macOS 27.0, Ocarina of Time v131072. Build: this PR as a host-only change on our OoT tree. The A side is the same tree without it: base 7f50222 plus #1833, #1902 and #1897, shaders unchanged. Handheld, 1440x810 main pass (dynamic resolution off), MetalFX, frame-rate hold off, target 60. The route walks Kokiri Forest. It is active. Draw-pass entry barriers saved per 300 frames went from 0 to about 26k; about 10k stay full. Per frame, the ones that stay full are about 28 after a non-draw encoder (compute or copy) and about 5 after a game barrier. But on the M2 it doesn't change the frame. Paired A/B, 5 runs a side plus a Metal System Trace of each, windows with the CPU clock below 2.9 GHz excluded:
All of these are within our run-to-run noise. 0 hangs, and no new Why, from the traces: OoT has about 157 encoders a frame here (about 100 render passes, 53 compute), and about 4 ms a frame of encoder-to-encoder idle in gaps of about 17–18 µs each. The gap count, total and median are the same with and without the PR (4.04 vs 4.07 ms, 17.3 vs 18.1 µs). So on this GPU the seams look like a fixed cost per encoder boundary (start and end, load and store, tile setup), not the barrier. That matches an earlier M4 Pro test on this game where relaxing the draw-pass barriers left the GPU span unchanged. Fewer encoders or passes would be the lever here. This doesn't argue against the PR: it's correct and active, and the M1 numbers in the description may hold on other GPUs. It just doesn't move OoT on an M2. |
…CT_DATA make wait, and a counter of why draw passes begin behind a full barrier Both ideas are from PR seedds#1970 (AdyaGMD), ported onto our pass-overlap rules, each behind its own lever: - DM_NVN_BARRIER_GAME_BITS (on by default; =0 off, =ab alternates with DM_AB): a game barrier makes the next draw pass wait for everything only when it carries ORDER_PRIMITIVES or ORDER_INDIRECT_DATA. The narrower rule it replaces (off) also made INVALIDATE_SHADER wait; the two differ on that one bit. The decision is one function, game_barrier_needs_full, instead of a mask in the barrier handler. - DM_NVN_BARRIER_WHY (on by default; =0 off): the stats line 'draw-pass barriers up to frame N, per frame' says why passes still began behind a full barrier (after a non-draw encoder, after a game barrier that needs everything, image heaps alive, draw_barriers full) and how many began beside the ones before. t_barrier_bits checks the truth table of both rules and the counter's hand-over, no GPU work, in the default and with each lever off (make check_t_barrier_bits). Not measured on a game: the effect on frame time is for an A/B run (DM_NVN_BARRIER_GAME_BITS=ab DM_AB=300). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…CT_DATA make wait, and a counter of why draw passes begin behind a full barrier Both ideas are from PR seedds#1970 (AdyaGMD), ported onto our pass-overlap rules, each behind its own lever: - DM_NVN_BARRIER_GAME_BITS (on by default; =0 off, =ab alternates with DM_AB): a game barrier makes the next draw pass wait for everything only when it carries ORDER_PRIMITIVES or ORDER_INDIRECT_DATA. The narrower rule it replaces (off) also made INVALIDATE_SHADER wait; the two differ on that one bit. The decision is one function, game_barrier_needs_full, instead of a mask in the barrier handler. - DM_NVN_BARRIER_WHY (on by default; =0 off): the stats line 'draw-pass barriers up to frame N, per frame' says why passes still began behind a full barrier (after a non-draw encoder, after a game barrier that needs everything, image heaps alive, draw_barriers full) and how many began beside the ones before. t_barrier_bits checks the truth table of both rules and the counter's hand-over, no GPU work, in the default and with each lever off (make check_t_barrier_bits). Not measured on a game: the effect on frame time is for an A/B run (DM_NVN_BARRIER_GAME_BITS=ab DM_AB=300). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…M_NVN_BARRIER_HEAP=0 restores the full barriers) Net change of PR seedds#1970 at 4b6ab0f (seedds/nxnative), merged by hand: the heap relaxation and the draw-pass full barriers line are taken; the game-barrier rule stays this tree's bar_frag_only (bar_game_full wraps it), so DM_NVN_BARRIER_GAME_BITS is not read.
|
Measured on an M1 Max (32-core GPU,
A second invocation (2 runs each) gave the same: 46.35 → 48.1 fps, GPU 19.31 → 18.20 ms. On the standing part of the route (identical view), frames held two vsyncs went from 28% to 22%. Screenshots match main. It cherry-picks cleanly onto f02d047. |
|
Data point from an M4 Pro (MacBook Pro, 24 GB, macOS 26.7.2), Ocarina of Time v131072, with this change at adb2c1b applied cleanly on our tree (an older main). The test was a Kokiri Forest route in a window, from the same save copy each run: two runs with this change and two with
The change does what it says: about 73% of the full barriers are gone. On this GPU, though, the GPU p50 stayed the same within the route's noise, so the seams cost little here. The M1 numbers in the description may be the case where it pays. These numbers are for adb2c1b, before the merge with main and the end-barrier stage change (4b6ab0f). |
|
Superseded by e775054 on main (from #2334), with you as co-author for the diagnosis. Both rules are there: image heaps no longer turn off the relaxed barriers, and fragment-only game barriers keep the overlap. The difference is where the alias flush goes. #2334 puts it on the next pass's start barrier, before its fragments, instead of on the end barrier, which costs about 100 KB of command allocator per encoder (see 'The alias flush on every encoder's end barrier' in frame-rate.md). The DM_NVN_BARRIER_HEAP / _GAME_BITS switches weren't taken, since DM_NVN_BARRIER=full gives the A/B. On the stages: MTLStageObject is now on the end barrier's 'after' side. It isn't on the 'before' side: NVN doesn't order later task or mesh work after ORDER_FRAGMENTS, just as it doesn't order vertex work. The device numbers from this thread (M1 Max, iPhone, M2 Air, M4 Pro) are in the reference entry. @cover93-95: the Deku Tree torch-room patch is listed as open. An A/B with DM_NVN_BARRIER=full in that room would settle it. |
…he object and mesh stages on both sides: a mesh pipeline's pass ends after its task and mesh work, and the next pass's task and mesh shaders wait for thi # Conflicts: # dm/gfx/nvn/nvn_mtl.m
…BARRIER_HEAP (seedds#1970), DM_NVN_IND_SPLIT (seedds#2226) and DM_NVN_VCONV_KEEP (main), from the code that reads them
…closed, superseded by seedds#2334, on main as e775054; its two env_switches rows (39aa802) go with it
What and why
With
DM_NVN_BARRIER=game, a draw pass that follows another draw pass can skip the full consumer barrier, so its vertex work overlaps the previous pass's fragments. Two conditions turned that off far more often than they needed to:image_texture) was alive, every draw pass ended and began with the full barrier, in every game. Ocarina of Time (0400486024DEC000) keeps one from its first frame, and its log showedsaved 0for every window. Donkey Kong Bananza's latest run shows the samesaved 0.nvnCommandBufferBarriersince the last pass gave the next pass the full barrier. Ocarina of Time issues about 60 a frame between its passes, mostly bits0x12: order fragments, invalidate texture.A Metal System Trace in Ocarina of Time's slowest area on an M1 showed what this costs. In 103 frames, vertex work started while the previous pass's fragments still ran only 135 times out of about 10,000 pass changes. The seams between and inside passes held about 4.6 ms of idle GPU a frame.
What changes:
resource_vis(), so the alias flush still happens while a heap is alive. Encoders other than draw passes keep their full barriers.ORDER_FRAGMENTS(and its tiled form) orders later fragment work only; later primitives may start at once, even on the Switch.INVALIDATE_TEXTURE/INVALIDATE_SHADERmake writes visible to that ordered work. The relaxed end barrier covers all of that, so onlyORDER_PRIMITIVESandORDER_INDIRECT_DATAkeep the next pass behind the full barrier.draw-pass full barriers up to frame N, per frame, counts why passes still began behind a full barrier: after a non-draw encoder, after a game barrier, image heaps, or barrier mode full.DM_NVN_BARRIER_HEAP=0andDM_NVN_BARRIER_GAME_BITS=0each restore the old behaviour.Checked
Ocarina of Time
0400486024DEC000v131072, Mac app, M1, macOS 26, 30 fps target, MetalFX upscaler on, gameplay through its slowest area. Picture checked by eye in both runs: no flicker, stale images or broken shadows.No local tests run. This is encode-order logic in
nvn_mtl.mwith no unit test covering it.Not checked: other games. Donkey Kong Bananza's logs show the same
saved 0pattern, so it should benefit too, but it was not run with this change. Games that read the previous pass's output in a vertex shader after only anORDER_FRAGMENTSbarrier would show it as stale or flickering geometry; NVN itself does not order that case.Docs
bar_heap_okandbar_game_fullinnvn_mtl.m🤖 Generated with Claude Code