| Flutter iOS Embedder |

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