Skip to content

[Draft, result not reproducible] The 60 Hz display link is turned off in fullscreen on a fixed-rate screen whose presents keep turning a refresh late - #2348

Closed
PromethOps wants to merge 1 commit into
seedds:mainfrom
PromethOps:link-late-off
Closed

PromethOps wants to merge 1 commit into
seedds:mainfrom
PromethOps:link-late-off

Conversation

@PromethOps

@PromethOps PromethOps commented Nov 11, 2026 •

Copy link
Copy Markdown

Draft, not reproducible. Five later runs with this change had 598-793 long frames in a setup where one run had 22. The measurements below are as taken, but the change is not shown to be what made the smooth runs smooth, and the "two screens" section is withdrawn. See the correction comment below.

What

In fullscreen on a fixed-rate screen, the host turns the 60 Hz display link off when presents keep turning a refresh late, and the present thread takes the drawables until the window leaves fullscreen. General: no game or screen is named; a screen that shows presents on their targets keeps the link.

This is a workaround. Why the system misses the link's target at a 32 ms lead is open; the code comment and the reference entry say so.

Why

Ocarina of Time (v131072) on an M5 Pro (16-core GPU, macOS 27.2), fullscreen on an external fixed 60 Hz 5K screen (drawable 5120x2880, display link: frame latency 1 (fullscreen, fixed refresh)): about one frame in six took two vsyncs with the GPU at 11-13 ms. The DM_NVN_TSV rows of one run show 3,190 of 7,201 presents shown a refresh after the link's target, on and off, and 817 of the 1,274 frames over 25 ms were such a present or the frame after one.

Steady lateness is harmless (600 late presents in a row in a window, frame p95 17.3 ms). The turns are what costs: a present late after one on time, 14-29 per 300 presents in fullscreen during play, 0-3 in a window or on the built-in screen.

How

  • link_late_note (nvn_mtl.m) counts the turns from each present's presented time and its tick's target, per 300 presents at one frame per tick.
  • Two windows in a row with 8 or more turn the link off (link_late_mode in link_sync), as DM_PRESENT_LINK=0 and the rescue of a silent link do.
  • Leaving fullscreen or the fixed-rate screen gives the link back.
  • A set DM_PRESENT_LINK or DM_PRESENT_LATENCY is left alone. DM_PRESENT_LINK_AUTO=0 keeps the link.

Measurements

A scripted 150 s walk of Kakariko Village from one autosave, about 7,500 frames, one run each, 2026-11-11.

Setup (fullscreen on the external screen unless said) [longframe] lines Windows of 300 frames with frame p95 < 20 ms
Link on, external screen alone (three runs) 1,021-1,245 1 of 26
DM_PRESENT_LINK=0, external screen alone 103 24 of 28
In a window, external screen alone 83 23 of 28
This change, external screen alone 59 28 of 30
Built-in screen (fixed 60 Hz mode), link on 8 30 of 30

With this change the menus kept the link (no bad window); play turned it off at frame 2864 (25 turns of 300); 4 frame-step changes against 11.

Known cost, from the DM_PRESENT_LINK=0 run: frame interval p99 48 ms in one window of 300 frames.

