| Flutter macOS Embedder |

FlutterVSyncWaiterTest.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/macos/framework/Source/FlutterDisplayLink.h"
6 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterVSyncWaiter.h"
7
8 #import "flutter/testing/testing.h"
9
10 @interface TestDisplayLink : FlutterDisplayLink {
11 }
12
13 @property(nonatomic) CFTimeInterval nominalOutputRefreshPeriod;
14
15 @end
16
17 @implementation TestDisplayLink
18
19 @synthesize nominalOutputRefreshPeriod = _nominalOutputRefreshPeriod;
20 @synthesize delegate = _delegate;
21 @synthesize paused = _paused;
22
23 - (instancetype)init {
24 if (self = [super init]) {
25 _paused = YES;
26 }
27 return self;
28 }
29
30 - (void)tickWithTimestamp:(CFTimeInterval)timestamp
31 targetTimestamp:(CFTimeInterval)targetTimestamp {
32 [_delegate onDisplayLink:timestamp targetTimestamp:targetTimestamp];
33 }
34
35 - (void)invalidate {
36 }
37
38 @end
39
40 TEST(FlutterVSyncWaiterTest, RequestsInitialVSync) {
41 TestDisplayLink* displayLink = [[TestDisplayLink alloc] init];
42 EXPECT_TRUE(displayLink.paused);
43 // When created waiter requests a reference vsync to determine vsync phase.
44 FlutterVSyncWaiter* waiter = [[FlutterVSyncWaiter alloc]
45 initWithDisplayLink:displayLink
46 block:^(CFTimeInterval timestamp, CFTimeInterval targetTimestamp,
47 uintptr_t baton){
48 }];
49 (void)waiter;
50 EXPECT_FALSE(displayLink.paused);
51 [displayLink tickWithTimestamp:CACurrentMediaTime()
52 targetTimestamp:CACurrentMediaTime() + 1.0 / 60.0];
53 EXPECT_TRUE(displayLink.paused);
54 }
55
56 static void BusyWait(CFTimeInterval duration) {
57 CFTimeInterval start = CACurrentMediaTime();
58 while (CACurrentMediaTime() < start + duration) {
59 }
60 }
61
62 // See FlutterVSyncWaiter.mm for the original definition.
63 static const CFTimeInterval kTimerLatencyCompensation = 0.001;
64
65 TEST(FlutterVSyncWaiterTest, FirstVSyncIsSynthesized) {
66 TestDisplayLink* displayLink = [[TestDisplayLink alloc] init];
67 displayLink.nominalOutputRefreshPeriod = 1.0 / 60.0;
68
69 auto test = [&](CFTimeInterval waitDuration, CFTimeInterval expectedDelay) {
70 __block CFTimeInterval timestamp = 0;
71 __block CFTimeInterval targetTimestamp = 0;
72 __block size_t baton = 0;
73 const uintptr_t kWarmUpBaton = 0xFFFFFFFF;
74 FlutterVSyncWaiter* waiter = [[FlutterVSyncWaiter alloc]
75 initWithDisplayLink:displayLink
76 block:^(CFTimeInterval _timestamp, CFTimeInterval _targetTimestamp,
77 uintptr_t _baton) {
78 if (_baton == kWarmUpBaton) {
79 return;
80 }
81 timestamp = _timestamp;
82 targetTimestamp = _targetTimestamp;
83 baton = _baton;
84 EXPECT_TRUE(CACurrentMediaTime() >= _timestamp - kTimerLatencyCompensation);
85 CFRunLoopStop(CFRunLoopGetCurrent());
86 }];
87
88 [waiter waitForVSync:kWarmUpBaton];
89
90 // Reference vsync to setup phase.
91 CFTimeInterval now = CACurrentMediaTime();
92 // CVDisplayLink callback is called one and a half frame before the target.
93 [displayLink tickWithTimestamp:now + 0.5 * displayLink.nominalOutputRefreshPeriod
94 targetTimestamp:now + 2 * displayLink.nominalOutputRefreshPeriod];
95 EXPECT_EQ(displayLink.paused, YES);
96 // Vsync was not requested yet, block should not have been called.
97 EXPECT_EQ(timestamp, 0);
98
99 BusyWait(waitDuration);
100
101 // Synthesized vsync should come in 1/60th of a second after the first.
102 CFTimeInterval expectedTimestamp = now + expectedDelay;
103 [waiter waitForVSync:1];
104
105 CFRunLoopRun();
106
107 EXPECT_DOUBLE_EQ(timestamp, expectedTimestamp);
108 EXPECT_DOUBLE_EQ(targetTimestamp, expectedTimestamp + displayLink.nominalOutputRefreshPeriod);
109 EXPECT_EQ(baton, size_t(1));
110 };
111
112 // First argument if the wait duration after reference vsync.
113 // Second argument is the expected delay between reference vsync and synthesized vsync.
114 test(0.005, displayLink.nominalOutputRefreshPeriod);
115 test(0.025, 2 * displayLink.nominalOutputRefreshPeriod);
116 test(0.040, 3 * displayLink.nominalOutputRefreshPeriod);
117 }
118
119 TEST(FlutterVSyncWaiterTest, VSyncWorks) {
120 TestDisplayLink* displayLink = [[TestDisplayLink alloc] init];
121 displayLink.nominalOutputRefreshPeriod = 1.0 / 60.0;
122 const uintptr_t kWarmUpBaton = 0xFFFFFFFF;
123
124 struct Entry {
125 CFTimeInterval timestamp;
126 CFTimeInterval targetTimestamp;
127 size_t baton;
128 };
129 __block std::vector<Entry> entries;
130
131 FlutterVSyncWaiter* waiter = [[FlutterVSyncWaiter alloc]
132 initWithDisplayLink:displayLink
133 block:^(CFTimeInterval timestamp, CFTimeInterval targetTimestamp,
134 uintptr_t baton) {
135 entries.push_back({timestamp, targetTimestamp, baton});
136 if (baton == kWarmUpBaton) {
137 return;
138 }
139 EXPECT_TRUE(CACurrentMediaTime() >= timestamp - kTimerLatencyCompensation);
140 CFRunLoopStop(CFRunLoopGetCurrent());
141 }];
142
143 __block CFTimeInterval expectedStartUntil;
144 // Warm up tick is scheduled immediately in a scheduled block. Schedule another
145 // block here to determine the maximum time when the warm up tick should be
146 // scheduled.
147 [waiter waitForVSync:kWarmUpBaton];
148 [[NSRunLoop currentRunLoop] performBlock:^{
149 expectedStartUntil = CACurrentMediaTime();
150 }];
151
152 // Reference vsync to setup phase.
153 CFTimeInterval now = CACurrentMediaTime();
154 // CVDisplayLink callback is called one and a half frame before the target.
155 [displayLink tickWithTimestamp:now + 0.5 * displayLink.nominalOutputRefreshPeriod
156 targetTimestamp:now + 2 * displayLink.nominalOutputRefreshPeriod];
157 EXPECT_EQ(displayLink.paused, YES);
158
159 [waiter waitForVSync:1];
160 CFRunLoopRun();
161
162 [waiter waitForVSync:2];
163 [displayLink tickWithTimestamp:now + 1.5 * displayLink.nominalOutputRefreshPeriod
164 targetTimestamp:now + 3 * displayLink.nominalOutputRefreshPeriod];
165 CFRunLoopRun();
166
167 [waiter waitForVSync:3];
168 [displayLink tickWithTimestamp:now + 2.5 * displayLink.nominalOutputRefreshPeriod
169 targetTimestamp:now + 4 * displayLink.nominalOutputRefreshPeriod];
170 CFRunLoopRun();
171
172 EXPECT_FALSE(displayLink.paused);
173 // Vsync without baton should pause the display link.
174 [displayLink tickWithTimestamp:now + 3.5 * displayLink.nominalOutputRefreshPeriod
175 targetTimestamp:now + 5 * displayLink.nominalOutputRefreshPeriod];
176
177 CFTimeInterval start = CACurrentMediaTime();
178 while (!displayLink.paused) {
179 // Make sure to run the timer scheduled in display link callback.
180 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.02, NO);
181 if (CACurrentMediaTime() - start > 1.0) {
182 break;
183 }
184 }
185 ASSERT_TRUE(displayLink.paused);
186
187 EXPECT_EQ(entries.size(), size_t(4));
188
189 // Warm up frame should be presented as soon as possible.
190 EXPECT_TRUE(entries[0].timestamp <= expectedStartUntil);
191 EXPECT_TRUE(entries[0].targetTimestamp <= expectedStartUntil);
192 EXPECT_EQ(entries[0].baton, kWarmUpBaton);
193
194 EXPECT_DOUBLE_EQ(entries[1].timestamp, now + displayLink.nominalOutputRefreshPeriod);
195 EXPECT_DOUBLE_EQ(entries[1].targetTimestamp, now + 2 * displayLink.nominalOutputRefreshPeriod);
196 EXPECT_EQ(entries[1].baton, size_t(1));
197 EXPECT_DOUBLE_EQ(entries[2].timestamp, now + 2 * displayLink.nominalOutputRefreshPeriod);
198 EXPECT_DOUBLE_EQ(entries[2].targetTimestamp, now + 3 * displayLink.nominalOutputRefreshPeriod);
199 EXPECT_EQ(entries[2].baton, size_t(2));
200 EXPECT_DOUBLE_EQ(entries[3].timestamp, now + 3 * displayLink.nominalOutputRefreshPeriod);
201 EXPECT_DOUBLE_EQ(entries[3].targetTimestamp, now + 4 * displayLink.nominalOutputRefreshPeriod);
202 EXPECT_EQ(entries[3].baton, size_t(3));
203 }
TEST
TEST(FlutterVSyncWaiterTest, RequestsInitialVSync)
Definition: FlutterVSyncWaiterTest.mm:40
TestDisplayLink::nominalOutputRefreshPeriod
CFTimeInterval nominalOutputRefreshPeriod
Definition: FlutterVSyncWaiterTest.mm:13
FlutterVSyncWaiter.h
FlutterVSyncWaiter
Definition: FlutterVSyncWaiter.h:8
FlutterDisplayLink
Definition: FlutterDisplayLink.h:15
-[FlutterVSyncWaiter waitForVSync:]
void waitForVSync:(uintptr_t baton)
Definition: FlutterVSyncWaiter.mm:108
kTimerLatencyCompensation
static const CFTimeInterval kTimerLatencyCompensation
Definition: FlutterVSyncWaiterTest.mm:63
FlutterDisplayLink::paused
BOOL paused
Pauses and resumes the display link. May be called from any thread.
Definition: FlutterDisplayLink.h:28
-[FlutterDisplayLink invalidate]
void invalidate()
Invalidates the display link. Must be called on the main thread.
Definition: FlutterDisplayLink.mm:354
FlutterDisplayLink.h
FlutterDisplayLink::delegate
id< FlutterDisplayLinkDelegate > delegate
Definition: FlutterDisplayLink.h:25
TestDisplayLink
Definition: FlutterVSyncWaiterTest.mm:10
BusyWait
static void BusyWait(CFTimeInterval duration)
Definition: FlutterVSyncWaiterTest.mm:56