Flutter Engine
The Flutter Engine
FlutterMetalLayerTest.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 <Metal/Metal.h>
6#import <OCMock/OCMock.h>
7#import <QuartzCore/QuartzCore.h>
8#import <XCTest/XCTest.h>
9
10#include "flutter/fml/logging.h"
11#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterMetalLayer.h"
12
13@interface FlutterMetalLayerTest : XCTestCase
14@end
15
16@interface TestFlutterMetalLayerView : UIView
17@end
18
19@implementation TestFlutterMetalLayerView
20
21+ (Class)layerClass {
22 return [FlutterMetalLayer class];
23}
24
25@end
26
27/// A fake compositor that simulates presenting layer surface by increasing
28/// and decreasing IOSurface use count.
29@interface TestCompositor : NSObject {
31 IOSurfaceRef _presentedSurface;
32}
33@end
34
35@implementation TestCompositor
36
37- (instancetype)initWithLayer:(FlutterMetalLayer*)layer {
38 self = [super init];
39 if (self) {
40 self->_layer = layer;
41 }
42 return self;
43}
44
45/// Increment use count of currently presented surface and decrement use count
46/// of previously presented surface.
47- (void)commitTransaction {
48 IOSurfaceRef surface = (__bridge IOSurfaceRef)self->_layer.contents;
49 if (self->_presentedSurface) {
50 IOSurfaceDecrementUseCount(self->_presentedSurface);
51 }
52 IOSurfaceIncrementUseCount(surface);
53 self->_presentedSurface = surface;
54}
55
56- (void)dealloc {
57 if (self->_presentedSurface) {
58 IOSurfaceDecrementUseCount(self->_presentedSurface);
59 }
60}
61
62@end
63
64@implementation FlutterMetalLayerTest
65
68 [[TestFlutterMetalLayerView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
69 FlutterMetalLayer* layer = (FlutterMetalLayer*)view.layer;
70 layer.drawableSize = CGSizeMake(100, 100);
71 return layer;
72}
73
74- (void)removeMetalLayer:(FlutterMetalLayer*)layer {
75}
76
77// For unknown reason sometimes CI fails to create IOSurface. Bail out
78// to prevent flakiness.
79#define BAIL_IF_NO_DRAWABLE(drawable) \
80 if (drawable == nil) { \
81 FML_LOG(ERROR) << "Could not allocate drawable"; \
82 return; \
83 }
84
85- (void)testFlip {
86 FlutterMetalLayer* layer = [self addMetalLayer];
87 TestCompositor* compositor = [[TestCompositor alloc] initWithLayer:layer];
88
89 id<MTLTexture> t1, t2, t3;
90
91 id<CAMetalDrawable> drawable = [layer nextDrawable];
92 BAIL_IF_NO_DRAWABLE(drawable);
93 t1 = drawable.texture;
94 [drawable present];
95 [compositor commitTransaction];
96
97 drawable = [layer nextDrawable];
98 BAIL_IF_NO_DRAWABLE(drawable);
99 t2 = drawable.texture;
100 [drawable present];
101 [compositor commitTransaction];
102
103 drawable = [layer nextDrawable];
104 BAIL_IF_NO_DRAWABLE(drawable);
105 t3 = drawable.texture;
106 [drawable present];
107 [compositor commitTransaction];
108
109 // If there was no frame drop, layer should return oldest presented
110 // texture.
111
112 drawable = [layer nextDrawable];
113 XCTAssertEqual(drawable.texture, t1);
114
115 [drawable present];
116 [compositor commitTransaction];
117
118 drawable = [layer nextDrawable];
119 XCTAssertEqual(drawable.texture, t2);
120 [drawable present];
121 [compositor commitTransaction];
122
123 drawable = [layer nextDrawable];
124 XCTAssertEqual(drawable.texture, t3);
125 [drawable present];
126 [compositor commitTransaction];
127
128 drawable = [layer nextDrawable];
129 XCTAssertEqual(drawable.texture, t1);
130 [drawable present];
131
132 [self removeMetalLayer:layer];
133}
134
136 FlutterMetalLayer* layer = [self addMetalLayer];
137 TestCompositor* compositor = [[TestCompositor alloc] initWithLayer:layer];
138
139 id<MTLTexture> t1, t2, t3;
140
141 id<CAMetalDrawable> drawable = [layer nextDrawable];
142 BAIL_IF_NO_DRAWABLE(drawable);
143 t1 = drawable.texture;
144 [drawable present];
145 [compositor commitTransaction];
146 XCTAssertTrue(IOSurfaceIsInUse(t1.iosurface));
147
148 drawable = [layer nextDrawable];
149 BAIL_IF_NO_DRAWABLE(drawable);
150 t2 = drawable.texture;
151 [drawable present];
152 [compositor commitTransaction];
153
154 drawable = [layer nextDrawable];
155 BAIL_IF_NO_DRAWABLE(drawable);
156 t3 = drawable.texture;
157 [drawable present];
158 [compositor commitTransaction];
159
160 // Simulate compositor holding on to t3 for a while.
161 IOSurfaceIncrementUseCount(t3.iosurface);
162
163 // Here the drawable is presented, but immediately replaced by another drawable
164 // (before the compositor has a chance to pick it up). This should result
165 // in same drawable returned in next call to nextDrawable.
166 drawable = [layer nextDrawable];
167 XCTAssertEqual(drawable.texture, t1);
168 XCTAssertFalse(IOSurfaceIsInUse(drawable.texture.iosurface));
169 [drawable present];
170
171 drawable = [layer nextDrawable];
172 XCTAssertEqual(drawable.texture, t2);
173 [drawable present];
174 [compositor commitTransaction];
175
176 // Next drawable should be t1, since it was never picked up by compositor.
177 drawable = [layer nextDrawable];
178 XCTAssertEqual(drawable.texture, t1);
179
180 IOSurfaceDecrementUseCount(t3.iosurface);
181
182 [self removeMetalLayer:layer];
183}
184
186 FlutterMetalLayer* layer = [self addMetalLayer];
187 // FlutterMetalLayer will keep creating new textures until it has 3.
188 @autoreleasepool {
189 for (int i = 0; i < 3; ++i) {
190 id<CAMetalDrawable> drawable = [layer nextDrawable];
191 BAIL_IF_NO_DRAWABLE(drawable);
192 }
193 }
194 id<MTLTexture> texture;
195 {
196 @autoreleasepool {
197 id<CAMetalDrawable> drawable = [layer nextDrawable];
198 XCTAssertNotNil(drawable);
199 texture = (id<MTLTexture>)drawable.texture;
200 // Dropping the drawable must return texture to pool, so
201 // next drawable should return the same texture.
202 }
203 }
204 {
205 id<CAMetalDrawable> drawable = [layer nextDrawable];
206 XCTAssertEqual(texture, drawable.texture);
207 }
208
209 [self removeMetalLayer:layer];
210}
211
213 FlutterMetalLayer* layer = [self addMetalLayer];
214
215 id<CAMetalDrawable> d1 = [layer nextDrawable];
217 id<CAMetalDrawable> d2 = [layer nextDrawable];
219 id<CAMetalDrawable> d3 = [layer nextDrawable];
221 XCTAssertNotNil(d3);
222
223 // Layer should not return more than 3 drawables.
224 id<CAMetalDrawable> d4 = [layer nextDrawable];
225 XCTAssertNil(d4);
226
227 [d1 present];
228
229 // Still no drawable, until the front buffer returns to pool
230 id<CAMetalDrawable> d5 = [layer nextDrawable];
231 XCTAssertNil(d5);
232
233 [d2 present];
234 id<CAMetalDrawable> d6 = [layer nextDrawable];
235 XCTAssertNotNil(d6);
236
237 [self removeMetalLayer:layer];
238}
239
240- (void)testTimeout {
241 FlutterMetalLayer* layer = [self addMetalLayer];
242 TestCompositor* compositor = [[TestCompositor alloc] initWithLayer:layer];
243
244 id<CAMetalDrawable> drawable = [layer nextDrawable];
245 BAIL_IF_NO_DRAWABLE(drawable);
246
247 __block MTLCommandBufferHandler handler;
248
249 id<MTLCommandBuffer> mockCommandBuffer = OCMProtocolMock(@protocol(MTLCommandBuffer));
250 OCMStub([mockCommandBuffer addCompletedHandler:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
251 MTLCommandBufferHandler handlerOnStack;
252 [invocation getArgument:&handlerOnStack atIndex:2];
253 // Required to copy stack block to heap.
254 handler = handlerOnStack;
255 });
256
257 [(id<FlutterMetalDrawable>)drawable flutterPrepareForPresent:mockCommandBuffer];
258 [drawable present];
259 [compositor commitTransaction];
260
261 // Drawable will not be available until the command buffer completes.
262 drawable = [layer nextDrawable];
263 XCTAssertNil(drawable);
264
265 handler(mockCommandBuffer);
266
267 drawable = [layer nextDrawable];
268 XCTAssertNotNil(drawable);
269
270 [self removeMetalLayer:layer];
271}
272
273@end
#define BAIL_IF_NO_DRAWABLE(drawable)
VkSurfaceKHR surface
Definition: main.cc:49
if(end==-1)
FlutterMetalLayer * addMetalLayer()
nullable id< CAMetalDrawable > nextDrawable()
FlutterMetalLayer * _layer
IOSurfaceRef _presentedSurface
FlTexture * texture