| 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
8 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurface.h"
9 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h"
10
11 #include "flutter/testing/testing.h"
12 #include "gtest/gtest.h"
13
14 @interface TestView : NSView <FlutterSurfaceManagerDelegate>
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
54 static FlutterSurfacePresentInfo* CreatePresentInfo(
55 FlutterSurface* surface,
56 CGPoint offset = CGPointZero,
57 size_t index = 0,
58 const std::vector<FlutterRect>& paintRegion = {}) {
59 FlutterSurfacePresentInfo* res = [[FlutterSurfacePresentInfo alloc] init];
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
FlutterSurface.h
FlutterSurfaceManager.h
-[FlutterBackBufferCache count]
NSUInteger count()
Definition: FlutterSurfaceManager.mm:376
FlutterSurface
Definition: FlutterSurface.h:17
FlutterSurface::ioSurface
IOSurfaceRef ioSurface
Definition: FlutterSurface.h:34
-[FlutterSurface asFlutterMetalTexture]
FlutterMetalTexture asFlutterMetalTexture()
Definition: FlutterSurface.mm:71
+[FlutterSurface fromFlutterMetalTexture:]
nullable FlutterSurface * fromFlutterMetalTexture:(nonnull const FlutterMetalTexture *texture)
FlutterSurfaceManager
Definition: FlutterSurfaceManager.h:47
-[FlutterSurfaceManager setEnableWideGamut:]
void setEnableWideGamut:(BOOL enableWideGamut)
Definition: FlutterSurfaceManager.mm:123
-[FlutterSurfaceManager surfaceForSize:]
nonnull FlutterSurface * surfaceForSize:(CGSize size)
Definition: FlutterSurfaceManager.mm:149
FlutterSurfaceManager::backBufferCache
FlutterBackBufferCache * backBufferCache
Definition: FlutterSurfaceManager.h:121
-[FlutterSurfaceManager presentSurfaces:atTime:notify:]
void presentSurfaces:atTime:notify:(nonnull NSArray< FlutterSurfacePresentInfo * > *surfaces,[atTime] CFTimeInterval presentationTime,[notify] nullable dispatch_block_t notify)
FlutterSurfaceManager::frontSurfaces
NSArray< FlutterSurface * > * frontSurfaces
Definition: FlutterSurfaceManager.h:122
FlutterSurfacePresentInfo
Definition: FlutterSurfaceManager.h:19
FlutterSurfacePresentInfo::surface
FlutterSurface * surface
Definition: FlutterSurfaceManager.h:20
FlutterSurfacePresentInfo::paintRegion
std::vector< FlutterRect > paintRegion
Definition: FlutterSurfaceManager.h:23
FlutterSurfacePresentInfo::offset
CGPoint offset
Definition: FlutterSurfaceManager.h:21
FlutterSurfacePresentInfo::zIndex
size_t zIndex
Definition: FlutterSurfaceManager.h:22
TestView
Definition: FlutterSurfaceManagerTest.mm:14
TestView::presentedFrameSize
CGSize presentedFrameSize
Definition: FlutterSurfaceManagerTest.mm:16
-[TestView init]
nonnull instancetype init()
Definition: FlutterSurfaceManagerTest.mm:23
flutter::testing
Definition: AccessibilityBridgeMacTest.mm:13
flutter::testing::TEST
TEST(FlutterSurfaceManager, DynamicSwitchNoOpWhenSameValue)
Definition: FlutterSurfaceManagerTest.mm:481
flutter::testing::operator==
bool operator==(const CGRect &lhs, const CGRect &rhs)
Definition: FlutterSurfaceManagerTest.mm:201
flutter::testing::CreatePresentInfo
static FlutterSurfacePresentInfo * CreatePresentInfo(FlutterSurface *surface, CGPoint offset=CGPointZero, size_t index=0, const std::vector< FlutterRect > &paintRegion={})
Definition: FlutterSurfaceManagerTest.mm:54
flutter::testing::CreateSurfaceManager
static FlutterSurfaceManager * CreateSurfaceManager(TestView *testView, BOOL enableWideGamut=NO)
Definition: FlutterSurfaceManagerTest.mm:43
FlutterSurfaceManagerDelegate-p
Definition: FlutterSurfaceManager.h:27