9 #include <Foundation/Foundation.h>
10 #include <UIKit/UIKit.h>
11 #include <mach/mach_time.h>
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"
24 @property(nonatomic, assign, readonly)
double refreshRate;
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);
39 client_ = [[
VSyncClient alloc] initWithTaskRunner:task_runners_.GetUITaskRunner()
53 max_refresh_rate_ = new_max_refresh_rate;
61 return client_.refreshRate;
67 flutter::VsyncWaiter::Callback _callback;
71 - (instancetype)initWithTaskRunner:(
fml::RefPtr<
fml::TaskRunner>)task_runner
72 callback:(
flutter::VsyncWaiter::Callback)callback {
73 FML_DCHECK(task_runner);
75 if (
self = [super init]) {
77 _allowPauseAfterVsync = YES;
78 _callback = std::move(callback);
79 _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)];
86 task_runner->PostTask([localDisplayLink]() {
87 [localDisplayLink addToRunLoop:NSRunLoop.currentRunLoop forMode:NSRunLoopCommonModes];
94 - (void)setMaxRefreshRate:(
double)refreshRate {
98 double maxFrameRate = fmax(refreshRate, 60);
99 double minFrameRate = fmax(maxFrameRate / 2, 60);
100 if (@available(iOS 15.0, *)) {
102 CAFrameRateRangeMake(minFrameRate, maxFrameRate, maxFrameRate);
116 - (void)onDisplayLink:(CADisplayLink*)link {
117 CFTimeInterval delay = CACurrentMediaTime() - link.timestamp;
118 fml::TimePoint frame_start_time = fml::TimePoint::Now() -
fml::TimeDelta::FromSecondsF(delay);
120 CFTimeInterval duration = link.targetTimestamp - link.timestamp;
121 fml::TimePoint frame_target_time = frame_start_time +
fml::TimeDelta::FromSecondsF(duration);
123 TRACE_EVENT2_INT(
"flutter",
"PlatformVsync",
"frame_start_time",
124 frame_start_time.ToEpochDelta().ToMicroseconds(),
"frame_target_time",
125 frame_target_time.ToEpochDelta().ToMicroseconds());
127 std::unique_ptr<flutter::FrameTimingsRecorder> recorder =
128 std::make_unique<flutter::FrameTimingsRecorder>();
130 _refreshRate = round(1 / (frame_target_time - frame_start_time).ToSecondsF());
132 recorder->RecordVsync(frame_start_time, frame_target_time);
133 if (_allowPauseAfterVsync) {
136 _callback(std::move(recorder));
140 [_displayLink invalidate];
144 - (CADisplayLink*)getDisplayLink {
153 CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:[[[
self class] alloc] init]
154 selector:@selector(onDisplayLink:)];
155 displayLink.paused = YES;
156 auto preferredFPS = displayLink.preferredFramesPerSecond;
163 if (preferredFPS != 0) {
167 return UIScreen.mainScreen.maximumFramesPerSecond;
170 - (void)onDisplayLink:(CADisplayLink*)link {
175 return [[NSBundle.mainBundle objectForInfoDictionaryKey:kCADisableMinimumFrameDurationOnPhoneKey]