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