fix(scroll): re-verify display link refresh rate after rebuild (#958) - #991
Open
wiskiiii wants to merge 1 commit into
Open
fix(scroll): re-verify display link refresh rate after rebuild (#958)#991wiskiiii wants to merge 1 commit into
wiskiiii wants to merge 1 commit into
Conversation
…s#958) After display sleep/wake or reconnect, CVDisplayLinkCreateWithActiveCGDisplays can bind to a transitional low refresh rate (e.g. 60Hz while the display is still settling back to 144Hz), leaving smooth scrolling stuck at that low rate until the app is restarted. The existing recovery only catches a fully stalled link, not one that is alive but bound to the wrong rate. Schedule a few delayed re-checks after each link creation; if the link's nominal rate is well below the active display's actual rate, rebuild to catch up and clear the recreate cooldown so a following screen-change can rebind against the settled configuration.
There was a problem hiding this comment.
Pull request overview
This PR addresses issue #958 by adding a self-healing mechanism in ScrollPoster to re-verify CVDisplayLink’s nominal refresh rate after (re)creation and recreate the link if it is clearly bound to a transitional/too-low refresh rate after display wake/reconnect.
Changes:
- Adds delayed refresh-rate verification attempts (+2s, +4s, +8s) after each successful
CVDisplayLinkcreation. - If the display link nominal Hz is < 70% of the maximum active display Hz, rebuilds the display link and clears the recreate cooldown to allow subsequent screen-change rebinding.
- Adds helper functions to read the display link nominal refresh rate and the max active display refresh rate.
Comment on lines
+336
to
+340
| guard rateVerifyAttemptsLeft > 0 else { rateVerifyTimer = nil; return } | ||
| let delay = rateVerifyDelays[rateVerifyDelays.count - rateVerifyAttemptsLeft] | ||
| rateVerifyTimer = Timer.scheduledTimer(withTimeInterval: delay, repeats: false) { [weak self] _ in | ||
| self?.verifyRateAndFixIfNeeded() | ||
| } |
Comment on lines
+360
to
+362
| if wasRunning, let refreshed = poster { | ||
| CVDisplayLinkStart(refreshed) | ||
| } |
Comment on lines
+342
to
+348
| // 复查: link 标称率明显低于显示器实际率 -> 重建追平 (幂等, 不受 recreateCooldown 限制) | ||
| private func verifyRateAndFixIfNeeded() { | ||
| rateVerifyAttemptsLeft -= 1 | ||
| guard let current = poster else { rateVerifyTimer = nil; return } | ||
| let nominal = linkNominalHz(current) | ||
| let maxHz = maxActiveDisplayHz() | ||
| guard nominal > 1, maxHz > 1, nominal < maxHz * rateVerifyRatio else { |
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.
Problem and cause analysis
Refer to #958 (comment)
Fix
After each
CVDisplayLinkcreation, schedule a few delayed re-checks (+2s, +4s, +8s). If the link's nominal rate is well below the display's actual rate (< 70%), rebuild and clear the cooldown so any pendingscreenChangecan also rebind.ScrollPoster.swift(+79 lines, no deletions)Testing
Ran a debug build with rate-verify logging on my machine (MBP + 144Hz external display, macOS 26.5) for a week. The fix fired and self-corrected quite a few times, each time the link was rebound from 60Hz to 144Hz within ~2 seconds, with no manual restart needed. Prior to the fix, the same scenario required an app restart every time.
Impact
Scroll smoothing behavior is unchanged — this only affects when and whether the
CVDisplayLinkgets recreated after a display transition. Worst case is an extra harmless recreation or two after a display wake.