Flutter macOS Embedder
FlutterDisplayLinkTest.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 
6 
7 #import <AppKit/AppKit.h>
8 #include <numeric>
9 
10 #include "flutter/fml/synchronization/waitable_event.h"
11 #include "flutter/testing/testing.h"
12 
14  void (^_block)(CFTimeInterval timestamp, CFTimeInterval targetTimestamp);
15 }
16 
17 - (instancetype)initWithBlock:(void (^)(CFTimeInterval timestamp,
18  CFTimeInterval targetTimestamp))block;
19 
20 @end
21 
22 @implementation TestDisplayLinkDelegate
23 - (instancetype)initWithBlock:(void (^__strong)(CFTimeInterval, CFTimeInterval))block {
24  if (self = [super init]) {
25  _block = block;
26  }
27  return self;
28 }
29 
30 - (void)onDisplayLink:(CFTimeInterval)timestamp targetTimestamp:(CFTimeInterval)targetTimestamp {
31  _block(timestamp, targetTimestamp);
32 }
33 
34 @end
35 
36 TEST(FlutterDisplayLinkTest, ViewAddedToWindowFirst) {
37  NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
38  styleMask:NSWindowStyleMaskTitled
39  backing:NSBackingStoreNonretained
40  defer:NO];
41  NSView* view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
42  [window setContentView:view];
43 
44  auto event = std::make_shared<fml::AutoResetWaitableEvent>();
45 
47  initWithBlock:^(CFTimeInterval timestamp, CFTimeInterval targetTimestamp) {
48  event->Signal();
49  }];
50 
52  displayLink.delegate = delegate;
53  displayLink.paused = NO;
54 
55  event->Wait();
56 
57  [displayLink invalidate];
58 }
59 
60 TEST(FlutterDisplayLinkTest, ViewAddedToWindowLater) {
61  NSView* view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
62 
63  auto event = std::make_shared<fml::AutoResetWaitableEvent>();
64 
66  initWithBlock:^(CFTimeInterval timestamp, CFTimeInterval targetTimestamp) {
67  event->Signal();
68  }];
69 
71  displayLink.delegate = delegate;
72  displayLink.paused = NO;
73 
74  NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
75  styleMask:NSWindowStyleMaskTitled
76  backing:NSBackingStoreNonretained
77  defer:NO];
78  [window setContentView:view];
79 
80  event->Wait();
81 
82  [displayLink invalidate];
83 }
84 
85 TEST(FlutterDisplayLinkTest, ViewRemovedFromWindow) {
86  NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
87  styleMask:NSWindowStyleMaskTitled
88  backing:NSBackingStoreNonretained
89  defer:NO];
90  NSView* view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
91  [window setContentView:view];
92 
93  auto event = std::make_shared<fml::AutoResetWaitableEvent>();
94 
96  initWithBlock:^(CFTimeInterval timestamp, CFTimeInterval targetTimestamp) {
97  event->Signal();
98  }];
99 
101  displayLink.delegate = delegate;
102  displayLink.paused = NO;
103 
104  event->Wait();
105  displayLink.paused = YES;
106 
107  event->Reset();
108 
109  displayLink.paused = NO;
110 
111  [window setContentView:nil];
112 
113  EXPECT_TRUE(event->WaitWithTimeout(fml::TimeDelta::FromMilliseconds(100)));
114  EXPECT_FALSE(event->IsSignaledForTest());
115 
116  [displayLink invalidate];
117 }
118 
119 TEST(FlutterDisplayLinkTest, WorkaroundForFB13482573) {
120  NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 100, 100)
121  styleMask:NSWindowStyleMaskTitled
122  backing:NSBackingStoreNonretained
123  defer:NO];
124  NSView* view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)];
125  [window setContentView:view];
126 
127  auto event = std::make_shared<fml::AutoResetWaitableEvent>();
128 
130  initWithBlock:^(CFTimeInterval timestamp, CFTimeInterval targetTimestamp) {
131  event->Signal();
132  }];
133 
135  displayLink.delegate = delegate;
136  displayLink.paused = NO;
137 
138  event->Wait();
139  displayLink.paused = YES;
140 
141  event->Reset();
142  [NSThread detachNewThreadWithBlock:^{
143  // Here pthread_self() will be same as pthread_self inside first invocation of
144  // display link callback, causing CVDisplayLinkStart to return error.
145  displayLink.paused = NO;
146  }];
147 
148  event->Wait();
149 
150  [displayLink invalidate];
151 }
152 
153 TEST(FlutterDisplayLinkTest, CVDisplayLinkInterval) {
154  CVDisplayLinkRef link;
155  CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &link);
156  __block CFTimeInterval last = 0;
157  auto intervals = std::make_shared<std::vector<CFTimeInterval>>();
158  auto event = std::make_shared<fml::AutoResetWaitableEvent>();
159  CVDisplayLinkSetOutputHandler(
160  link, ^(CVDisplayLinkRef displayLink, const CVTimeStamp* inNow,
161  const CVTimeStamp* inOutputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut) {
162  if (last != 0) {
163  intervals->push_back(CACurrentMediaTime() - last);
164  }
165  last = CACurrentMediaTime();
166  if (intervals->size() == 10) {
167  event->Signal();
168  }
169  return 0;
170  });
171 
172  CVDisplayLinkStart(link);
173  event->Wait();
174  CVDisplayLinkStop(link);
175  CVDisplayLinkRelease(link);
176  CFTimeInterval average = std::reduce(intervals->begin(), intervals->end()) / intervals->size();
177  CFTimeInterval max = *std::max_element(intervals->begin(), intervals->end());
178  CFTimeInterval min = *std::min_element(intervals->begin(), intervals->end());
179  NSLog(@"CVDisplayLink Interval: Average: %fs, Max: %fs, Min: %fs", average, max, min);
180 }