Flutter macOS Embedder
FlutterSurfaceManagerTest.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 <Cocoa/Cocoa.h>
6 #import <Metal/Metal.h>
7 
10 
11 #include "flutter/testing/testing.h"
12 #include "gtest/gtest.h"
13 
15 
16 @property(readwrite, nonatomic) CGSize presentedFrameSize;
17 - (nonnull instancetype)init;
18 
19 @end
20 
21 @implementation TestView
22 
23 - (instancetype)init {
24  self = [super initWithFrame:NSZeroRect];
25  if (self) {
26  [self setWantsLayer:YES];
27  self.layer.contentsScale = 2.0;
28  }
29  return self;
30 }
31 
32 - (void)onPresent:(CGSize)frameSize withBlock:(nonnull dispatch_block_t)block {
33  self.presentedFrameSize = frameSize;
34  block();
35 }
36 
37 @end
38 
39 namespace flutter::testing {
40 
42  id<MTLDevice> device = MTLCreateSystemDefaultDevice();
43  id<MTLCommandQueue> commandQueue = [device newCommandQueue];
44  CALayer* layer = reinterpret_cast<CALayer*>(testView.layer);
45  return [[FlutterSurfaceManager alloc] initWithDevice:device
46  commandQueue:commandQueue
47  layer:layer
48  delegate:testView];
49 }
50 
52  FlutterSurface* surface,
53  CGPoint offset = CGPointZero,
54  size_t index = 0,
55  const std::vector<FlutterRect>& paintRegion = {}) {
57  res.surface = surface;
58  res.offset = offset;
59  res.zIndex = index;
60  res.paintRegion = paintRegion;
61  return res;
62 }
63 
64 TEST(FlutterSurfaceManager, MetalTextureSizeMatchesSurfaceSize) {
65  TestView* testView = [[TestView alloc] init];
66  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
67 
68  // Get back buffer, lookup should work for borrowed surfaces util present.
69  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
70  auto texture = surface.asFlutterMetalTexture;
71  id<MTLTexture> metalTexture = (__bridge id)texture.texture;
72  EXPECT_EQ(metalTexture.width, 100ul);
73  EXPECT_EQ(metalTexture.height, 50ul);
74  texture.destruction_callback(texture.user_data);
75 }
76 
77 TEST(FlutterSurfaceManager, TestSurfaceLookupFromTexture) {
78  TestView* testView = [[TestView alloc] init];
79  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
80 
81  // Get back buffer, lookup should work for borrowed surfaces util present.
82  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
83 
84  // SurfaceManager should keep texture alive while borrowed.
85  auto texture = surface.asFlutterMetalTexture;
86  texture.destruction_callback(texture.user_data);
87 
88  FlutterMetalTexture dummyTexture{.texture_id = 1, .texture = nullptr, .user_data = nullptr};
89  auto surface1 = [FlutterSurface fromFlutterMetalTexture:&dummyTexture];
90  EXPECT_EQ(surface1, nil);
91 
92  auto surface2 = [FlutterSurface fromFlutterMetalTexture:&texture];
93  EXPECT_EQ(surface2, surface);
94 }
95 
96 TEST(FlutterSurfaceManager, BackBufferCacheDoesNotLeak) {
97  TestView* testView = [[TestView alloc] init];
98  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
99  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
100 
101  auto surface1 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
102  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface1) ] atTime:0 notify:nil];
103 
104  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
105 
106  auto surface2 = [surfaceManager surfaceForSize:CGSizeMake(110, 110)];
107  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface2) ] atTime:0 notify:nil];
108 
109  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
110 
111  auto surface3 = [surfaceManager surfaceForSize:CGSizeMake(120, 120)];
112  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface3) ] atTime:0 notify:nil];
113 
114  // Cache should be cleaned during present and only contain the last visible
115  // surface(s).
116  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
117  auto surfaceFromCache = [surfaceManager surfaceForSize:CGSizeMake(110, 110)];
118  EXPECT_EQ(surfaceFromCache, surface2);
119 
120  // Submit empty surfaces until the one in cache gets to age >= kSurfaceEvictionAge, in which case
121  // it should be removed.
122 
123  for (int i = 0; i < 30 /* kSurfaceEvictionAge */; ++i) {
124  [surfaceManager presentSurfaces:@[] atTime:0 notify:nil];
125  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
126  }
127 
128  [surfaceManager presentSurfaces:@[] atTime:0 notify:nil];
129  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
130 }
131 
132 TEST(FlutterSurfaceManager, SurfacesAreRecycled) {
133  TestView* testView = [[TestView alloc] init];
134  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
135 
136  EXPECT_EQ(surfaceManager.frontSurfaces.count, 0ul);
137 
138  // Get first surface and present it.
139 
140  auto surface1 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
141  EXPECT_TRUE(CGSizeEqualToSize(surface1.size, CGSizeMake(100, 100)));
142 
143  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
144  EXPECT_EQ(surfaceManager.frontSurfaces.count, 0ul);
145 
146  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface1) ] atTime:0 notify:nil];
147 
148  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
149  EXPECT_EQ(surfaceManager.frontSurfaces.count, 1ul);
150  EXPECT_EQ(testView.layer.sublayers.count, 1ul);
151 
152  // Get second surface and present it.
153 
154  auto surface2 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
155  EXPECT_TRUE(CGSizeEqualToSize(surface2.size, CGSizeMake(100, 100)));
156 
157  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
158 
159  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface2) ] atTime:0 notify:nil];
160 
161  // Check that current front surface returns to cache.
162  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
163  EXPECT_EQ(surfaceManager.frontSurfaces.count, 1ul);
164  EXPECT_EQ(testView.layer.sublayers.count, 1ull);
165 
166  // Check that surface is properly reused.
167  auto surface3 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
168  EXPECT_EQ(surface3, surface1);
169 }
170 
171 TEST(FlutterSurfaceManager, BackingStoreCacheSurfaceStuckInUse) {
172  TestView* testView = [[TestView alloc] init];
173  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
174 
175  auto surface1 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
176 
177  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface1) ] atTime:0 notify:nil];
178  // Pretend that compositor is holding on to the surface. The surface will be kept
179  // in cache until the age of kSurfaceEvictionAge is reached, and then evicted.
180  surface1.isInUseOverride = YES;
181 
182  auto surface2 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
183  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface2) ] atTime:0 notify:nil];
184  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
185 
186  for (int i = 0; i < 30 /* kSurfaceEvictionAge */ - 1; ++i) {
187  auto surface3 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
188  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface3) ] atTime:0 notify:nil];
189  EXPECT_EQ(surfaceManager.backBufferCache.count, 2ul);
190  }
191 
192  auto surface4 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
193  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface4) ] atTime:0 notify:nil];
194  // Surface in use should bet old enough at this point to be evicted.
195  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
196 }
197 
198 inline bool operator==(const CGRect& lhs, const CGRect& rhs) {
199  return CGRectEqualToRect(lhs, rhs);
200 }
201 
202 TEST(FlutterSurfaceManager, LayerManagement) {
203  TestView* testView = [[TestView alloc] init];
204  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
205 
206  EXPECT_EQ(testView.layer.sublayers.count, 0ul);
207 
208  auto surface1_1 = [surfaceManager surfaceForSize:CGSizeMake(50, 30)];
209  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface1_1, CGPointMake(20, 10)) ]
210  atTime:0
211  notify:nil];
212 
213  EXPECT_EQ(testView.layer.sublayers.count, 1ul);
214  EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(70, 40)));
215 
216  auto surface2_1 = [surfaceManager surfaceForSize:CGSizeMake(50, 30)];
217  auto surface2_2 = [surfaceManager surfaceForSize:CGSizeMake(20, 20)];
218  [surfaceManager presentSurfaces:@[
219  CreatePresentInfo(surface2_1, CGPointMake(20, 10), 1),
220  CreatePresentInfo(surface2_2, CGPointMake(40, 50), 2,
221  {
222  FlutterRect{0, 0, 20, 20},
223  FlutterRect{40, 0, 60, 20},
224  })
225  ]
226  atTime:0
227  notify:nil];
228 
229  EXPECT_EQ(testView.layer.sublayers.count, 2ul);
230  EXPECT_EQ(testView.layer.sublayers[0].zPosition, 1.0);
231  EXPECT_EQ(testView.layer.sublayers[1].zPosition, 2.0);
232  CALayer* firstOverlaySublayer;
233  {
234  NSArray<CALayer*>* sublayers = testView.layer.sublayers[1].sublayers;
235  EXPECT_EQ(sublayers.count, 2ul);
236  EXPECT_TRUE(CGRectEqualToRect(sublayers[0].frame, CGRectMake(0, 0, 10, 10)));
237  EXPECT_TRUE(CGRectEqualToRect(sublayers[1].frame, CGRectMake(20, 0, 10, 10)));
238  EXPECT_TRUE(CGRectEqualToRect(sublayers[0].contentsRect, CGRectMake(0, 0, 1, 1)));
239  EXPECT_TRUE(CGRectEqualToRect(sublayers[1].contentsRect, CGRectMake(2, 0, 1, 1)));
240  EXPECT_EQ(sublayers[0].contents, sublayers[1].contents);
241  firstOverlaySublayer = sublayers[0];
242  }
243  EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(70, 70)));
244 
245  // Check second overlay sublayer is removed while first is reused and updated
246  [surfaceManager presentSurfaces:@[
247  CreatePresentInfo(surface2_1, CGPointMake(20, 10), 1),
248  CreatePresentInfo(surface2_2, CGPointMake(40, 50), 2,
249  {
250  FlutterRect{0, 10, 20, 20},
251  })
252  ]
253  atTime:0
254  notify:nil];
255  EXPECT_EQ(testView.layer.sublayers.count, 2ul);
256  {
257  NSArray<CALayer*>* sublayers = testView.layer.sublayers[1].sublayers;
258  EXPECT_EQ(sublayers.count, 1ul);
259  EXPECT_EQ(sublayers[0], firstOverlaySublayer);
260  EXPECT_TRUE(CGRectEqualToRect(sublayers[0].frame, CGRectMake(0, 5, 10, 5)));
261  }
262 
263  // Check that second overlay sublayer is added back while first is reused and updated
264  [surfaceManager presentSurfaces:@[
265  CreatePresentInfo(surface2_1, CGPointMake(20, 10), 1),
266  CreatePresentInfo(surface2_2, CGPointMake(40, 50), 2,
267  {
268  FlutterRect{0, 0, 20, 20},
269  FlutterRect{40, 0, 60, 20},
270  })
271  ]
272  atTime:0
273  notify:nil];
274 
275  EXPECT_EQ(testView.layer.sublayers.count, 2ul);
276  {
277  NSArray<CALayer*>* sublayers = testView.layer.sublayers[1].sublayers;
278  EXPECT_EQ(sublayers.count, 2ul);
279  EXPECT_EQ(sublayers[0], firstOverlaySublayer);
280  EXPECT_TRUE(CGRectEqualToRect(sublayers[0].frame, CGRectMake(0, 0, 10, 10)));
281  EXPECT_TRUE(CGRectEqualToRect(sublayers[1].frame, CGRectMake(20, 0, 10, 10)));
282  EXPECT_EQ(sublayers[0].contents, sublayers[1].contents);
283  }
284 
285  auto surface3_1 = [surfaceManager surfaceForSize:CGSizeMake(50, 30)];
286  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface3_1, CGPointMake(20, 10)) ]
287  atTime:0
288  notify:nil];
289 
290  EXPECT_EQ(testView.layer.sublayers.count, 1ul);
291  EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(70, 40)));
292 
293  // Check removal of all surfaces.
294  [surfaceManager presentSurfaces:@[] atTime:0 notify:nil];
295  EXPECT_EQ(testView.layer.sublayers.count, 0ul);
296  EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(0, 0)));
297 }
298 
299 } // namespace flutter::testing
TestView
Definition: FlutterSurfaceManagerTest.mm:14
+[FlutterSurface fromFlutterMetalTexture:]
nullable FlutterSurface * fromFlutterMetalTexture:(nonnull const FlutterMetalTexture *texture)
-[FlutterSurface asFlutterMetalTexture]
FlutterMetalTexture asFlutterMetalTexture()
Definition: FlutterSurface.mm:59
flutter::testing::CreatePresentInfo
static FlutterSurfacePresentInfo * CreatePresentInfo(FlutterSurface *surface, CGPoint offset=CGPointZero, size_t index=0, const std::vector< FlutterRect > &paintRegion={})
Definition: FlutterSurfaceManagerTest.mm:51
FlutterSurfaceManager.h
-[FlutterBackBufferCache count]
NSUInteger count()
Definition: FlutterSurfaceManager.mm:352
FlutterSurfaceManager
Definition: FlutterSurfaceManager.h:44
flutter::testing
Definition: AccessibilityBridgeMacTest.mm:13
FlutterSurface.h
FlutterSurface
Definition: FlutterSurface.h:16
FlutterSurfaceManagerDelegate-p
Definition: FlutterSurfaceManager.h:27
FlutterSurfacePresentInfo::surface
FlutterSurface * surface
Definition: FlutterSurfaceManager.h:20
FlutterSurfaceManager::frontSurfaces
NSArray< FlutterSurface * > * frontSurfaces
Definition: FlutterSurfaceManager.h:106
FlutterSurfacePresentInfo::offset
CGPoint offset
Definition: FlutterSurfaceManager.h:21
FlutterSurfaceManager::backBufferCache
FlutterBackBufferCache * backBufferCache
Definition: FlutterSurfaceManager.h:105
flutter::testing::operator==
bool operator==(const CGRect &lhs, const CGRect &rhs)
Definition: FlutterSurfaceManagerTest.mm:198
FlutterSurfacePresentInfo
Definition: FlutterSurfaceManager.h:18
-[FlutterSurfaceManager presentSurfaces:atTime:notify:]
void presentSurfaces:atTime:notify:(nonnull NSArray< FlutterSurfacePresentInfo * > *surfaces,[atTime] CFTimeInterval presentationTime,[notify] nullable dispatch_block_t notify)
FlutterSurfacePresentInfo::zIndex
size_t zIndex
Definition: FlutterSurfaceManager.h:22
-[FlutterSurfaceManager surfaceForSize:]
nonnull FlutterSurface * surfaceForSize:(CGSize size)
Definition: FlutterSurfaceManager.mm:133
TestView::presentedFrameSize
CGSize presentedFrameSize
Definition: FlutterSurfaceManagerTest.mm:16
flutter::testing::CreateSurfaceManager
static FlutterSurfaceManager * CreateSurfaceManager(TestView *testView)
Definition: FlutterSurfaceManagerTest.mm:41
flutter::testing::TEST
TEST(FlutterSurfaceManager, LayerManagement)
Definition: FlutterSurfaceManagerTest.mm:202
FlutterSurfacePresentInfo::paintRegion
std::vector< FlutterRect > paintRegion
Definition: FlutterSurfaceManager.h:23
-[TestView init]
nonnull instancetype init()
Definition: FlutterSurfaceManagerTest.mm:23