| 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
18 // When calculating refresh rate diffrence, anything within 0.1 fps is ignored.
19 const static double kRefreshRateDiffToIgnore = 0.1;
20
21 namespace flutter {
22
23 VsyncWaiterIOS::VsyncWaiterIOS(const flutter::TaskRunners& task_runners)
24 : VsyncWaiter(task_runners) {
25 auto callback = [this](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
26 const fml::TimePoint start_time = recorder->GetVsyncStartTime();
27 const fml::TimePoint target_time = recorder->GetVsyncTargetTime();
28 FireCallback(start_time, target_time, true);
29 };
30 client_ =
31 fml::scoped_nsobject{[[VSyncClient alloc] initWithTaskRunner:task_runners_.GetUITaskRunner()
32 callback:callback]};
33 max_refresh_rate_ = [DisplayLinkManager displayRefreshRate];
34 }
35
36 VsyncWaiterIOS::~VsyncWaiterIOS() {
37 // This way, we will get no more callbacks from the display link that holds a weak (non-nilling)
38 // reference to this C++ object.
39 [client_.get() invalidate];
40 }
41
42 void VsyncWaiterIOS::AwaitVSync() {
43 double new_max_refresh_rate = [DisplayLinkManager displayRefreshRate];
44 if (fabs(new_max_refresh_rate - max_refresh_rate_) > kRefreshRateDiffToIgnore) {
45 max_refresh_rate_ = new_max_refresh_rate;
46 [client_.get() setMaxRefreshRate:max_refresh_rate_];
47 }
48 [client_.get() await];
49 }
50
51 // |VariableRefreshRateReporter|
52 double VsyncWaiterIOS::GetRefreshRate() const {
53 return [client_.get() getRefreshRate];
54 }
55
56 fml::scoped_nsobject<VSyncClient> VsyncWaiterIOS::GetVsyncClient() const {
57 return client_;
58 }
59
60 } // namespace flutter
61
62 @implementation VSyncClient {
63 flutter::VsyncWaiter::Callback callback_;
64 fml::scoped_nsobject<CADisplayLink> display_link_;
65 double current_refresh_rate_;
66 }
67
68 - (instancetype)initWithTaskRunner:(fml::RefPtr<fml::TaskRunner>)task_runner
69 callback:(flutter::VsyncWaiter::Callback)callback {
70 self = [super init];
71
72 if (self) {
73 current_refresh_rate_ = [DisplayLinkManager displayRefreshRate];
74 _allowPauseAfterVsync = YES;
75 callback_ = std::move(callback);
76 display_link_ = fml::scoped_nsobject<CADisplayLink> {
77 [[CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)] retain]
78 };
79 display_link_.get().paused = YES;
80
81 [self setMaxRefreshRate:[DisplayLinkManager displayRefreshRate]];
82
83 task_runner->PostTask([client = [self retain]]() {
84 [client->display_link_.get() addToRunLoop:[NSRunLoop currentRunLoop]
85 forMode:NSRunLoopCommonModes];
86 [client release];
87 });
88 }
89
90 return self;
91 }
92
93 - (void)setMaxRefreshRate:(double)refreshRate {
94 if (!DisplayLinkManager.maxRefreshRateEnabledOnIPhone) {
95 return;
96 }
97 double maxFrameRate = fmax(refreshRate, 60);
98 double minFrameRate = fmax(maxFrameRate / 2, 60);
99 if (@available(iOS 15.0, *)) {
100 display_link_.get().preferredFrameRateRange =
101 CAFrameRateRangeMake(minFrameRate, maxFrameRate, maxFrameRate);
102 } else {
103 display_link_.get().preferredFramesPerSecond = maxFrameRate;
104 }
105 }
106
107 - (void)await {
108 display_link_.get().paused = NO;
109 }
110
111 - (void)pause {
112 display_link_.get().paused = YES;
113 }
114
115 - (void)onDisplayLink:(CADisplayLink*)link {
116 CFTimeInterval delay = CACurrentMediaTime() - link.timestamp;
117 fml::TimePoint frame_start_time = fml::TimePoint::Now() - fml::TimeDelta::FromSecondsF(delay);
118
119 CFTimeInterval duration = link.targetTimestamp - link.timestamp;
120 fml::TimePoint frame_target_time = frame_start_time + fml::TimeDelta::FromSecondsF(duration);
121
122 TRACE_EVENT2_INT("flutter", "PlatformVsync", "frame_start_time",
123 frame_start_time.ToEpochDelta().ToMicroseconds(), "frame_target_time",
124 frame_target_time.ToEpochDelta().ToMicroseconds());
125
126 std::unique_ptr<flutter::FrameTimingsRecorder> recorder =
127 std::make_unique<flutter::FrameTimingsRecorder>();
128
129 current_refresh_rate_ = round(1 / (frame_target_time - frame_start_time).ToSecondsF());
130
131 recorder->RecordVsync(frame_start_time, frame_target_time);
132 if (_allowPauseAfterVsync) {
133 display_link_.get().paused = YES;
134 }
135 callback_(std::move(recorder));
136 }
137
138 - (void)invalidate {
139 [display_link_.get() invalidate];
140 }
141
142 - (void)dealloc {
143 [self invalidate];
144
145 [super dealloc];
146 }
147
148 - (double)getRefreshRate {
149 return current_refresh_rate_;
150 }
151
152 - (CADisplayLink*)getDisplayLink {
153 return display_link_.get();
154 }
155
156 @end
157
158 @implementation DisplayLinkManager
159
160 + (double)displayRefreshRate {
161 fml::scoped_nsobject<CADisplayLink> display_link = fml::scoped_nsobject<CADisplayLink> {
162 [[CADisplayLink displayLinkWithTarget:[[[DisplayLinkManager alloc] init] autorelease]
163 selector:@selector(onDisplayLink:)] retain]
164 };
165 display_link.get().paused = YES;
166 auto preferredFPS = display_link.get().preferredFramesPerSecond;
167
168 // From Docs:
169 // The default value for preferredFramesPerSecond is 0. When this value is 0, the preferred
170 // frame rate is equal to the maximum refresh rate of the display, as indicated by the
171 // maximumFramesPerSecond property.
172
173 if (preferredFPS != 0) {
174 return preferredFPS;
175 }
176
177 return [UIScreen mainScreen].maximumFramesPerSecond;
178 }
179
180 - (void)onDisplayLink:(CADisplayLink*)link {
181 // no-op.
182 }
183
184 + (BOOL)maxRefreshRateEnabledOnIPhone {
185 return [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"]
186 boolValue];
187 }
188
189 @end
kRefreshRateDiffToIgnore
const static double kRefreshRateDiffToIgnore
Definition: vsync_waiter_ios.mm:19
-[flutter::VsyncWaiterIOS AwaitVSync]
void AwaitVSync() override
Definition: vsync_waiter_ios.mm:42
DisplayLinkManager::maxRefreshRateEnabledOnIPhone
BOOL maxRefreshRateEnabledOnIPhone
Definition: FlutterMetalLayer.mm:13
-[flutter::VsyncWaiterIOS GetRefreshRate]
double GetRefreshRate() const override
Definition: vsync_waiter_ios.mm:52
DisplayLinkManager
Definition: FlutterMetalLayer.mm:12
-[flutter::VsyncWaiterIOS ~VsyncWaiterIOS]
~VsyncWaiterIOS() override
Definition: vsync_waiter_ios.mm:36
display_link_
fml::scoped_nsobject< CADisplayLink > display_link_
Definition: vsync_waiter_ios.mm:62
-[VSyncClient setMaxRefreshRate:]
void setMaxRefreshRate:(double refreshRate)
Definition: vsync_waiter_ios.mm:93
current_refresh_rate_
double current_refresh_rate_
Definition: vsync_waiter_ios.mm:65
flutter
Definition: accessibility_bridge.h:28
-[flutter::VsyncWaiterIOS GetVsyncClient]
fml::scoped_nsobject< VSyncClient > GetVsyncClient() const
Definition: vsync_waiter_ios.mm:56
-[flutter::VsyncWaiterIOS VsyncWaiterIOS]
VsyncWaiterIOS(const flutter::TaskRunners &task_runners)
Definition: vsync_waiter_ios.mm:23
vsync_waiter_ios.h
+[DisplayLinkManager displayRefreshRate]
double displayRefreshRate()
Definition: vsync_waiter_ios.mm:160
VSyncClient
Definition: vsync_waiter_ios.h:38