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 
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 
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 
43  double new_max_refresh_rate = [DisplayLinkManager displayRefreshRate];
44  if (fml::TaskRunnerChecker::RunsOnTheSameThread(
45  task_runners_.GetRasterTaskRunner()->GetTaskQueueId(),
46  task_runners_.GetPlatformTaskRunner()->GetTaskQueueId())) {
47  BOOL isRunningOnMac = NO;
48  if (@available(iOS 14.0, *)) {
49  isRunningOnMac = [NSProcessInfo processInfo].iOSAppOnMac;
50  }
51  if (!isRunningOnMac) {
52  // Pressure tested on iPhone 13 pro, the oldest iPhone that supports refresh rate greater than
53  // 60fps. A flutter app can handle fast scrolling on 80 fps with 6 PlatformViews in the scene
54  // at the same time.
55  new_max_refresh_rate = 80;
56  }
57  }
58  if (fabs(new_max_refresh_rate - max_refresh_rate_) > kRefreshRateDiffToIgnore) {
59  max_refresh_rate_ = new_max_refresh_rate;
60  [client_.get() setMaxRefreshRate:max_refresh_rate_];
61  }
62  [client_.get() await];
63 }
64 
65 // |VariableRefreshRateReporter|
67  return [client_.get() getRefreshRate];
68 }
69 
70 fml::scoped_nsobject<VSyncClient> VsyncWaiterIOS::GetVsyncClient() const {
71  return client_;
72 }
73 
74 } // namespace flutter
75 
76 @implementation VSyncClient {
77  flutter::VsyncWaiter::Callback callback_;
78  fml::scoped_nsobject<CADisplayLink> display_link_;
80 }
81 
82 - (instancetype)initWithTaskRunner:(fml::RefPtr<fml::TaskRunner>)task_runner
83  callback:(flutter::VsyncWaiter::Callback)callback {
84  self = [super init];
85 
86  if (self) {
88  _allowPauseAfterVsync = YES;
89  callback_ = std::move(callback);
90  display_link_ = fml::scoped_nsobject<CADisplayLink> {
91  [[CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)] retain]
92  };
93  display_link_.get().paused = YES;
94 
96 
97  task_runner->PostTask([client = [self retain]]() {
98  [client->display_link_.get() addToRunLoop:[NSRunLoop currentRunLoop]
99  forMode:NSRunLoopCommonModes];
100  [client release];
101  });
102  }
103 
104  return self;
105 }
106 
107 - (void)setMaxRefreshRate:(double)refreshRate {
109  return;
110  }
111  double maxFrameRate = fmax(refreshRate, 60);
112  double minFrameRate = fmax(maxFrameRate / 2, 60);
113  if (@available(iOS 15.0, *)) {
114  display_link_.get().preferredFrameRateRange =
115  CAFrameRateRangeMake(minFrameRate, maxFrameRate, maxFrameRate);
116  } else {
117  display_link_.get().preferredFramesPerSecond = maxFrameRate;
118  }
119 }
120 
121 - (void)await {
122  display_link_.get().paused = NO;
123 }
124 
125 - (void)pause {
126  display_link_.get().paused = YES;
127 }
128 
129 - (void)onDisplayLink:(CADisplayLink*)link {
130  CFTimeInterval delay = CACurrentMediaTime() - link.timestamp;
131  fml::TimePoint frame_start_time = fml::TimePoint::Now() - fml::TimeDelta::FromSecondsF(delay);
132 
133  CFTimeInterval duration = link.targetTimestamp - link.timestamp;
134  fml::TimePoint frame_target_time = frame_start_time + fml::TimeDelta::FromSecondsF(duration);
135 
136  TRACE_EVENT2_INT("flutter", "PlatformVsync", "frame_start_time",
137  frame_start_time.ToEpochDelta().ToMicroseconds(), "frame_target_time",
138  frame_target_time.ToEpochDelta().ToMicroseconds());
139 
140  std::unique_ptr<flutter::FrameTimingsRecorder> recorder =
141  std::make_unique<flutter::FrameTimingsRecorder>();
142 
143  current_refresh_rate_ = round(1 / (frame_target_time - frame_start_time).ToSecondsF());
144 
145  recorder->RecordVsync(frame_start_time, frame_target_time);
146  if (_allowPauseAfterVsync) {
147  display_link_.get().paused = YES;
148  }
149  callback_(std::move(recorder));
150 }
151 
152 - (void)invalidate {
153  [display_link_.get() invalidate];
154 }
155 
156 - (void)dealloc {
157  [self invalidate];
158 
159  [super dealloc];
160 }
161 
162 - (double)getRefreshRate {
163  return current_refresh_rate_;
164 }
165 
166 - (CADisplayLink*)getDisplayLink {
167  return display_link_.get();
168 }
169 
170 @end
171 
172 @implementation DisplayLinkManager
173 
175  fml::scoped_nsobject<CADisplayLink> display_link = fml::scoped_nsobject<CADisplayLink> {
176  [[CADisplayLink displayLinkWithTarget:[[[DisplayLinkManager alloc] init] autorelease]
177  selector:@selector(onDisplayLink:)] retain]
178  };
179  display_link.get().paused = YES;
180  auto preferredFPS = display_link.get().preferredFramesPerSecond;
181 
182  // From Docs:
183  // The default value for preferredFramesPerSecond is 0. When this value is 0, the preferred
184  // frame rate is equal to the maximum refresh rate of the display, as indicated by the
185  // maximumFramesPerSecond property.
186 
187  if (preferredFPS != 0) {
188  return preferredFPS;
189  }
190 
191  return [UIScreen mainScreen].maximumFramesPerSecond;
192 }
193 
194 - (void)onDisplayLink:(CADisplayLink*)link {
195  // no-op.
196 }
197 
199  return [[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CADisableMinimumFrameDurationOnPhone"]
200  boolValue];
201 }
202 
203 @end
kRefreshRateDiffToIgnore
const static double kRefreshRateDiffToIgnore
Definition: vsync_waiter_ios.mm:19
-[flutter::VsyncWaiterIOS AwaitVSync]
void AwaitVSync() override
Definition: vsync_waiter_ios.mm:42
-[flutter::VsyncWaiterIOS GetRefreshRate]
double GetRefreshRate() const override
Definition: vsync_waiter_ios.mm:66
-[flutter::VsyncWaiterIOS ~VsyncWaiterIOS]
~VsyncWaiterIOS() override
Definition: vsync_waiter_ios.mm:36
display_link_
fml::scoped_nsobject< CADisplayLink > display_link_
Definition: vsync_waiter_ios.mm:76
-[VSyncClient setMaxRefreshRate:]
void setMaxRefreshRate:(double refreshRate)
Definition: vsync_waiter_ios.mm:107
current_refresh_rate_
double current_refresh_rate_
Definition: vsync_waiter_ios.mm:79
flutter
Definition: accessibility_bridge.h:28
-[flutter::VsyncWaiterIOS GetVsyncClient]
fml::scoped_nsobject< VSyncClient > GetVsyncClient() const
Definition: vsync_waiter_ios.mm:70
-[flutter::VsyncWaiterIOS VsyncWaiterIOS]
VsyncWaiterIOS(const flutter::TaskRunners &task_runners)
Definition: vsync_waiter_ios.mm:23
vsync_waiter_ios.h
VSyncClient
Definition: vsync_waiter_ios.h:38