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