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