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 
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
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:63
BusyWait
static void BusyWait(CFTimeInterval duration)
Definition: FlutterVSyncWaiterTest.mm:56