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 
7 
8 #import "flutter/testing/testing.h"
9 
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 }
TEST
TEST(FlutterVSyncWaiterTest, RequestsInitialVSync)
Definition: FlutterVSyncWaiterTest.mm:38
FlutterVSyncWaiter.h
FlutterVSyncWaiter
Definition: FlutterVSyncWaiter.h:8
-[FlutterVSyncWaiter waitForVSync:]
void waitForVSync:(uintptr_t baton)
Definition: FlutterVSyncWaiter.mm:108
kTimerLatencyCompensation
static const CFTimeInterval kTimerLatencyCompensation
Definition: FlutterVSyncWaiterTest.mm:61
BusyWait
static void BusyWait(CFTimeInterval duration)
Definition: FlutterVSyncWaiterTest.mm:54