| Flutter iOS Embedder |

vsync_waiter_ios.mm
Go to the documentation of this file.
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 "flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.h"
6
7 #include <utility>
8
9 #include <Foundation/Foundation.h>
10 #include <UIKit/UIKit.h>
11 #include <mach/mach_time.h>
12
13 #include "flutter/common/task_runners.h"
14 #include "flutter/fml/logging.h"
15 #include "flutter/fml/memory/task_runner_checker.h"
16 #include "flutter/fml/trace_event.h"
17 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
18
19 FLUTTER_ASSERT_ARC
20
21 @interface VSyncClient ()
22 @property(nonatomic, assign, readonly) double refreshRate;
23 @end
24
25 // When calculating refresh rate diffrence, anything within 0.1 fps is ignored.
26 const static double kRefreshRateDiffToIgnore = 0.1;
27
28 namespace flutter {
29
30 VsyncWaiterIOS::VsyncWaiterIOS(const flutter::TaskRunners& task_runners)
31 : VsyncWaiter(task_runners) {
32 auto callback = [this](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
33 const fml::TimePoint start_time = recorder->GetVsyncStartTime();
34 const fml::TimePoint target_time = recorder->GetVsyncTargetTime();
35 FireCallback(start_time, target_time, true);
36 };
37 client_ = [[VSyncClient alloc] initWithTaskRunner:task_runners_.GetUITaskRunner()
38 callback:callback];
39 max_refresh_rate_ = DisplayLinkManager.displayRefreshRate;
40 }
41
42 VsyncWaiterIOS::~VsyncWaiterIOS() {
43 // This way, we will get no more callbacks from the display link that holds a weak (non-nilling)
44 // reference to this C++ object.
45 [client_ invalidate];
46 }
47
48 void VsyncWaiterIOS::AwaitVSync() {
49 double new_max_refresh_rate = DisplayLinkManager.displayRefreshRate;
50 if (fabs(new_max_refresh_rate - max_refresh_rate_) > kRefreshRateDiffToIgnore) {
51 max_refresh_rate_ = new_max_refresh_rate;
52 [client_ setMaxRefreshRate:max_refresh_rate_];
53 }
54 [client_ await];
55 }
56
57 // |VariableRefreshRateReporter|
58 double VsyncWaiterIOS::GetRefreshRate() const {
59 return client_.refreshRate;
60 }
61
62 } // namespace flutter
63
64 @implementation VSyncClient {
65 flutter::VsyncWaiter::Callback _callback;
66 CADisplayLink* _displayLink;
67 }
68
69 - (instancetype)initWithTaskRunner:(fml::RefPtr<fml::TaskRunner>)task_runner
70 callback:(flutter::VsyncWaiter::Callback)callback {
71 self = [super init];
72
73 if (self) {
74 _refreshRate = DisplayLinkManager.displayRefreshRate;
75 _allowPauseAfterVsync = YES;
76 _callback = std::move(callback);
77 _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)];
78 _displayLink.paused = YES;
79
80 [self setMaxRefreshRate:DisplayLinkManager.displayRefreshRate];
81
82 // Strongly retain the the captured link until it is added to the runloop.
83 CADisplayLink* localDisplayLink = _displayLink;
84 task_runner->PostTask([localDisplayLink]() {
85 [localDisplayLink addToRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
86 });
87 }
88
89 return self;
90 }
91
92 - (void)setMaxRefreshRate:(double)refreshRate {
93 if (!DisplayLinkManager.maxRefreshRateEnabledOnIPhone) {
94 return;
95 }
96 double maxFrameRate = fmax(refreshRate, 60);
97 double minFrameRate = fmax(maxFrameRate / 2, 60);
98 if (@available(iOS 15.0, *)) {
99 _displayLink.preferredFrameRateRange =
100 CAFrameRateRangeMake(minFrameRate, maxFrameRate, maxFrameRate);
101 } else {
102 _displayLink.preferredFramesPerSecond = maxFrameRate;
103 }
104 }
105
106 - (void)await {
107 _displayLink.paused = NO;
108 }
109
110 - (void)pause {
111 _displayLink.paused = YES;
112 }
113
114 - (void)onDisplayLink:(CADisplayLink*)link {
115 CFTimeInterval delay = CACurrentMediaTime() - link.timestamp;
116 fml::TimePoint frame_start_time = fml::TimePoint::Now() - fml::TimeDelta::FromSecondsF(delay);
117
118 CFTimeInterval duration = link.targetTimestamp - link.timestamp;
119 fml::TimePoint frame_target_time = frame_start_time + fml::TimeDelta::FromSecondsF(duration);
120
121 TRACE_EVENT2_INT("flutter", "PlatformVsync", "frame_start_time",
122 frame_start_time.ToEpochDelta().ToMicroseconds(), "frame_target_time",
123 frame_target_time.ToEpochDelta().ToMicroseconds());
124
125 std::unique_ptr<flutter::FrameTimingsRecorder> recorder =
126 std::make_unique<flutter::FrameTimingsRecorder>();
127
128 _refreshRate = round(1 / (frame_target_time - frame_start_time).ToSecondsF());
129
130 recorder->RecordVsync(frame_start_time, frame_target_time);
131 if (_allowPauseAfterVsync) {
132 link.paused = YES;
133 }
134 _callback(std::move(recorder));
135 }
136
137 - (void)invalidate {
138 [_displayLink invalidate];
139 _displayLink = nil; // Break retain cycle.
140 }
141
142 - (CADisplayLink*)getDisplayLink {
143 return _displayLink;
144 }
145
146 @end
147
148 @implementation DisplayLinkManager
149
150 + (double)displayRefreshRate {
151 CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:[[[self class] alloc] init]
152 selector:@selector(onDisplayLink:)];
153 displayLink.paused = YES;
154 auto preferredFPS = displayLink.preferredFramesPerSecond;
155
156 // From Docs:
157 // The default value for preferredFramesPerSecond is 0. When this value is 0, the preferred
158 // frame rate is equal to the maximum refresh rate of the display, as indicated by the
159 // maximumFramesPerSecond property.
160
161 if (preferredFPS != 0) {
162 return preferredFPS;
163 }
164
165 return UIScreen.mainScreen.maximumFramesPerSecond;
166 }
167
168 - (void)onDisplayLink:(CADisplayLink*)link {
169 // no-op.
170 }
171
172 + (BOOL)maxRefreshRateEnabledOnIPhone {
173 return [[NSBundle.mainBundle objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"]
174 boolValue];
175 }
176
177 @end
kRefreshRateDiffToIgnore
const static double kRefreshRateDiffToIgnore
Definition: vsync_waiter_ios.mm:26
-[flutter::VsyncWaiterIOS AwaitVSync]
void AwaitVSync() override
Definition: vsync_waiter_ios.mm:48
DisplayLinkManager::maxRefreshRateEnabledOnIPhone
BOOL maxRefreshRateEnabledOnIPhone
Definition: FlutterMetalLayer.mm:17
-[flutter::VsyncWaiterIOS GetRefreshRate]
double GetRefreshRate() const override
Definition: vsync_waiter_ios.mm:58
-[VSyncClient invalidate]
void invalidate()
Call invalidate before releasing this object to remove from runloops.
Definition: vsync_waiter_ios.mm:137
DisplayLinkManager
Definition: FlutterMetalLayer.mm:16
DisplayLinkManager::displayRefreshRate
double displayRefreshRate
The display refresh rate used for reporting purposes. The engine does not care about this for frame s...
Definition: vsync_waiter_ios.h:32
-[flutter::VsyncWaiterIOS ~VsyncWaiterIOS]
~VsyncWaiterIOS() override
Definition: vsync_waiter_ios.mm:42
-[VSyncClient await]
void await()
Definition: vsync_waiter_ios.mm:106
FlutterMacros.h
-[VSyncClient setMaxRefreshRate:]
void setMaxRefreshRate:(double refreshRate)
Definition: vsync_waiter_ios.mm:92
_displayLink
CADisplayLink * _displayLink
Definition: vsync_waiter_ios.mm:64
flutter
Definition: accessibility_bridge.h:28
-[flutter::VsyncWaiterIOS VsyncWaiterIOS]
VsyncWaiterIOS(const flutter::TaskRunners &task_runners)
Definition: vsync_waiter_ios.mm:30
vsync_waiter_ios.h
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
VSyncClient
Definition: vsync_waiter_ios.h:36