Flutter Engine
 
Loading...
Searching...
No Matches
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
14#include "flutter/fml/logging.h"
18
20
21NSString* 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.
28const static double kRefreshRateDiffToIgnore = 0.1;
29
30namespace flutter {
31
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 {
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
std::function< void(std::unique_ptr< FrameTimingsRecorder >)> Callback
void FireCallback(fml::TimePoint frame_start_time, fml::TimePoint frame_target_time, bool pause_secondary_tasks=true)
double GetRefreshRate() const override
VsyncWaiterIOS(const flutter::TaskRunners &task_runners)
constexpr int64_t ToMicroseconds() const
Definition time_delta.h:62
constexpr TimeDelta ToEpochDelta() const
Definition time_point.h:52
static TimePoint Now()
Definition time_point.cc:49
FlutterDesktopBinaryReply callback
#define FML_DCHECK(condition)
Definition logging.h:122
void setMaxRefreshRate:(double refreshRate)
void invalidate()
Call invalidate before releasing this object to remove from runloops.
#define TRACE_EVENT2_INT(category_group, name, arg1_name, arg1_val, arg2_name, arg2_val)
CADisplayLink * _displayLink
static const double kRefreshRateDiffToIgnore
FLUTTER_ASSERT_ARC NSString *const kCADisableMinimumFrameDurationOnPhoneKey
Info.plist key enabling the full range of ProMotion refresh rates for CADisplayLink callbacks and CAA...
int BOOL