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 @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 
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.
46 }
47 
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|
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) {
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 {
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 
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
-[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
-[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