blob: 8473d2b7395211ae5c962b9cc1f21add202e2adc [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.h"
#import <Metal/Metal.h>
#include <algorithm>
#include "flutter/fml/logging.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterSurface.h"
@implementation FlutterSurfacePresentInfo
@end
@interface FlutterSurfaceManager () {
id<MTLDevice> _device;
id<MTLCommandQueue> _commandQueue;
CALayer* _containingLayer;
__weak id<FlutterSurfaceManagerDelegate> _delegate;
// Available (cached) back buffer surfaces. These will be cleared during
// present and replaced by current frong surfaces.
FlutterBackBufferCache* _backBufferCache;
// Surfaces currently used to back visible layers.
NSMutableArray<FlutterSurface*>* _frontSurfaces;
// Currently visible layers.
NSMutableArray<CALayer*>* _layers;
}
/**
* Updates underlying CALayers with the contents of the surfaces to present.
*/
- (void)commit:(NSArray<FlutterSurfacePresentInfo*>*)surfaces;
@end
@implementation FlutterSurfaceManager
- (instancetype)initWithDevice:(id<MTLDevice>)device
commandQueue:(id<MTLCommandQueue>)commandQueue
layer:(CALayer*)containingLayer
delegate:(__weak id<FlutterSurfaceManagerDelegate>)delegate {
if (self = [super init]) {
_device = device;
_commandQueue = commandQueue;
_containingLayer = containingLayer;
_delegate = delegate;
_backBufferCache = [[FlutterBackBufferCache alloc] init];
_frontSurfaces = [NSMutableArray array];
_layers = [NSMutableArray array];
}
return self;
}
- (FlutterBackBufferCache*)backBufferCache {
return _backBufferCache;
}
- (NSArray*)frontSurfaces {
return _frontSurfaces;
}
- (NSArray*)layers {
return _layers;
}
- (FlutterSurface*)surfaceForSize:(CGSize)size {
FlutterSurface* surface = [_backBufferCache removeSurfaceForSize:size];
if (surface == nil) {
surface = [[FlutterSurface alloc] initWithSize:size device:_device];
}
return surface;
}
- (void)commit:(NSArray<FlutterSurfacePresentInfo*>*)surfaces {
FML_DCHECK([NSThread isMainThread]);
// Release all unused back buffer surfaces and replace them with front surfaces.
[_backBufferCache replaceSurfaces:_frontSurfaces];
// Front surfaces will be replaced by currently presented surfaces.
[_frontSurfaces removeAllObjects];
for (FlutterSurfacePresentInfo* info in surfaces) {
[_frontSurfaces addObject:info.surface];
}
// Add or remove layers to match the count of surfaces to present.
while (_layers.count > _frontSurfaces.count) {
[_layers.lastObject removeFromSuperlayer];
[_layers removeLastObject];
}
while (_layers.count < _frontSurfaces.count) {
CALayer* layer = [CALayer layer];
[_containingLayer addSublayer:layer];
[_layers addObject:layer];
}
// Update contents of surfaces.
for (size_t i = 0; i < surfaces.count; ++i) {
FlutterSurfacePresentInfo* info = surfaces[i];
CALayer* layer = _layers[i];
CGFloat scale = _containingLayer.contentsScale;
layer.frame = CGRectMake(info.offset.x / scale, info.offset.y / scale,
info.surface.size.width / scale, info.surface.size.height / scale);
layer.contents = (__bridge id)info.surface.ioSurface;
layer.zPosition = info.zIndex;
}
}
static CGSize GetRequiredFrameSize(NSArray<FlutterSurfacePresentInfo*>* surfaces) {
CGSize size = CGSizeZero;
for (FlutterSurfacePresentInfo* info in surfaces) {
size = CGSizeMake(std::max(size.width, info.offset.x + info.surface.size.width),
std::max(size.height, info.offset.y + info.surface.size.height));
}
return size;
}
- (void)present:(NSArray<FlutterSurfacePresentInfo*>*)surfaces notify:(dispatch_block_t)notify {
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
[commandBuffer commit];
[commandBuffer waitUntilScheduled];
// Get the actual dimensions of the frame (relevant for thread synchronizer).
CGSize size = GetRequiredFrameSize(surfaces);
[_delegate onPresent:size
withBlock:^{
[self commit:surfaces];
if (notify != nil) {
notify();
}
}];
}
@end
// Cached back buffers will be released after kIdleDelay if there is no activity.
static const double kIdleDelay = 1.0;
@interface FlutterBackBufferCache () {
NSMutableArray<FlutterSurface*>* _surfaces;
}
@end
@implementation FlutterBackBufferCache
- (instancetype)init {
if (self = [super init]) {
self->_surfaces = [[NSMutableArray alloc] init];
}
return self;
}
- (nullable FlutterSurface*)removeSurfaceForSize:(CGSize)size {
@synchronized(self) {
for (FlutterSurface* surface in _surfaces) {
if (CGSizeEqualToSize(surface.size, size)) {
// By default ARC doesn't retain enumeration iteration variables.
FlutterSurface* res = surface;
[_surfaces removeObject:surface];
return res;
}
}
return nil;
}
}
- (void)replaceSurfaces:(nonnull NSArray<FlutterSurface*>*)surfaces {
@synchronized(self) {
[_surfaces removeAllObjects];
[_surfaces addObjectsFromArray:surfaces];
}
// performSelector:withObject:afterDelay needs to be performed on RunLoop thread
[self performSelectorOnMainThread:@selector(reschedule) withObject:nil waitUntilDone:NO];
}
- (NSUInteger)count {
@synchronized(self) {
return _surfaces.count;
}
}
- (void)onIdle {
@synchronized(self) {
[_surfaces removeAllObjects];
}
}
- (void)reschedule {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(onIdle) object:nil];
[self performSelector:@selector(onIdle) withObject:nil afterDelay:kIdleDelay];
}
- (void)dealloc {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(onIdle) object:nil];
}
@end