Skip to content

Commit 5bcd096

Browse files
authored
[iOS] Migrate VSyncClient and DisplayLinkManager to Swift (flutter#187001)
This also splits DisplayLinkManager to its own file. `(void)` casts needed to be added to OCMock usages of DisplayLinkManager because by default Swift warns on unused results and overall, it's safer to keep that behaviour than marking the method results as okay to discard. This renames the `setMaxRefreshRate` `refreshRate` parameter to `requestedRate` since it was shadowing `self.refreshRate`. Makes the code a bit more readable and a bit more future-proof. Finally, this also makes refreshRate a computed property, which ensures that any callers users get a rate within a valid range. It also makes the code in `setMaxRefreshRate` a little more readable. Issue: flutter#112232 <!-- Thanks for filing a pull request! Reviewers are typically assigned within a week of filing a request. To learn more about code review, see our documentation on Tree Hygiene: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md --> ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent 69a048f commit 5bcd096

13 files changed

Lines changed: 317 additions & 380 deletions

engine/src/flutter/shell/platform/darwin/ios/BUILD.gn

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ source_set("InternalFlutterSwift") {
6464
sources = [
6565
"framework/Source/AccessibilityFeatures.swift",
6666
"framework/Source/ConnectionCollection.swift",
67+
"framework/Source/DisplayLinkManager.swift",
6768
"framework/Source/LaunchEngine.swift",
6869
"framework/Source/SplashScreenManager.swift",
6970
"framework/Source/UIPressProxy.swift",
71+
"framework/Source/VSyncClient.swift",
7072
]
7173
public = _flutter_framework_headers + framework_common_headers
7274
}
@@ -162,6 +164,8 @@ source_set("flutter_framework_source") {
162164
"framework/Source/platform_message_response_darwin.mm",
163165
"framework/Source/profiler_metrics_ios.h",
164166
"framework/Source/profiler_metrics_ios.mm",
167+
"framework/Source/vsync_waiter_ios.h",
168+
"framework/Source/vsync_waiter_ios.mm",
165169
"ios_context.h",
166170
"ios_context.mm",
167171
"ios_context_metal_impeller.h",
@@ -233,7 +237,6 @@ if (enable_ios_unittests) {
233237
sources = [
234238
"framework/Source/FlutterFMLTaskRunnerTestHelper.h",
235239
"framework/Source/FlutterFMLTaskRunnerTestHelper.mm",
236-
"framework/Source/FlutterVSyncClient+Testing.h",
237240
]
238241
deps = [
239242
":flutter_engine_bindings",
@@ -374,10 +377,6 @@ source_set("flutter_engine_bindings") {
374377
"framework/Source/FlutterFMLTaskRunner.mm",
375378
"framework/Source/FlutterFMLTaskRunners.h",
376379
"framework/Source/FlutterFMLTaskRunners.mm",
377-
"framework/Source/FlutterVSyncClient.h",
378-
"framework/Source/FlutterVSyncClient.mm",
379-
"framework/Source/vsync_waiter_ios.h",
380-
"framework/Source/vsync_waiter_ios.mm",
381380
]
382381
deps = [
383382
"//flutter/common",

engine/src/flutter/shell/platform/darwin/ios/FlutterTests-Bridging-Header.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,4 @@
88
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFMLTaskRunner.h"
99
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFMLTaskRunnerTestHelper.h"
1010
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFMLTaskRunners.h"
11-
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterVSyncClient+Testing.h"
12-
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterVSyncClient.h"
13-
1411
#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FLUTTERTESTS_BRIDGING_HEADER_H_
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import Foundation
6+
import QuartzCore
7+
import UIKit
8+
9+
/// Provides access to display capabilities and configuration metadata.
10+
///
11+
/// - Warning: Do not use this class to drive active frame scheduling or rendering loops. For frame
12+
/// scheduling, use `VSyncClient` instead.
13+
///
14+
/// This class is responsible for display link configuration management, such as querying the
15+
/// maximum supported refresh rate from the platform as well as plist-based configuration.
16+
///
17+
/// On ProMotion iPhones, 120Hz variable refresh rate support must be explicitly unlocked by setting
18+
/// the `CADisableMinimumFrameDurationOnPhone` key (`disableMinimumFrameDurationOnPhoneKey`) to
19+
/// `true` in the application's `Info.plist`. iPad Pro devices will use 120Hz by default.
20+
///
21+
/// - Note: This class contains only stateless `static` properties and class methods and should not
22+
/// be instantiated.
23+
@objc(FlutterDisplayLinkManager)
24+
public class DisplayLinkManager: NSObject {
25+
26+
/// Info.plist key enabling the full range of ProMotion refresh rates for CADisplayLink callbacks
27+
/// and CAAnimation animations in the app.
28+
///
29+
/// - SeeAlso: https://developer.apple.com/documentation/quartzcore/optimizing_promotion_refresh_rates_for_iphone_13_pro_and_ipad_pro#3885321
30+
internal static let disableMinimumFrameDurationOnPhoneKey = "CADisableMinimumFrameDurationOnPhone"
31+
32+
/// Whether the max refresh rate on iPhone ProMotion devices is enabled.
33+
///
34+
/// This reflects the value of `disableMinimumFrameDurationOnPhoneKey` in the info.plist file.
35+
/// On iPads that support ProMotion, the max refresh rate is enabled by default. Maximum frame
36+
/// rate can be limited via `VSyncClient.setMaxRefreshRate(_:)`
37+
///
38+
/// - Returns: `true` if the max refresh rate on ProMotion devices is enabled.
39+
@objc
40+
public static var maxRefreshRateEnabledOnIPhone: Bool {
41+
return Bundle.main.object(forInfoDictionaryKey: disableMinimumFrameDurationOnPhoneKey)
42+
as? Bool ?? false
43+
}
44+
45+
/// The maximum display refresh rate used for reporting purposes.
46+
///
47+
/// This is intended to return either the hardware maximum refresh rate or the maximum configured
48+
/// by the user (e.g. via an Info.plist setting or custom configuration). The engine does not care
49+
/// about this for frame scheduling. It is only used by tools for instrumentation. The engine uses
50+
/// the duration field of the link per frame for frame scheduling.
51+
///
52+
/// - Attention: Do not use this call in frame scheduling. It is only meant for reporting.
53+
/// - Returns: The refresh rate in frames per second.
54+
@objc
55+
public static var displayRefreshRate: Double {
56+
// TODO(cbracken): This code is incorrect. https://github.com/flutter/flutter/issues/185759
57+
//
58+
// We create a new CADisplayLink, call `preferredFramesPerSecond` on it, then immediately throw
59+
// it away. As noted below, the default value for `preferredFramesPerSecond` is zero, in which
60+
// case, we just return UIScreen.main.maximumFramesPerSecond in all cases; everything before
61+
// that line can be deleted.
62+
//
63+
// If we intend to support configurable preferred FPS, then we should provide API for it. We
64+
// should delete this code either way.
65+
66+
let displayLink = CADisplayLink(target: self, selector: #selector(onDisplayLink(_:)))
67+
displayLink.isPaused = true
68+
let preferredFPS = displayLink.preferredFramesPerSecond
69+
70+
// From Docs:
71+
// The default value for preferredFramesPerSecond is 0. When this value is 0, the preferred
72+
// frame rate is equal to the maximum refresh rate of the display, as indicated by the
73+
// maximumFramesPerSecond property.
74+
if preferredFPS != 0 {
75+
return Double(preferredFPS)
76+
}
77+
78+
return Double(UIScreen.main.maximumFramesPerSecond)
79+
}
80+
81+
@objc
82+
private static func onDisplayLink(_ link: CADisplayLink) {
83+
// no-op.
84+
}
85+
}

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardInsetManager.mm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#include "flutter/fml/time/time_delta.h"
1111
#include "flutter/fml/time/time_point.h"
1212
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
13-
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterVSyncClient.h"
1413
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
1514
#import "flutter/shell/platform/embedder/embedder.h"
1615
#import "flutter/third_party/spring_animation/spring_animation.h"

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterVSyncClient+Testing.h

Lines changed: 0 additions & 31 deletions
This file was deleted.

engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterVSyncClient.h

Lines changed: 0 additions & 135 deletions
This file was deleted.

0 commit comments

Comments
 (0)