| Flutter macOS Embedder |

FlutterSurfaceManager.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 "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h"
6
7 #import <Metal/Metal.h>
8
9 #include <algorithm>
10
11 #include "flutter/fml/logging.h"
12 #import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurface.h"
13
14 @implementation FlutterSurfacePresentInfo
15 @end
16
17 @interface FlutterSurfaceManager () {
18 id<MTLDevice> _device;
19 id<MTLCommandQueue> _commandQueue;
20 CALayer* _containingLayer;
21 __weak id<FlutterSurfaceManagerDelegate> _delegate;
22
23 // Available (cached) back buffer surfaces. These will be cleared during
24 // present and replaced by current frong surfaces.
25 FlutterBackBufferCache* _backBufferCache;
26
27 // Surfaces currently used to back visible layers.
28 NSMutableArray<FlutterSurface*>* _frontSurfaces;
29
30 // Currently visible layers.
31 NSMutableArray<CALayer*>* _layers;
32
33 // Whether to highlight borders of overlay surfaces. Determined by
34 // FLTEnableSurfaceDebugInfo value in main bundle Info.plist.
35 NSNumber* _enableSurfaceDebugInfo;
36 CATextLayer* _infoLayer;
37
38 CFTimeInterval _lastPresentationTime;
39 }
40
41 /**
42 * Updates underlying CALayers with the contents of the surfaces to present.
43 */
44 - (void)commit:(NSArray<FlutterSurfacePresentInfo*>*)surfaces;
45
46 @end
47
48 static NSColor* GetBorderColorForLayer(int layer) {
49 NSArray* colors = @[
50 [NSColor yellowColor],
51 [NSColor cyanColor],
52 [NSColor magentaColor],
53 [NSColor greenColor],
54 [NSColor purpleColor],
55 [NSColor orangeColor],
56 [NSColor blueColor],
57 ];
58 return colors[layer % colors.count];
59 }
60
61 /// Creates sublayers for given layer, each one displaying a portion of the
62 /// of the surface determined by a rectangle in the provided paint region.
63 static void UpdateContentSubLayers(CALayer* layer,
64 IOSurfaceRef surface,
65 CGFloat scale,
66 CGSize surfaceSize,
67 NSColor* borderColor,
68 const std::vector<FlutterRect>& paintRegion) {
69 // Adjust sublayer count to paintRegion count.
70 while (layer.sublayers.count > paintRegion.size()) {
71 [layer.sublayers.lastObject removeFromSuperlayer];
72 }
73
74 while (layer.sublayers.count < paintRegion.size()) {
75 CALayer* newLayer = [CALayer layer];
76 [layer addSublayer:newLayer];
77 }
78
79 for (size_t i = 0; i < paintRegion.size(); i++) {
80 CALayer* subLayer = [layer.sublayers objectAtIndex:i];
81 const auto& rect = paintRegion[i];
82 subLayer.frame = CGRectMake(rect.left / scale, rect.top / scale,
83 (rect.right - rect.left) / scale, (rect.bottom - rect.top) / scale);
84
85 double width = surfaceSize.width;
86 double height = surfaceSize.height;
87
88 subLayer.contentsRect =
89 CGRectMake(rect.left / width, rect.top / height, (rect.right - rect.left) / width,
90 (rect.bottom - rect.top) / height);
91
92 if (borderColor != nil) {
93 // Visualize sublayer
94 subLayer.borderColor = borderColor.CGColor;
95 subLayer.borderWidth = 1.0;
96 }
97
98 subLayer.contents = (__bridge id)surface;
99 }
100 }
101
102 @implementation FlutterSurfaceManager
103
104 - (instancetype)initWithDevice:(id<MTLDevice>)device
105 commandQueue:(id<MTLCommandQueue>)commandQueue
106 layer:(CALayer*)containingLayer
107 delegate:(__weak id<FlutterSurfaceManagerDelegate>)delegate {
108 if (self = [super init]) {
109 _device = device;
110 _commandQueue = commandQueue;
111 _containingLayer = containingLayer;
112 _delegate = delegate;
113
114 _backBufferCache = [[FlutterBackBufferCache alloc] init];
115 _frontSurfaces = [NSMutableArray array];
116 _layers = [NSMutableArray array];
117 }
118 return self;
119 }
120
121 - (FlutterBackBufferCache*)backBufferCache {
122 return _backBufferCache;
123 }
124
125 - (NSArray*)frontSurfaces {
126 return _frontSurfaces;
127 }
128
129 - (NSArray*)layers {
130 return _layers;
131 }
132
133 - (FlutterSurface*)surfaceForSize:(CGSize)size {
134 FlutterSurface* surface = [_backBufferCache removeSurfaceForSize:size];
135 if (surface == nil) {
136 surface = [[FlutterSurface alloc] initWithSize:size device:_device];
137 }
138 return surface;
139 }
140
141 - (BOOL)enableSurfaceDebugInfo {
142 if (_enableSurfaceDebugInfo == nil) {
143 _enableSurfaceDebugInfo =
144 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTEnableSurfaceDebugInfo"];
145 if (_enableSurfaceDebugInfo == nil) {
146 _enableSurfaceDebugInfo = @NO;
147 }
148 }
149 return [_enableSurfaceDebugInfo boolValue];
150 }
151
152 - (void)commit:(NSArray<FlutterSurfacePresentInfo*>*)surfaces {
153 FML_DCHECK([NSThread isMainThread]);
154
155 // Release all unused back buffer surfaces and replace them with front surfaces.
156 [_backBufferCache returnSurfaces:_frontSurfaces];
157
158 // Front surfaces will be replaced by currently presented surfaces.
159 [_frontSurfaces removeAllObjects];
160 for (FlutterSurfacePresentInfo* info in surfaces) {
161 [_frontSurfaces addObject:info.surface];
162 }
163
164 // Add or remove layers to match the count of surfaces to present.
165 while (_layers.count > _frontSurfaces.count) {
166 [_layers.lastObject removeFromSuperlayer];
167 [_layers removeLastObject];
168 }
169 while (_layers.count < _frontSurfaces.count) {
170 CALayer* layer = [CALayer layer];
171 [_containingLayer addSublayer:layer];
172 [_layers addObject:layer];
173 }
174
175 bool enableSurfaceDebugInfo = self.enableSurfaceDebugInfo;
176
177 // Update contents of surfaces.
178 for (size_t i = 0; i < surfaces.count; ++i) {
179 FlutterSurfacePresentInfo* info = surfaces[i];
180 CALayer* layer = _layers[i];
181 CGFloat scale = _containingLayer.contentsScale;
182 if (i == 0) {
183 layer.frame = CGRectMake(info.offset.x / scale, info.offset.y / scale,
184 info.surface.size.width / scale, info.surface.size.height / scale);
185 layer.contents = (__bridge id)info.surface.ioSurface;
186 } else {
187 layer.frame = CGRectZero;
188 NSColor* borderColor = enableSurfaceDebugInfo ? GetBorderColorForLayer(i - 1) : nil;
189 UpdateContentSubLayers(layer, info.surface.ioSurface, scale, info.surface.size, borderColor,
190 info.paintRegion);
191 }
192 layer.zPosition = info.zIndex;
193 }
194
195 if (enableSurfaceDebugInfo) {
196 if (_infoLayer == nil) {
197 _infoLayer = [[CATextLayer alloc] init];
198 [_containingLayer addSublayer:_infoLayer];
199 _infoLayer.fontSize = 15;
200 _infoLayer.foregroundColor = [NSColor yellowColor].CGColor;
201 _infoLayer.frame = CGRectMake(15, 15, 300, 100);
202 _infoLayer.contentsScale = _containingLayer.contentsScale;
203 _infoLayer.zPosition = 100000;
204 }
205 _infoLayer.string = [NSString stringWithFormat:@"Surface count: %li", _layers.count];
206 }
207 }
208
209 static CGSize GetRequiredFrameSize(NSArray<FlutterSurfacePresentInfo*>* surfaces) {
210 CGSize size = CGSizeZero;
211 for (FlutterSurfacePresentInfo* info in surfaces) {
212 size = CGSizeMake(std::max(size.width, info.offset.x + info.surface.size.width),
213 std::max(size.height, info.offset.y + info.surface.size.height));
214 }
215 return size;
216 }
217
218 - (void)presentSurfaces:(NSArray<FlutterSurfacePresentInfo*>*)surfaces
219 atTime:(CFTimeInterval)presentationTime
220 notify:(dispatch_block_t)notify {
221 id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
222 [commandBuffer commit];
223 [commandBuffer waitUntilScheduled];
224
225 CGSize size = GetRequiredFrameSize(surfaces);
226
227 CFTimeInterval delay = 0;
228
229 if (presentationTime > 0) {
230 // Enforce frame pacing. It seems that the target timestamp of CVDisplayLink does not
231 // exactly correspond to core animation deadline. Especially with 120hz, setting the frame
232 // contents too close after previous target timestamp will result in uneven frame pacing.
233 // Empirically setting the content in the second half of frame interval seems to work
234 // well for both 60hz and 120hz.
235 //
236 // This schedules a timer on current (raster) thread runloop. Raster thread at
237 // this point should be idle (the next frame vsync has not been signalled yet).
238 //
239 // Alternative could be simply blocking the raster thread, but that would show
240 // as a average_frame_rasterizer_time_millis regresson.
241 CFTimeInterval minPresentationTime = (presentationTime + _lastPresentationTime) / 2.0;
242 CFTimeInterval now = CACurrentMediaTime();
243 delay = std::max(minPresentationTime - now, 0.0);
244 }
245 [_delegate onPresent:size
246 withBlock:^{
247 _lastPresentationTime = presentationTime;
248 [CATransaction begin];
249 [CATransaction setDisableActions:YES];
250 [self commit:surfaces];
251 if (notify != nil) {
252 notify();
253 }
254 [CATransaction commit];
255 }
256 delay:delay];
257 }
258
259 @end
260
261 // Cached back buffers will be released after kIdleDelay if there is no activity.
262 static const double kIdleDelay = 1.0;
263 // Once surfaces reach kEvictionAge, they will be evicted from the cache.
264 // The age of 30 has been chosen to reduce potential surface allocation churn.
265 // For unused surface 30 frames means only half a second at 60fps, and there is
266 // idle timeout of 1 second where all surfaces are evicted.
267 static const int kSurfaceEvictionAge = 30;
268
269 @interface FlutterBackBufferCache () {
270 NSMutableArray<FlutterSurface*>* _surfaces;
271 NSMapTable<FlutterSurface*, NSNumber*>* _surfaceAge;
272 }
273
274 @end
275
276 @implementation FlutterBackBufferCache
277
278 - (instancetype)init {
279 if (self = [super init]) {
280 self->_surfaces = [[NSMutableArray alloc] init];
281 self->_surfaceAge = [NSMapTable weakToStrongObjectsMapTable];
282 }
283 return self;
284 }
285
286 - (int)ageForSurface:(FlutterSurface*)surface {
287 NSNumber* age = [_surfaceAge objectForKey:surface];
288 return age != nil ? age.intValue : 0;
289 }
290
291 - (void)setAge:(int)age forSurface:(FlutterSurface*)surface {
292 [_surfaceAge setObject:@(age) forKey:surface];
293 }
294
295 - (nullable FlutterSurface*)removeSurfaceForSize:(CGSize)size {
296 @synchronized(self) {
297 // Purge all cached surfaces if the size has changed.
298 if (_surfaces.firstObject != nil && !CGSizeEqualToSize(_surfaces.firstObject.size, size)) {
299 [_surfaces removeAllObjects];
300 }
301
302 FlutterSurface* res;
303
304 // Returns youngest surface that is not in use. Returning youngest surface ensures
305 // that the cache doesn't keep more surfaces than it needs to, as the unused surfaces
306 // kept in cache will have their age kept increasing until purged (inside [returnSurfaces:]).
307 for (FlutterSurface* surface in _surfaces) {
308 if (!surface.isInUse &&
309 (res == nil || [self ageForSurface:res] > [self ageForSurface:surface])) {
310 res = surface;
311 }
312 }
313 if (res != nil) {
314 [_surfaces removeObject:res];
315 }
316 return res;
317 }
318 }
319
320 - (void)returnSurfaces:(nonnull NSArray<FlutterSurface*>*)returnedSurfaces {
321 @synchronized(self) {
322 for (FlutterSurface* surface in returnedSurfaces) {
323 [self setAge:0 forSurface:surface];
324 }
325 for (FlutterSurface* surface in _surfaces) {
326 [self setAge:[self ageForSurface:surface] + 1 forSurface:surface];
327 }
328
329 [_surfaces addObjectsFromArray:returnedSurfaces];
330
331 // Purge all surface with age = kSurfaceEvictionAge. Reaching this age can mean two things:
332 // - Surface is still in use and we can't return it. This can happen in some edge
333 // cases where the compositor holds on to the surface for much longer than expected.
334 // - Surface is not in use but it hasn't been requested from the cache for a while.
335 // This means there are too many surfaces in the cache.
336 [_surfaces filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(FlutterSurface* surface,
337 NSDictionary* bindings) {
338 return [self ageForSurface:surface] < kSurfaceEvictionAge;
339 }]];
340 }
341
342 // performSelector:withObject:afterDelay needs to be performed on RunLoop thread
343 [self performSelectorOnMainThread:@selector(reschedule) withObject:nil waitUntilDone:NO];
344 }
345
346 - (NSUInteger)count {
347 @synchronized(self) {
348 return _surfaces.count;
349 }
350 }
351
352 - (void)onIdle {
353 @synchronized(self) {
354 [_surfaces removeAllObjects];
355 }
356 }
357
358 - (void)reschedule {
359 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(onIdle) object:nil];
360 [self performSelector:@selector(onIdle) withObject:nil afterDelay:kIdleDelay];
361 }
362
363 - (void)dealloc {
364 [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(onIdle) object:nil];
365 }
366
367 @end
FlutterSurface.h
FlutterSurfaceManager.h
UpdateContentSubLayers
static void UpdateContentSubLayers(CALayer *layer, IOSurfaceRef surface, CGFloat scale, CGSize surfaceSize, NSColor *borderColor, const std::vector< FlutterRect > &paintRegion)
Definition: FlutterSurfaceManager.mm:63
GetBorderColorForLayer
static NSColor * GetBorderColorForLayer(int layer)
Definition: FlutterSurfaceManager.mm:48
kIdleDelay
static const double kIdleDelay
Definition: FlutterSurfaceManager.mm:262
kSurfaceEvictionAge
static const int kSurfaceEvictionAge
Definition: FlutterSurfaceManager.mm:267
FlutterBackBufferCache()::_surfaces
NSMutableArray< FlutterSurface * > * _surfaces
Definition: FlutterSurfaceManager.mm:270
FlutterBackBufferCache()::_surfaceAge
NSMapTable< FlutterSurface *, NSNumber * > * _surfaceAge
Definition: FlutterSurfaceManager.mm:271
FlutterSurfaceManager()::_containingLayer
CALayer * _containingLayer
Definition: FlutterSurfaceManager.mm:20
FlutterSurfaceManager()::_commandQueue
id< MTLCommandQueue > _commandQueue
Definition: FlutterSurfaceManager.mm:19
FlutterSurfaceManager()::_frontSurfaces
NSMutableArray< FlutterSurface * > * _frontSurfaces
Definition: FlutterSurfaceManager.mm:28
FlutterSurfaceManager()::_device
id< MTLDevice > _device
Definition: FlutterSurfaceManager.mm:18
FlutterSurfaceManager()::_infoLayer
CATextLayer * _infoLayer
Definition: FlutterSurfaceManager.mm:36
FlutterSurfaceManager()::_delegate
__weak id< FlutterSurfaceManagerDelegate > _delegate
Definition: FlutterSurfaceManager.mm:21
FlutterSurfaceManager()::_backBufferCache
FlutterBackBufferCache * _backBufferCache
Definition: FlutterSurfaceManager.mm:25
FlutterSurfaceManager()::_lastPresentationTime
CFTimeInterval _lastPresentationTime
Definition: FlutterSurfaceManager.mm:38
FlutterSurfaceManager()::_layers
NSMutableArray< CALayer * > * _layers
Definition: FlutterSurfaceManager.mm:31
FlutterSurfaceManager()::_enableSurfaceDebugInfo
NSNumber * _enableSurfaceDebugInfo
Definition: FlutterSurfaceManager.mm:35
FlutterSurfaceManager(Private)::layers
NSArray< CALayer * > * layers
Definition: FlutterSurfaceManager.h:109
FlutterSurfaceManager(Private)::backBufferCache
FlutterBackBufferCache * backBufferCache
Definition: FlutterSurfaceManager.h:107
FlutterSurfaceManager(Private)::frontSurfaces
NSArray< FlutterSurface * > * frontSurfaces
Definition: FlutterSurfaceManager.h:108
FlutterBackBufferCache
Definition: FlutterSurfaceManager.h:84
-[FlutterBackBufferCache count]
NSUInteger count()
Definition: FlutterSurfaceManager.mm:346
FlutterSurface
Definition: FlutterSurface.h:17
FlutterSurface::ioSurface
IOSurfaceRef ioSurface
Definition: FlutterSurface.h:32
FlutterSurface::isInUse
BOOL isInUse
Definition: FlutterSurface.h:36
FlutterSurface::size
CGSize size
Definition: FlutterSurface.h:33
FlutterSurfaceManager
Definition: FlutterSurfaceManager.h:47
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
FlutterSurfaceManagerDelegate-p
Definition: FlutterSurfaceManager.h:27