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"
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 {
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 {
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];
216  id<CAMetalDrawable> d2 = [layer nextDrawable];
218  id<CAMetalDrawable> d3 = [layer nextDrawable];
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