Skip to content

draw_barriers game works again with image family heaps; fragment-only barriers keep the overlap - #2334

Closed
LiamPalmqvist wants to merge 3 commits into
seedds:mainfrom
LiamPalmqvist:draw-barriers-relaxed
Closed

LiamPalmqvist wants to merge 3 commits into
seedds:mainfrom
LiamPalmqvist:draw-barriers-relaxed

Conversation

@LiamPalmqvist

Copy link
Copy Markdown
Contributor

draw_barriers game lets a draw pass's vertex work overlap the previous pass's fragments, ordered by the game's own barriers. On Ocarina of Time it had been doing nothing since 7f50222: every draw pass got the full barrier (draw-pass entry barriers full 31025 / saved 0 per 300 frames).

Cause

Two things in enc_r_begin_pass / enc_r_end_pass (dm/gfx/nvn/nvn_mtl.m):

  1. The relaxed barriers were turned off whenever image family heaps exist (&& !g_image_heap_n), to keep the alias flush off end barriers, where it costs a command allocator about 100 KB per encoder. Ocarina of Time's dynamic resolution always has family heaps.
  2. Any nonzero nvnCommandBufferBarrier forced the full consumer barrier. In NVN only ORDER_PRIMITIVES and ORDER_INDIRECT_DATA order earlier work before later vertex or indirect fetch. Ocarina of Time's most common barrier, 0x12 (ORDER_FRAGMENTS | INVALIDATE_TEXTURE, pass dump of frame 1400), asks only for fragment-to-fragment order, which the relaxed end barrier (vertex, mesh and fragment before fragment, dispatch and blit) already gives.

Fix (about 7 lines)

  • The guard is removed. The relaxed end barrier flushes to the device only, like m4_end. When aliases exist, the alias flush is a start barrier of the next pass before its fragments (barrierAfterQueueStages:All beforeStages:Fragment visibilityOptions:resource_vis()), following the file's rule that alias flushes go on start barriers (no allocator memory), and vertex work still overlaps.
  • g_game_barrier_all: only a barrier with ORDER_PRIMITIVES or ORDER_INDIRECT_DATA forces the full consumer barrier. g_game_barrier keeps its meaning for the slice-merge check.
  • Games without aliases behave as before apart from the bit narrowing.
  • Docs: a docs/reference/frame-rate.md entry, the draw_barriers row in dm/games/README.md, the Ocarina of Time note (profile row and "Found on this game"), the symptom index.

Measured

Ocarina of Time (0400486024DEC000), M1 Max on AC, kokiri-deck gameplay (t 36-52 s), frame hold off, two runs each, every screenshot in Kokiri Forest:

frame ms GPU ms
base 23.15 / 23.55 22.02 / 22.25
guard removed only 22.76 / 22.82 21.57 / 21.68
both changes 21.85 / 21.59 20.38 / 20.23

Bench p50: fps 42.5 → 45.8, frame 23.52 → 21.84 ms, GPU 22.88 → 20.91 ms, frames over budget 88 → 23. Entry barriers saved 0 → about 68 of 103 a frame. In a Metal System Trace, render-to-render idle gaps in one command buffer went from 141 to 72 a frame (2.34 → 1.15 ms). The picture is unchanged: the screenshots match as closely as two base runs match each other (PSNR base vs base 19.8 dB, base vs fix 20.0-20.6 dB, the differences in Navi, particles and leaves); no [RED] lines.

Checked

  • make -C dm/host GAME=0400486024DEC000 builds; make -C dm/gfx/tests GAME=0400486024DEC000 run passes, including t_image_alias, t_rt_range* and t_copy_alias.
  • test_docs.py passes after docs_symptoms.py.
  • regress.py check could not run: the only game built on this Mac is Ocarina of Time, which has no smoke route. The other games with draw_barriers game (Breath of the Wild and others) now also skip the full barrier after fragment-only barriers, as NVN allows, and were not run on this change.

Open

About 37 full entry barriers a frame remain on Ocarina of Time (ORDER_PRIMITIVES barriers, compute in between), and about 1.3 ms a frame of gaps at command-buffer boundaries (segment cuts and the CPU commit).

…ent-only game barrier no longer forces the full one: since 7f50222 the relaxed draw-pass barriers were off whenever image family heaps existed (to keep the alias flush off end barriers, about 100 KB of command allocator per encoder), which Ocarina of Time's dynamic resolution always has, and any nonzero nvnCommandBufferBarrier forced the full consumer barrier although NVN orders later vertex or indirect fetch only for ORDER_PRIMITIVES and ORDER_INDIRECT_DATA (the game's most common barrier is 0x12, ORDER_FRAGMENTS | INVALIDATE_TEXTURE). The relaxed end barrier now flushes to the device only and, when aliases exist, the alias flush is a start barrier of the next pass before its fragments (no allocator cost, vertex work still overlaps); g_game_barrier_all marks the barriers that force the full one. Ocarina of Time on an M1 Max, gameplay, frame hold off, two runs each: frame 23.2-23.6 to 21.6-21.9 ms, GPU 22.0-22.3 to 20.2-20.4 ms, about 68 of 103 entry barriers a frame saved, same-buffer render-pass gaps 141 to 72 a frame; picture unchanged
@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 11, 2026
# Conflicts:
#	dm/games/0400486024DEC000/README.md
#	dm/games/README.md
# Conflicts:
#	dm/games/0400486024DEC000/README.md
@dpadr

dpadr commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

A user log on main from an M1 Pro (discussion #1789, @brucewrks; our read of it) shows the same thing in the wild: draw-pass entry barriers full 33320 / saved 0 per 300 frames over a 3 h session.

A second device data point: on an M2 Air, the equivalent fix in #1970 saved ~75 % of entry barriers but didn't move GPU span, so the gain looks device-dependent.

@seedds

seedds commented Nov 12, 2026

Copy link
Copy Markdown
Owner

Landed as e775054 on main, with you as co-author. Your code is kept: the guard is gone, the alias flush is a start barrier before the fragments, and only ORDER_PRIMITIVES / ORDER_INDIRECT_DATA force the full barrier. Added on top:

  • The relaxed end barrier also waits for MTLStageObject. A task shader's writes weren't covered on that side; this was missing on main too.
  • One variable instead of two: g_game_barrier keeps the barrier bits, and GAME_BARRIER_FULL masks them. The slice merge still stops at any barrier.
  • Docs: only Ocarina of Time's profile has draw_barriers game (Breath of the Wild's says full), so the 'other games' line is gone. OoT's note no longer says the start-barrier flush 'was not kept'. The entry now has the per-device numbers from Draw passes overlap again while image heaps are alive and after fragment-only game barriers #1970: a second M1 Max and an iPhone gained, an M2 Air and an M4 Pro didn't change.
    Checked: host build, make -C dm/gfx/tests run, and the alias and rt_range checks with DM_NVN_BARRIER=game. Thanks for the measurements and the trace.

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.

3 participants