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