Repository navigation
Conversation
…th the 30 fps frame rate): MetalFX's frame interpolator makes the frame halfway between each two presents and the display link shows it between them, so a game held at 30 fps is shown at 60 Ported from cheemu21's Ocarina of Time fork (kid-with-sword-remake 7979a46, X2-X9), the frame generation part only; its pipeline-script capture (DM_PSO_CAPTURE) is left out. The offline archive's .mtl4x unwrap (pso_bundle_unwrap, t_pso_unwrap) comes with it, since the same nvn_mtl.m hunks carry it; build_ipa.sh does not pack .mtl4x yet, so it is idle until an app carries one. - After each present's snapshot copy, in the same command buffer, MTL4FXFrameInterpolator makes the frame between the previous snapshot and this one into the slot's own texture; the display link shows it on the present's first tick and the present's own picture half an interval later. Depth and motion inputs are constant (far depth, no motion): the interpolator's optical flow finds the motion, so it needs nothing from a game's profile. - A picture shown smaller than the game draws it is copied at the shown size, a crop alone 1:1; generation pauses while frames drop until three presents in a row come in time; the picture after a generated frame takes a present allocator of its own (3 to 5); the 30 fps cap keeps the 60 Hz link with frame generation on. - RED_ONCE keys 164-167 (main uses up to 163). Checked on the Mac (M1 Pro): t_framegen 29 checks, t_present_snap 14, t_present_alloc 13, t_link_ticks 43, t_pso_unwrap 9, all pass with Metal validation; test_docs, test_launcher_settings, test_settings. Tears of the Kingdom 1.4.3 at the Great Sky Island lookout, 30 fps cap with frame generation: interpolation p50 7.8-8.5 ms, p95 14.6-16.4 ms of GPU at 1920x1080 over 1,800 generated frames, the history never started again, one slow display-link callback (77 ms); the picture itself was not checked (the Mac's shader libraries for this chain were incomplete and every run drew black, frame generation on or off). Front-ends: new: the launcher setting DM_FRAMEGEN (Frame generation), a toggle in Game drawn while DM_FPS_TARGET is 30 (needs=DM_FPS_TARGET=30), NVN games Co-authored-by: cheemu21 <130556833+cheemu21@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ter its own is taken, not once that present has run on the GPU With frame generation a ring slot's ticket is the present after its own, because its picture is shown again after its generated frame. The acquire and the copy waited until that present had run on the GPU, and it is committed behind the game's next frame on the same queue, so the game lost its frame of overlap with the GPU. Now the slot is free once the display link has taken that present: the picture owed after the generated frame has been committed or skipped by then, and the next copy, committed later on the same queue, runs after it. Ocarina of Time on the Mac (M1 Pro, Hyrule Field, 30 fps cap): the snapshot ring blocked 68-89 % of frames for 8-16 ms, frame time p95 36-39 ms; after it 0 % in most windows, 33.5-33.8 ms (33.5 with frame generation off). On an iPhone 17 in Kakariko Village by day: no measurable change (24.9 fps against 26.2). t_framegen holds the queue once the link has taken p2 and asks for p1's slot. Its alarm goes from 30 to 60 s: the test took 26.6 s before this check.
Open
2 of 3 tasks
This was referenced Nov 13, 2026
Contributor
Author
|
OoT-fps combined phone build, against the baseline (session A, A-2) iPhone 17, Ocarina of Time, Kakariko Village by day, unattended drive script, 7 minutes each, back to back (2026-11-13, 22:20 and 22:36). Medians over the gameplay windows (more than 1,000 draws a frame).
Against A-1's targets:
An earlier pair that cannot be compared: the first pair (21:04-21:19) gave 11.4 fps for the baseline and 29.2 fps for the combined build. That gap is the time of day, not the change: the baseline's save loaded Kakariko at night (GPU p50 60 ms) and the combined build's by day, as B found (#2582). Every run's The combined build is the one installed on the phone. |
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.
Depends on #2241 (frame generation). This branch is based on its head, so until #2241 merges the diff below includes it; the change itself is the last commit.
What changes
With frame generation on, a snapshot ring slot's ticket is the present after its own (
tk + 1), because the slot's picture is shown again after its generated frame. The game's acquire (mtl_snap_acquire_wait) and the copy (snap_take) waited until presenttk + 1had run on the GPU. That present is committed behind the game's next frame on the same queue, so the game waited for the GPU to finish that frame and lost its frame of overlap with the GPU.Now the slot is free once the display link (or the present thread) has taken present
tk + 1(snap_slot_free). By then the picture owed after the generated frame has been committed or skipped, and the next copy, committed later on the same queue, runs after it. When the link drops presents after a stall, it also drops the picture still owed. Frame generation stays off by default; with it off, nothing changes.Before and after
Mac (M1 Pro), Ocarina of Time, Hyrule Field, 30 fps cap, the same route each run,
DM_NVN_WAITS=1:iPhone 17, Kakariko Village by day, 30 fps cap, 6 minutes each, unattended and back to back:
On the phone the difference is within noise. The 4-7 fps seen on the phone with frame generation on comes from the scene, not from frame generation: Kakariko Village at night ran at 8.5 fps (124 ms frames, every present dropped) with frame generation off as well. That is open and outside this change.
Also tried and left out: frame-generation presents on a queue of their own, so that a present's wait for a free drawable does not hold back the game's next frame. With the present waiting for its snapshot on the GPU, the game blocked about 23 ms a frame on its fences (Mac field, 54-64 ms frames). Waiting for the snapshot on the CPU, it matched this change on the Mac and on the phone (26.2 fps), so it brings nothing.
Checks
dm/gfx/tests/t_framegen: a new check holds the Metal 4 queue once the link has taken p2, then asks for p1's slot. Before the fix the acquire is still waiting after 200 ms. After the fix it returns and p4 gets its snapshot. 30 of 30 checks pass with Metal validation. The test's alarm goes from 30 s to 60 s, because the test already took 26.6 s before this check.python3 -m unittest dm/tools/tests/test_docs.pypasses.scripts/regress.py checkwas not run. With frame generation off (the default, and how regress runs), the changed code reduces exactly to the old rule (fg_on()is false), so no game in the regress set reaches it. The Mac and phone runs above are the check for the frame-generation path.The new reference entry is "A frame-generation ring slot waits for the GPU" in
docs/reference/frame-rate.md, with its line in Ocarina of Time's note.