Caveat: two of the three baseline runs and the run with the change were on a tree with DM_NVN_ENC_THREAD=1 and a local change that is not in main (a submission's end not waiting for the encode thread). The baseline without either had 1,245 lines.

Also recorded, open (not fixed here)

With a ProMotion built-in screen lit beside the external one (lid open), the fullscreen walk is slow in every mode, from the title menu on:

Lid open, fullscreen on the external screen [longframe] lines Tick gaps > 25 ms in the menu (of 300)
Built-in screen in ProMotion, this change (two runs) 205, 257 47
Built-in screen in ProMotion, DM_PRESENT_LINK=0 207 no link
Built-in screen set to a fixed 60 Hz, this change 22 1
In a window, built-in screen in ProMotion 21 0

Battery or mains made no difference. The host does nothing about it yet: front_screen_match only moves the screen the window is on. A log hint, or matching the neighbour screen while fullscreen, is for discussion.

Checked

  • make -C dm/gfx/tests GAME=0400486024DEC000 check_t_present_rescue: 112 checks, 0 failed. The new late cases: on-time presents, steadily late ones, a single bad window, held frames, window presents and set switches keep the link; two bad windows turn the 60 Hz link off and leave the 120 and 30 Hz links; leaving fullscreen gives it back.

Not run

  • scripts/regress.py check --games: only Ocarina of Time was played. The policy acts only in fullscreen on a fixed-rate screen with the 60 Hz link, after 600 presents with late turns.
  • dm/tools/docs_symptoms.py and test_docs.py (no heading was added; the entry "Fixed-rate display-link delay remains at latency 1" gained three bullets).
  • An iPhone, another external screen, another Mac. Frame latency 2 on the external screen alone is not measured.

🤖 Generated with Claude Code

…reen whose presents keep turning a refresh late, and the present thread takes the drawables until the window leaves fullscreen

In fullscreen on an external fixed 60 Hz 5K screen (M5 Pro, macOS 27.2) the system showed 3,190 of 7,201 presents a refresh after the display link's target, on and off: 14-29 presents per 300 turned late after one on time, against 0-3 in a window or on the built-in screen. Each turn costs a frame of two vsyncs and the frame step then holds 2 or 3 vsyncs on frames that cost 0.8-1.3. Steady lateness is harmless (600 late presents in a row in a window at frame p95 17.3 ms), so the turns are what is counted.

link_late_note (nvn_mtl.m) counts the turns from each present's presented time and its tick's target, per 300 presents at one frame per tick. Two windows in a row with 8 or more turn the link off (link_late_mode in link_sync), as DM_PRESENT_LINK=0 and the rescue of a silent link do; leaving fullscreen or the fixed-rate screen gives it back. A set DM_PRESENT_LINK or DM_PRESENT_LATENCY is left alone; DM_PRESENT_LINK_AUTO=0 keeps the link. This is a workaround: why the system misses the target at a 32 ms lead is open, and the code comment and the reference entry say so.

Measured on Ocarina of Time (v131072), a scripted 150 s walk of Kakariko Village from one autosave, about 7,500 frames, one run each, the external screen as the only one lit: 1,021-1,245 [longframe] lines with the link (1 of 26 windows of 300 frames with frame p95 under 20 ms), 59 with this change (28 of 30, link off at frame 2864, menus kept it). Two of the three baseline runs and the run with the change were on a tree with DM_NVN_ENC_THREAD=1 and a local change that is not in main. Known cost, from a DM_PRESENT_LINK=0 run: frame interval p99 48 ms in one window of 300 frames.

Also recorded, open: with a ProMotion built-in screen lit beside the external one, the fullscreen walk is slow in every mode (205-257 lines, the link skipping 47 of 300 ticks in the title menu); 22 with the built-in screen set to a fixed 60 Hz, 21 in a window. The host does nothing about it yet.

Checked: t_present_rescue (late) 112 checks, 0 failed. Not run: regress.py on other games, docs_symptoms.py (no heading added), an iPhone, another external screen or Mac.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@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
@PromethOps
PromethOps marked this pull request as draft November 11, 2026 18:32
@PromethOps

Copy link
Copy Markdown
Author

Correction: the result in this PR does not reproduce. Marked as draft; please do not merge.

Later the same day, with the same policy, the same screens (external fixed 60 Hz screen as the main display, the game fullscreen on it, lid open, built-in screen at a fixed 60 Hz, mains power) and the same scripted walk, five runs in a row had 598-793 [longframe] lines of about 7,500 frames, against the 22 of the one run I reported for that setup. The policy fired in each (link off at frames 606-645), so turning the link off is not shown to be what made the 59 and 22 runs smooth.

What separates the smooth runs from the others, in the DM_NVN_TSV rows (frames 3500-7000 of the route):

Run [longframe] @sync_ms mean @drawable_ms mean
smooth 22 10.7 1.7
not smooth 600 0.4 21.8

In the smooth runs the game blocks about a GPU frame in its fence wait each frame and the present thread gets its drawable at once. In the others the fence wait is near 0 and the present thread waits 22 ms on average in nextDrawable (1,140 of 3,500 waits over 20 ms). The regime is there from the title menu, before the policy acts, and one run changed regime by itself around frame 450. I do not know what selects it.

Explanations I gave and that later runs contradict:

  • "A ProMotion screen lit beside the external one makes the system skip refreshes; setting it to 60 Hz fixes it": the five runs above had it at 60 Hz. Withdrawn, here and in my comment on Mac fullscreen: a refresh-rate switch that the screens undo is not made again and again #2095.
  • Also checked and not the cause, each contradicted by a run: battery or mains, Spotlight indexing after a build (a run at 0% background load had 598 lines), the game not being the frontmost app (it was, in runs with 600 and 701 lines), the share of time on performance cores (90% in a run with 600 lines).

What still stands: the counts of late turns per 300 presents (14-29 in fullscreen on this screen during play, 0-3 in a window), the window runs (21 and 83 lines), and the test (t_present_rescue, 112 checks). The reading "the system misses the link's target" is not established either: with the GPU at 12 ms of 16.7 and a 32 ms lead, a frame that is ready close to the tick would show the same on-and-off lateness, and that depends on the phase the two regimes differ by.

One more uncontrolled difference: the five later runs were on a build that also had #2344 applied (one of them with DM_NVN_SEEN_KEEP=0); I have not rebuilt without it.

I am leaving this as a draft with the measurements for whoever knows the pacing code; the two regimes may be the useful part. Happy to close it if it is only noise.

@PromethOps PromethOps changed the title The 60 Hz display link is turned off in fullscreen on a fixed-rate screen whose presents keep turning a refresh late (Ocarina of Time on an external 60 Hz screen: 1,021-1,245 long frames of 7,500 → 59) [Draft, result not reproducible] The 60 Hz display link is turned off in fullscreen on a fixed-rate screen whose presents keep turning a refresh late Nov 11, 2026
@PromethOps

Copy link
Copy Markdown
Author

Closing: the result does not reproduce (see the correction above), so there is nothing here to merge. The measurements stay in this thread in case the two pacing regimes are of use to someone working on the frame step or the display link.

@PromethOps PromethOps closed this Nov 11, 2026
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.

1 participant