Flutter Engine
The Flutter Engine
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
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
39namespace 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
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
64TEST(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
77TEST(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
96TEST(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
132TEST(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
171TEST(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
198inline bool operator==(const CGRect& lhs, const CGRect& rhs) {
199 return CGRectEqualToRect(lhs, rhs);
200}
201
202TEST(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
VkDevice device
Definition: main.cc:53
VkSurfaceKHR surface
Definition: main.cc:49
double frame
Definition: examples.cpp:31
nonnull FlutterSurface * surfaceForSize:(CGSize size)
void presentSurfaces:atTime:notify:(nonnull NSArray< FlutterSurfacePresentInfo * > *surfaces,[atTime] CFTimeInterval presentationTime,[notify] nullable dispatch_block_t notify)
FlutterBackBufferCache * backBufferCache()
std::vector< FlutterRect > paintRegion
nullable FlutterSurface * fromFlutterMetalTexture:(nonnull const FlutterMetalTexture *texture)
nonnull instancetype init()
FlTexture * texture
TEST(FlutterSurfaceManager, LayerManagement)
static FlutterSurfacePresentInfo * CreatePresentInfo(FlutterSurface *surface, CGPoint offset=CGPointZero, size_t index=0, const std::vector< FlutterRect > &paintRegion={})
bool operator==(const CGRect &lhs, const CGRect &rhs)
static FlutterSurfaceManager * CreateSurfaceManager(TestView *testView)
SeparatedVector2 offset
int64_t texture_id
Definition: embedder.h:656
A structure to represent a rectangle.
Definition: embedder.h:437
const uintptr_t id
#define EXPECT_TRUE(handle)
Definition: unit_test.h:678