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  [waiter waitForVSync:kWarmUpBaton];
144 
145  // Reference vsync to setup phase.
146  CFTimeInterval now = CACurrentMediaTime();
147  // CVDisplayLink callback is called one and a half frame before the target.
148  [displayLink tickWithTimestamp:now + 0.5 * displayLink.nominalOutputRefreshPeriod
149  targetTimestamp:now + 2 * displayLink.nominalOutputRefreshPeriod];
150  EXPECT_EQ(displayLink.paused, YES);
151 
152  [waiter waitForVSync:1];
153  CFRunLoopRun();
154 
155  [waiter waitForVSync:2];
156  [displayLink tickWithTimestamp:now + 1.5 * displayLink.nominalOutputRefreshPeriod
157  targetTimestamp:now + 3 * displayLink.nominalOutputRefreshPeriod];
158  CFRunLoopRun();
159 
160  [waiter waitForVSync:3];
161  [displayLink tickWithTimestamp:now + 2.5 * displayLink.nominalOutputRefreshPeriod
162  targetTimestamp:now + 4 * displayLink.nominalOutputRefreshPeriod];
163  CFRunLoopRun();
164 
165  EXPECT_FALSE(displayLink.paused);
166  // Vsync without baton should pause the display link.
167  [displayLink tickWithTimestamp:now + 3.5 * displayLink.nominalOutputRefreshPeriod
168  targetTimestamp:now + 5 * displayLink.nominalOutputRefreshPeriod];
169  // Make sure to run the timer scheduled in display link callback.
170  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.02, NO);
171  ASSERT_TRUE(displayLink.paused);
172 
173  EXPECT_EQ(entries.size(), size_t(4));
174 
175  // Warm up frame should be presented as soon as possible.
176  EXPECT_TRUE(fabs(entries[0].timestamp - now) < 0.001);
177  EXPECT_TRUE(fabs(entries[0].targetTimestamp - now) < 0.001);
178  EXPECT_EQ(entries[0].baton, kWarmUpBaton);
179 
180  EXPECT_DOUBLE_EQ(entries[1].timestamp, now + displayLink.nominalOutputRefreshPeriod);
181  EXPECT_DOUBLE_EQ(entries[1].targetTimestamp, now + 2 * displayLink.nominalOutputRefreshPeriod);
182  EXPECT_EQ(entries[1].baton, size_t(1));
183  EXPECT_DOUBLE_EQ(entries[2].timestamp, now + 2 * displayLink.nominalOutputRefreshPeriod);
184  EXPECT_DOUBLE_EQ(entries[2].targetTimestamp, now + 3 * displayLink.nominalOutputRefreshPeriod);
185  EXPECT_EQ(entries[2].baton, size_t(2));
186  EXPECT_DOUBLE_EQ(entries[3].timestamp, now + 3 * displayLink.nominalOutputRefreshPeriod);
187  EXPECT_DOUBLE_EQ(entries[3].targetTimestamp, now + 4 * displayLink.nominalOutputRefreshPeriod);
188  EXPECT_EQ(entries[3].baton, size_t(3));
189 }
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