7#include <CoreMedia/CoreMedia.h>
8#include <IOSurface/IOSurfaceObjC.h>
9#include <Metal/Metal.h>
10#include <UIKit/UIKit.h>
12#import "flutter/shell/platform/darwin/common/InternalFlutterSwiftCommon/InternalFlutterSwiftCommon.h"
14#import "flutter/shell/platform/darwin/ios/InternalFlutterSwift/InternalFlutterSwift.h"
51- (void)onDisplayLink:(CADisplayLink*)link;
59@property(readonly, nonatomic) id<MTLTexture>
texture;
60@property(readonly, nonatomic) IOSurface*
surface;
68- (instancetype)initWithTexture:(
id<MTLTexture>)texture surface:(IOSurface*)surface {
69 if (
self = [super init]) {
87 drawableId:(NSUInteger)drawableId;
95 drawableId:(NSUInteger)drawableId {
96 if (
self = [super init]) {
99 _drawableId = drawableId;
105 return self->_texture.texture;
108#pragma clang diagnostic push
109#pragma clang diagnostic ignored "-Wunguarded-availability-new"
110- (CAMetalLayer*)layer {
111 return (
id)
self->_layer;
113#pragma clang diagnostic pop
115- (NSUInteger)drawableID {
116 return self->_drawableId;
124 [_layer presentTexture:self->_texture];
125 self->_presented = YES;
130 [_layer returnTexture:self->_texture];
134- (void)addPresentedHandler:(nonnull MTLDrawablePresentedHandler)block {
135 [FlutterLogger logWarning:@"FlutterMetalLayer drawable does not implement addPresentedHandler:"];
138- (void)presentAtTime:(CFTimeInterval)presentationTime {
139 [FlutterLogger logWarning:@"FlutterMetalLayer drawable does not implement presentAtTime:"];
142- (void)presentAfterMinimumDuration:(CFTimeInterval)duration {
144 logWarning:@"FlutterMetalLayer drawable does not implement presentAfterMinimumDuration:"];
147- (void)flutterPrepareForPresent:(nonnull
id<MTLCommandBuffer>)commandBuffer {
150 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
151 texture.waitingForCompletion = NO;
165 if (
self = [super init]) {
171- (void)onDisplayLink:(CADisplayLink*)link {
172 [_layer onDisplayLink:link];
179- (instancetype)init {
180 if (
self = [super init]) {
181 _preferredDevice = MTLCreateSystemDefaultDevice();
183 self.pixelFormat = MTLPixelFormatBGRA8Unorm;
184 _availableTextures = [[NSMutableSet alloc] init];
185 _displayLinkManager = FlutterDisplayLinkManager.shared;
189 _displayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(onDisplayLink:)];
190 [
self setMaxRefreshRate:_displayLinkManager.displayRefreshRate forceMax:NO];
191 [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
192 [[NSNotificationCenter defaultCenter] addObserver:self
193 selector:@selector(didEnterBackground:)
194 name:UIApplicationDidEnterBackgroundNotification
201 [_displayLink invalidate];
202 [[NSNotificationCenter defaultCenter] removeObserver:self];
205- (void)setMaxRefreshRate:(
double)refreshRate forceMax:(
BOOL)forceMax {
210 if (!_displayLinkManager.maxRefreshRateEnabledOnIPhone) {
213 double maxFrameRate = fmax(refreshRate, 60);
214 double minFrameRate = fmax(maxFrameRate / 2, 60);
216 CAFrameRateRangeMake(forceMax ? maxFrameRate : minFrameRate, maxFrameRate, maxFrameRate);
219- (void)onDisplayLink:(CADisplayLink*)link {
220 _didSetContentsDuringThisDisplayLinkPeriod = NO;
222 if (_displayLinkPauseCountdown == 3) {
224 if (_displayLinkForcedMaxRate) {
225 [
self setMaxRefreshRate:_displayLinkManager.displayRefreshRate forceMax:NO];
226 _displayLinkForcedMaxRate = NO;
229 ++_displayLinkPauseCountdown;
233- (
BOOL)isKindOfClass:(Class)aClass {
234#pragma clang diagnostic push
235#pragma clang diagnostic ignored "-Wunguarded-availability-new"
237 if ([aClass isEqual:[CAMetalLayer class]]) {
240#pragma clang diagnostic pop
241 return [
super isKindOfClass:aClass];
244- (void)setDrawableSize:(CGSize)drawableSize {
245 @
synchronized(
self) {
246 [_availableTextures removeAllObjects];
249 _drawableSize = drawableSize;
253- (void)didEnterBackground:(
id)notification {
254 @
synchronized(
self) {
255 [_availableTextures removeAllObjects];
256 _totalTextures = _front != nil ? 1 : 0;
261- (CGSize)drawableSize {
262 @
synchronized(
self) {
263 return _drawableSize;
267- (IOSurface*)createIOSurface {
268 unsigned pixelFormat;
269 unsigned bytesPerElement;
270 if (
self.pixelFormat == MTLPixelFormatRGBA16Float) {
271 pixelFormat = kCVPixelFormatType_64RGBAHalf;
273 }
else if (
self.pixelFormat == MTLPixelFormatBGRA8Unorm) {
274 pixelFormat = kCVPixelFormatType_32BGRA;
276 }
else if (
self.pixelFormat == MTLPixelFormatBGRA10_XR) {
277 pixelFormat = kCVPixelFormatType_40ARGBLEWideGamut;
280 NSString* errorMessage =
281 [NSString stringWithFormat:@"Unsupported pixel format: %lu", self.pixelFormat];
282 [FlutterLogger logError:errorMessage];
286 IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, _drawableSize.width * bytesPerElement);
288 IOSurfaceAlignProperty(kIOSurfaceAllocSize, _drawableSize.height * bytesPerRow);
289 NSDictionary* options = @{
290 (
id)kIOSurfaceWidth : @(_drawableSize.
width),
291 (
id)kIOSurfaceHeight : @(_drawableSize.
height),
292 (
id)kIOSurfacePixelFormat : @(pixelFormat),
293 (
id)kIOSurfaceBytesPerElement : @(bytesPerElement),
294 (
id)kIOSurfaceBytesPerRow : @(bytesPerRow),
295 (
id)kIOSurfaceAllocSize : @(totalBytes),
298 IOSurfaceRef res = IOSurfaceCreate((CFDictionaryRef)options);
300 NSString* errorMessage = [NSString
301 stringWithFormat:@"Failed to create IOSurface with options %@", options.debugDescription];
302 [FlutterLogger logError:errorMessage];
306 if (
self.colorspace != nil) {
307 CFStringRef
name = CGColorSpaceGetName(
self.colorspace);
308 IOSurfaceSetValue(res, kIOSurfaceColorSpace,
name);
310 IOSurfaceSetValue(res, kIOSurfaceColorSpace, kCGColorSpaceSRGB);
312 return (__bridge_transfer IOSurface*)res;
316 CFTimeInterval
start = CACurrentMediaTime();
322 CFTimeInterval elapsed = CACurrentMediaTime() -
start;
324 NSLog(
@"Waited %f seconds for a drawable, giving up.", elapsed);
331 @
synchronized(
self) {
332 if (_front != nil && _front.waitingForCompletion) {
335 if (_totalTextures < 3) {
337 IOSurface*
surface = [
self createIOSurface];
338 if (surface == nil) {
341 MTLTextureDescriptor* textureDescriptor =
342 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:_pixelFormat
343 width:_drawableSize.width
344 height:_drawableSize.height
347 if (_framebufferOnly) {
348 textureDescriptor.usage = MTLTextureUsageRenderTarget;
350 textureDescriptor.usage =
351 MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
353 id<MTLTexture>
texture = [
self.device newTextureWithDescriptor:textureDescriptor
354 iosurface:(__bridge IOSurfaceRef)surface
358 return flutterTexture;
382 [_availableTextures removeObject:res];
389- (
id<CAMetalDrawable>)nextDrawable {
396 drawableId:_nextDrawableId++];
401 if (
texture.texture.width != _drawableSize.width ||
402 texture.texture.height != _drawableSize.height) {
411 [
self setNeedsDisplay];
413 [CATransaction begin];
414 [CATransaction setDisableActions:YES];
416 [CATransaction commit];
418 _displayLinkPauseCountdown = 0;
419 if (!_didSetContentsDuringThisDisplayLinkPeriod) {
420 _didSetContentsDuringThisDisplayLinkPeriod = YES;
421 }
else if (!_displayLinkForcedMaxRate) {
422 _displayLinkForcedMaxRate = YES;
423 [
self setMaxRefreshRate:_displayLinkManager.displayRefreshRate forceMax:YES];
428 @
synchronized(
self) {
429 if (
texture.texture.width != _drawableSize.width ||
430 texture.texture.height != _drawableSize.height) {
434 [_availableTextures addObject:_front];
438 if ([NSThread isMainThread]) {
439 [
self presentOnMainThread:texture];
442 dispatch_async(dispatch_get_main_queue(), ^{
443 [
self presentOnMainThread:texture];
453 @
synchronized(
self) {
454 if (
texture.texture.width == _drawableSize.width &&
455 texture.texture.height == _drawableSize.height) {
456 [_availableTextures addObject:texture];
462 static BOOL enabled = YES;
463 static BOOL didCheckInfoPlist = NO;
464 if (!didCheckInfoPlist) {
465 didCheckInfoPlist = YES;
466 NSNumber* use_flutter_metal_layer =
467 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTUseFlutterMetalLayer"];
468 if (use_flutter_metal_layer != nil && ![use_flutter_metal_layer boolValue]) {
id< FlutterTexture > _texture
FlutterDisplayLink * _displayLink
BOOL paused
Pauses and resumes the display link.
FlutterTexture * _texture
__weak FlutterMetalLayer * _layer
BOOL waitingForCompletion
CFTimeInterval presentedTime