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
33  withBlock:(nonnull dispatch_block_t)block
34  delay:(NSTimeInterval)delay {
35  self.presentedFrameSize = frameSize;
36  block();
37 }
38 
39 @end
40 
41 namespace flutter::testing {
42 
43 static FlutterSurfaceManager* CreateSurfaceManager(TestView* testView, BOOL enableWideGamut = NO) {
44  id<MTLDevice> device = MTLCreateSystemDefaultDevice();
45  id<MTLCommandQueue> commandQueue = [device newCommandQueue];
46  CALayer* layer = reinterpret_cast<CALayer*>(testView.layer);
47  return [[FlutterSurfaceManager alloc] initWithDevice:device
48  commandQueue:commandQueue
49  layer:layer
50  delegate:testView
51  wideGamut:enableWideGamut];
52 }
53 
55  FlutterSurface* surface,
56  CGPoint offset = CGPointZero,
57  size_t index = 0,
58  const std::vector<FlutterRect>& paintRegion = {}) {
60  res.surface = surface;
61  res.offset = offset;
62  res.zIndex = index;
63  res.paintRegion = paintRegion;
64  return res;
65 }
66 
67 TEST(FlutterSurfaceManager, MetalTextureSizeMatchesSurfaceSize) {
68  TestView* testView = [[TestView alloc] init];
69  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
70 
71  // Get back buffer, lookup should work for borrowed surfaces util present.
72  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
73  auto texture = surface.asFlutterMetalTexture;
74  id<MTLTexture> metalTexture = (__bridge id)texture.texture;
75  EXPECT_EQ(metalTexture.width, 100ul);
76  EXPECT_EQ(metalTexture.height, 50ul);
77  texture.destruction_callback(texture.user_data);
78 }
79 
80 TEST(FlutterSurfaceManager, TestSurfaceLookupFromTexture) {
81  TestView* testView = [[TestView alloc] init];
82  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
83 
84  // Get back buffer, lookup should work for borrowed surfaces util present.
85  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
86 
87  // SurfaceManager should keep texture alive while borrowed.
88  auto texture = surface.asFlutterMetalTexture;
89  texture.destruction_callback(texture.user_data);
90 
91  FlutterMetalTexture dummyTexture{.texture_id = 1, .texture = nullptr, .user_data = nullptr};
92  auto surface1 = [FlutterSurface fromFlutterMetalTexture:&dummyTexture];
93  EXPECT_EQ(surface1, nil);
94 
95  auto surface2 = [FlutterSurface fromFlutterMetalTexture:&texture];
96  EXPECT_EQ(surface2, surface);
97 }
98 
99 TEST(FlutterSurfaceManager, BackBufferCacheDoesNotLeak) {
100  TestView* testView = [[TestView alloc] init];
101  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
102  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
103 
104  auto surface1 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
105  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface1) ] atTime:0 notify:nil];
106 
107  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
108 
109  auto surface2 = [surfaceManager surfaceForSize:CGSizeMake(110, 110)];
110  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface2) ] atTime:0 notify:nil];
111 
112  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
113 
114  auto surface3 = [surfaceManager surfaceForSize:CGSizeMake(120, 120)];
115  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface3) ] atTime:0 notify:nil];
116 
117  // Cache should be cleaned during present and only contain the last visible
118  // surface(s).
119  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
120  auto surfaceFromCache = [surfaceManager surfaceForSize:CGSizeMake(110, 110)];
121  EXPECT_EQ(surfaceFromCache, surface2);
122 
123  // Submit empty surfaces until the one in cache gets to age >= kSurfaceEvictionAge, in which case
124  // it should be removed.
125 
126  for (int i = 0; i < 30 /* kSurfaceEvictionAge */; ++i) {
127  [surfaceManager presentSurfaces:@[] atTime:0 notify:nil];
128  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
129  }
130 
131  [surfaceManager presentSurfaces:@[] atTime:0 notify:nil];
132  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
133 }
134 
135 TEST(FlutterSurfaceManager, SurfacesAreRecycled) {
136  TestView* testView = [[TestView alloc] init];
137  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
138 
139  EXPECT_EQ(surfaceManager.frontSurfaces.count, 0ul);
140 
141  // Get first surface and present it.
142 
143  auto surface1 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
144  EXPECT_TRUE(CGSizeEqualToSize(surface1.size, CGSizeMake(100, 100)));
145 
146  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
147  EXPECT_EQ(surfaceManager.frontSurfaces.count, 0ul);
148 
149  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface1) ] atTime:0 notify:nil];
150 
151  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
152  EXPECT_EQ(surfaceManager.frontSurfaces.count, 1ul);
153  EXPECT_EQ(testView.layer.sublayers.count, 1ul);
154 
155  // Get second surface and present it.
156 
157  auto surface2 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
158  EXPECT_TRUE(CGSizeEqualToSize(surface2.size, CGSizeMake(100, 100)));
159 
160  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
161 
162  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface2) ] atTime:0 notify:nil];
163 
164  // Check that current front surface returns to cache.
165  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
166  EXPECT_EQ(surfaceManager.frontSurfaces.count, 1ul);
167  EXPECT_EQ(testView.layer.sublayers.count, 1ull);
168 
169  // Check that surface is properly reused.
170  auto surface3 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
171  EXPECT_EQ(surface3, surface1);
172 }
173 
174 TEST(FlutterSurfaceManager, BackingStoreCacheSurfaceStuckInUse) {
175  TestView* testView = [[TestView alloc] init];
176  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
177 
178  auto surface1 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
179 
180  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface1) ] atTime:0 notify:nil];
181  // Pretend that compositor is holding on to the surface. The surface will be kept
182  // in cache until the age of kSurfaceEvictionAge is reached, and then evicted.
183  surface1.isInUseOverride = YES;
184 
185  auto surface2 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
186  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface2) ] atTime:0 notify:nil];
187  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
188 
189  for (int i = 0; i < 30 /* kSurfaceEvictionAge */ - 1; ++i) {
190  auto surface3 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
191  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface3) ] atTime:0 notify:nil];
192  EXPECT_EQ(surfaceManager.backBufferCache.count, 2ul);
193  }
194 
195  auto surface4 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
196  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface4) ] atTime:0 notify:nil];
197  // Surface in use should bet old enough at this point to be evicted.
198  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
199 }
200 
201 inline bool operator==(const CGRect& lhs, const CGRect& rhs) {
202  return CGRectEqualToRect(lhs, rhs);
203 }
204 
205 TEST(FlutterSurfaceManager, LayerManagement) {
206  TestView* testView = [[TestView alloc] init];
207  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView);
208 
209  EXPECT_EQ(testView.layer.sublayers.count, 0ul);
210 
211  auto surface1_1 = [surfaceManager surfaceForSize:CGSizeMake(50, 30)];
212  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface1_1, CGPointMake(20, 10)) ]
213  atTime:0
214  notify:nil];
215 
216  EXPECT_EQ(testView.layer.sublayers.count, 1ul);
217  EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(70, 40)));
218 
219  auto surface2_1 = [surfaceManager surfaceForSize:CGSizeMake(50, 30)];
220  auto surface2_2 = [surfaceManager surfaceForSize:CGSizeMake(20, 20)];
221  [surfaceManager presentSurfaces:@[
222  CreatePresentInfo(surface2_1, CGPointMake(20, 10), 1),
223  CreatePresentInfo(surface2_2, CGPointMake(40, 50), 2,
224  {
225  FlutterRect{0, 0, 20, 20},
226  FlutterRect{40, 0, 60, 20},
227  })
228  ]
229  atTime:0
230  notify:nil];
231 
232  EXPECT_EQ(testView.layer.sublayers.count, 2ul);
233  EXPECT_EQ(testView.layer.sublayers[0].zPosition, 1.0);
234  EXPECT_EQ(testView.layer.sublayers[1].zPosition, 2.0);
235  CALayer* firstOverlaySublayer;
236  {
237  NSArray<CALayer*>* sublayers = testView.layer.sublayers[1].sublayers;
238  EXPECT_EQ(sublayers.count, 2ul);
239  EXPECT_TRUE(CGRectEqualToRect(sublayers[0].frame, CGRectMake(0, 0, 10, 10)));
240  EXPECT_TRUE(CGRectEqualToRect(sublayers[1].frame, CGRectMake(20, 0, 10, 10)));
241  EXPECT_TRUE(CGRectEqualToRect(sublayers[0].contentsRect, CGRectMake(0, 0, 1, 1)));
242  EXPECT_TRUE(CGRectEqualToRect(sublayers[1].contentsRect, CGRectMake(2, 0, 1, 1)));
243  EXPECT_EQ(sublayers[0].contents, sublayers[1].contents);
244  firstOverlaySublayer = sublayers[0];
245  }
246  EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(70, 70)));
247 
248  // Check second overlay sublayer is removed while first is reused and updated
249  [surfaceManager presentSurfaces:@[
250  CreatePresentInfo(surface2_1, CGPointMake(20, 10), 1),
251  CreatePresentInfo(surface2_2, CGPointMake(40, 50), 2,
252  {
253  FlutterRect{0, 10, 20, 20},
254  })
255  ]
256  atTime:0
257  notify:nil];
258  EXPECT_EQ(testView.layer.sublayers.count, 2ul);
259  {
260  NSArray<CALayer*>* sublayers = testView.layer.sublayers[1].sublayers;
261  EXPECT_EQ(sublayers.count, 1ul);
262  EXPECT_EQ(sublayers[0], firstOverlaySublayer);
263  EXPECT_TRUE(CGRectEqualToRect(sublayers[0].frame, CGRectMake(0, 5, 10, 5)));
264  }
265 
266  // Check that second overlay sublayer is added back while first is reused and updated
267  [surfaceManager presentSurfaces:@[
268  CreatePresentInfo(surface2_1, CGPointMake(20, 10), 1),
269  CreatePresentInfo(surface2_2, CGPointMake(40, 50), 2,
270  {
271  FlutterRect{0, 0, 20, 20},
272  FlutterRect{40, 0, 60, 20},
273  })
274  ]
275  atTime:0
276  notify:nil];
277 
278  EXPECT_EQ(testView.layer.sublayers.count, 2ul);
279  {
280  NSArray<CALayer*>* sublayers = testView.layer.sublayers[1].sublayers;
281  EXPECT_EQ(sublayers.count, 2ul);
282  EXPECT_EQ(sublayers[0], firstOverlaySublayer);
283  EXPECT_TRUE(CGRectEqualToRect(sublayers[0].frame, CGRectMake(0, 0, 10, 10)));
284  EXPECT_TRUE(CGRectEqualToRect(sublayers[1].frame, CGRectMake(20, 0, 10, 10)));
285  EXPECT_EQ(sublayers[0].contents, sublayers[1].contents);
286  }
287 
288  auto surface3_1 = [surfaceManager surfaceForSize:CGSizeMake(50, 30)];
289  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface3_1, CGPointMake(20, 10)) ]
290  atTime:0
291  notify:nil];
292 
293  EXPECT_EQ(testView.layer.sublayers.count, 1ul);
294  EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(70, 40)));
295 
296  // Check removal of all surfaces.
297  [surfaceManager presentSurfaces:@[] atTime:0 notify:nil];
298  EXPECT_EQ(testView.layer.sublayers.count, 0ul);
299  EXPECT_TRUE(CGSizeEqualToSize(testView.presentedFrameSize, CGSizeMake(0, 0)));
300 }
301 
302 TEST(FlutterSurfaceManager, WideGamutSurfaceHasCorrectPixelFormat) {
303  TestView* testView = [[TestView alloc] init];
304  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/YES);
305 
306  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
307  auto texture = surface.asFlutterMetalTexture;
308  id<MTLTexture> metalTexture = (__bridge id)texture.texture;
309  EXPECT_EQ(metalTexture.pixelFormat, MTLPixelFormatBGRA10_XR);
310  texture.destruction_callback(texture.user_data);
311 }
312 
313 TEST(FlutterSurfaceManager, StandardGamutSurfaceHasCorrectPixelFormat) {
314  TestView* testView = [[TestView alloc] init];
315  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/NO);
316 
317  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
318  auto texture = surface.asFlutterMetalTexture;
319  id<MTLTexture> metalTexture = (__bridge id)texture.texture;
320  EXPECT_EQ(metalTexture.pixelFormat, MTLPixelFormatBGRA8Unorm);
321  texture.destruction_callback(texture.user_data);
322 }
323 
324 TEST(FlutterSurfaceManager, WideGamutLayerWorksWithoutExplicitContentsFormat) {
325  TestView* testView = [[TestView alloc] init];
326  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/YES);
327 
328  auto surface = [surfaceManager surfaceForSize:CGSizeMake(50, 30)];
329  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface, CGPointMake(0, 0)) ]
330  atTime:0
331  notify:nil];
332 
333  EXPECT_EQ(testView.layer.sublayers.count, 1ul);
334  EXPECT_NE(testView.layer.sublayers[0].contents, nil);
335 }
336 
337 TEST(FlutterSurfaceManager, WideGamutIOSurfaceHasCorrectColorSpace) {
338  TestView* testView = [[TestView alloc] init];
339  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/YES);
340 
341  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
342  IOSurfaceRef ioSurface = surface.ioSurface;
343  CFTypeRef colorSpace = IOSurfaceCopyValue(ioSurface, kIOSurfaceColorSpace);
344  if (colorSpace == nullptr) {
345  GTEST_FAIL() << "IOSurfaceCopyValue returned nullptr for kIOSurfaceColorSpace";
346  return;
347  }
348  EXPECT_TRUE(CFEqual(colorSpace, kCGColorSpaceExtendedSRGB));
349  CFRelease(colorSpace);
350 }
351 
352 TEST(FlutterSurfaceManager, StandardGamutIOSurfaceHasCorrectColorSpace) {
353  TestView* testView = [[TestView alloc] init];
354  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/NO);
355 
356  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
357  IOSurfaceRef ioSurface = surface.ioSurface;
358  CFTypeRef colorSpace = IOSurfaceCopyValue(ioSurface, kIOSurfaceColorSpace);
359  if (colorSpace == nullptr) {
360  GTEST_FAIL() << "IOSurfaceCopyValue returned nullptr for kIOSurfaceColorSpace";
361  return;
362  }
363  EXPECT_TRUE(CFEqual(colorSpace, kCGColorSpaceSRGB));
364  CFRelease(colorSpace);
365 }
366 
367 TEST(FlutterSurfaceManager, WideGamutIOSurfaceHasCorrectPixelFormat) {
368  TestView* testView = [[TestView alloc] init];
369  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/YES);
370 
371  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
372  IOSurfaceRef ioSurface = surface.ioSurface;
373  uint32_t pixelFormat = (uint32_t)IOSurfaceGetPixelFormat(ioSurface);
374  EXPECT_EQ(pixelFormat, (uint32_t)kCVPixelFormatType_40ARGBLEWideGamut);
375 }
376 
377 TEST(FlutterSurfaceManager, StandardGamutIOSurfaceHasCorrectPixelFormat) {
378  TestView* testView = [[TestView alloc] init];
379  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/NO);
380 
381  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
382  IOSurfaceRef ioSurface = surface.ioSurface;
383  uint32_t pixelFormat = (uint32_t)IOSurfaceGetPixelFormat(ioSurface);
384  EXPECT_EQ(pixelFormat, (uint32_t)kCVPixelFormatType_32BGRA);
385 }
386 
387 TEST(FlutterSurfaceManager, WideGamutIOSurfaceHasCorrectBytesPerElement) {
388  TestView* testView = [[TestView alloc] init];
389  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/YES);
390 
391  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
392  IOSurfaceRef ioSurface = surface.ioSurface;
393  size_t bytesPerElement = IOSurfaceGetBytesPerElement(ioSurface);
394  EXPECT_EQ(bytesPerElement, 8ul);
395 }
396 
397 TEST(FlutterSurfaceManager, StandardGamutIOSurfaceHasCorrectBytesPerElement) {
398  TestView* testView = [[TestView alloc] init];
399  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/NO);
400 
401  auto surface = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
402  IOSurfaceRef ioSurface = surface.ioSurface;
403  size_t bytesPerElement = IOSurfaceGetBytesPerElement(ioSurface);
404  EXPECT_EQ(bytesPerElement, 4ul);
405 }
406 
407 TEST(FlutterSurfaceManager, SurfaceCacheDoesNotMixGamutModes) {
408  TestView* testView = [[TestView alloc] init];
409  // Create a wide gamut surface manager.
410  FlutterSurfaceManager* wideManager = CreateSurfaceManager(testView, /*enableWideGamut=*/YES);
411 
412  auto wideSurface = [wideManager surfaceForSize:CGSizeMake(100, 100)];
413  auto wideTexture = wideSurface.asFlutterMetalTexture;
414  id<MTLTexture> wideMetalTexture = (__bridge id)wideTexture.texture;
415  EXPECT_EQ(wideMetalTexture.pixelFormat, MTLPixelFormatBGRA10_XR);
416 
417  // Present and get it cached.
418  [wideManager presentSurfaces:@[ CreatePresentInfo(wideSurface) ] atTime:0 notify:nil];
419  wideTexture.destruction_callback(wideTexture.user_data);
420 
421  // Request same size again - should get a recycled wide gamut surface.
422  auto recycledSurface = [wideManager surfaceForSize:CGSizeMake(100, 100)];
423  auto recycledTexture = recycledSurface.asFlutterMetalTexture;
424  id<MTLTexture> recycledMetalTexture = (__bridge id)recycledTexture.texture;
425  EXPECT_EQ(recycledMetalTexture.pixelFormat, MTLPixelFormatBGRA10_XR);
426  recycledTexture.destruction_callback(recycledTexture.user_data);
427 }
428 
429 TEST(FlutterSurfaceManager, DynamicSwitchFromStandardToWideGamut) {
430  TestView* testView = [[TestView alloc] init];
431  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/NO);
432 
433  // Start with standard gamut surface.
434  auto surface1 = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
435  auto texture1 = surface1.asFlutterMetalTexture;
436  id<MTLTexture> metalTexture1 = (__bridge id)texture1.texture;
437  EXPECT_EQ(metalTexture1.pixelFormat, MTLPixelFormatBGRA8Unorm);
438  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface1) ] atTime:0 notify:nil];
439  texture1.destruction_callback(texture1.user_data);
440 
441  // Switch to wide gamut.
442  [surfaceManager setEnableWideGamut:YES];
443 
444  // Verify cache was flushed.
445  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
446 
447  // New surface should be wide gamut.
448  auto surface2 = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
449  auto texture2 = surface2.asFlutterMetalTexture;
450  id<MTLTexture> metalTexture2 = (__bridge id)texture2.texture;
451  EXPECT_EQ(metalTexture2.pixelFormat, MTLPixelFormatBGRA10_XR);
452  texture2.destruction_callback(texture2.user_data);
453 }
454 
455 TEST(FlutterSurfaceManager, DynamicSwitchFromWideToStandardGamut) {
456  TestView* testView = [[TestView alloc] init];
457  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/YES);
458 
459  // Start with wide gamut surface.
460  auto surface1 = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
461  auto texture1 = surface1.asFlutterMetalTexture;
462  id<MTLTexture> metalTexture1 = (__bridge id)texture1.texture;
463  EXPECT_EQ(metalTexture1.pixelFormat, MTLPixelFormatBGRA10_XR);
464  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface1) ] atTime:0 notify:nil];
465  texture1.destruction_callback(texture1.user_data);
466 
467  // Switch to standard gamut.
468  [surfaceManager setEnableWideGamut:NO];
469 
470  // Verify cache was flushed.
471  EXPECT_EQ(surfaceManager.backBufferCache.count, 0ul);
472 
473  // New surface should be standard gamut.
474  auto surface2 = [surfaceManager surfaceForSize:CGSizeMake(100, 50)];
475  auto texture2 = surface2.asFlutterMetalTexture;
476  id<MTLTexture> metalTexture2 = (__bridge id)texture2.texture;
477  EXPECT_EQ(metalTexture2.pixelFormat, MTLPixelFormatBGRA8Unorm);
478  texture2.destruction_callback(texture2.user_data);
479 }
480 
481 TEST(FlutterSurfaceManager, DynamicSwitchNoOpWhenSameValue) {
482  TestView* testView = [[TestView alloc] init];
483  FlutterSurfaceManager* surfaceManager = CreateSurfaceManager(testView, /*enableWideGamut=*/YES);
484 
485  // Cache a surface.
486  auto surface1 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
487  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface1) ] atTime:0 notify:nil];
488 
489  auto surface2 = [surfaceManager surfaceForSize:CGSizeMake(100, 100)];
490  [surfaceManager presentSurfaces:@[ CreatePresentInfo(surface2) ] atTime:0 notify:nil];
491  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
492 
493  // Set same value — cache should not be flushed.
494  [surfaceManager setEnableWideGamut:YES];
495  EXPECT_EQ(surfaceManager.backBufferCache.count, 1ul);
496 }
497 
498 } // namespace flutter::testing
IOSurfaceRef ioSurface
FlutterMetalTexture asFlutterMetalTexture()
nullable FlutterSurface * fromFlutterMetalTexture:(nonnull const FlutterMetalTexture *texture)
void setEnableWideGamut:(BOOL enableWideGamut)
nonnull FlutterSurface * surfaceForSize:(CGSize size)
FlutterBackBufferCache * backBufferCache
void presentSurfaces:atTime:notify:(nonnull NSArray< FlutterSurfacePresentInfo * > *surfaces,[atTime] CFTimeInterval presentationTime,[notify] nullable dispatch_block_t notify)
NSArray< FlutterSurface * > * frontSurfaces
std::vector< FlutterRect > paintRegion
nonnull instancetype init()
TEST(FlutterSurfaceManager, DynamicSwitchNoOpWhenSameValue)
bool operator==(const CGRect &lhs, const CGRect &rhs)
static FlutterSurfacePresentInfo * CreatePresentInfo(FlutterSurface *surface, CGPoint offset=CGPointZero, size_t index=0, const std::vector< FlutterRect > &paintRegion={})
static FlutterSurfaceManager * CreateSurfaceManager(TestView *testView, BOOL enableWideGamut=NO)