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