Skip to content

Mac fullscreen: a refresh-rate switch that the screens undo is not made again and again - #2095

Open
Haseenesss wants to merge 1 commit into
seedds:mainfrom
Haseenesss:screen-match-bounce
Open

Haseenesss wants to merge 1 commit into
seedds:mainfrom
Haseenesss:screen-match-bounce

Conversation

@Haseenesss

Copy link
Copy Markdown
Contributor

What and why

With several screens attached, fullscreen made the screens flicker on and off until some lost their signal and the Dock hung.

  • Cause. front_screen_match switches the screen's rate in fullscreen. Each switch reconfigures every screen. On the reporter's three screens (one on DisplayLink) that moved the window off the switched screen and back. Each move undid the switch and made it again: 18 switches in a minute.
  • Change. A switch that has not shown yet is not made again. A switch due within 5 s of the last switch or switch back is not made, and the screen keeps its rate for the run. screen_match_apply is never entered inside itself.

Reported in LNPANDY/NX2Apple#1085, which carries this as a patch until it lands here. The entry is under "Fullscreen switches the screen to a rate the frame rate divides" in docs/reference/frame-rate.md.

Details
  • CGCompleteDisplayConfiguration runs the main run loop. A probe saw NSWindowDidChangeScreenNotification arrive inside a switch back, so the switch back ran twice. The issue's log shows the same doubled switched to and back at lines.
  • The reporter's log (Ocarina of Time, M4 Max, macOS 27.0.1, a 75 Hz screen): 18 × screen at 75 Hz switched to 60 Hz, 13 × screen back at 75 Hz, 3 × could not be switched back, all while the window stayed in fullscreen.

Checked

  • Tests: clang -fsyntax-only of front_mac.m (no new warnings); dm/tools/tests/test_docs.py; docs_symptoms.py (no change).
  • A standalone probe with the same logic, on a 144 Hz screen beside the built-in one: entering fullscreen switched once (144 → 120 Hz), leaving switched back once, as before. A faked move off the screen and back switched back once, deferred the call made inside it, then kept 144 Hz.
  • Not checked: the bounce on DisplayLink screens (none here), and no game was run with the change.
  • Devices and OS: Apple M2, macOS 27.2.

Docs

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

🤖 Generated with Claude Code

…de again and again

With three screens, one of them on DisplayLink, the fullscreen refresh-rate
match (front_screen_match) switched a 75 Hz screen to 60 Hz and back 18
times in a minute: each switch reconfigured every screen, the window's screen
changed and changed back, and each change undid the switch and made it again.
Two of the three screens lost their signal and the Dock and Finder stopped
answering until the screens were unplugged.

- A switch that has not shown on the screen yet is not made again.
- A switch due less than 5 s after the last switch or switch back is not
  made: the screen keeps its rate for the rest of the run, and the log says so.
- screen_match_apply is never entered inside itself. CGCompleteDisplayConfiguration
  runs the main run loop, and the window's screen notification came inside a
  switch back, which then ran twice (the doubled log lines of the issue).

Upstream-Status: pending
Reason: the screen-match loop blanks screens and hangs the Dock with several screens attached
Issue: LNPANDY/NX2Apple#1085

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added host The Horizon host: SVCs, services, threads, memory (dm/host) mac The Mac front end and app (dm/mac, front_mac.m) docs Documentation labels Nov 10, 2026
@PromethOps

Copy link
Copy Markdown

A related measurement, in case it helps here: a second fullscreen problem with several screens, which this PR's front_screen_match changes do not cause and do not cover.

Ocarina of Time (v131072), M5 Pro, macOS 27.2, the game fullscreen on an external fixed 60 Hz 5K screen (the main display), the MacBook's lid open with its ProMotion screen lit beside it. A scripted 150 s walk of Kakariko Village, about 7,500 frames, one run each unless said:

Built-in screen Mode [longframe] lines Display-link tick gaps > 25 ms in the title menu (of 300)
ProMotion fullscreen, link on then off by #2348 (two runs) 205, 257 47
ProMotion fullscreen, DM_PRESENT_LINK=0 207 no link
set to a fixed 60 Hz in System Settings fullscreen 22 1
ProMotion in a window on the external screen 21 0
off (lid closed) fullscreen 59 2

With the built-in screen in ProMotion the link's ticks arrive 33.6 ms apart about one time in six from the title menu on, at a GPU time of 7.5 ms, and the frame step holds 2 vsyncs for most of the route. Battery or mains made no difference. No refresh-rate switch is logged: the external screen is already at 60 Hz, so front_screen_match has nothing to do there, and it does not look at the neighbour screen.

So a variable-refresh screen lit beside the fixed 60 Hz screen that holds the fullscreen window seems to make the system skip that window's refreshes. One Mac, one external screen; why the system does it is not known. The details are in #2348's reference entry ("Open, two screens lit"). Whether the host should match the neighbour screen while fullscreen, or only say so in the log, is your call; given what this PR found about switches reconfiguring every screen, a log hint looks like the safer of the two.

@PromethOps

Copy link
Copy Markdown

Correction to my comment above: its conclusion does not hold. With the built-in screen at a fixed 60 Hz, five later runs of the same walk had 598-793 [longframe] lines, against the 22 of the single run I reported for that setting. So the ProMotion screen beside the external one is not shown to be the cause, and nothing here argues for touching the neighbour screen. The numbers, and what does separate the smooth runs from the others, are in #2348 (now a draft). Sorry for the noise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Documentation host The Horizon host: SVCs, services, threads, memory (dm/host) mac The Mac front end and app (dm/mac, front_mac.m)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants