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"
50- (void)onDisplayLink:(CADisplayLink*)link;
58@property(readonly, nonatomic) id<MTLTexture>
texture;
59@property(readonly, nonatomic) IOSurface*
surface;
67- (instancetype)initWithTexture:(
id<MTLTexture>)texture surface:(IOSurface*)surface {
68 if (
self = [super init]) {
86 drawableId:(NSUInteger)drawableId;
94 drawableId:(NSUInteger)drawableId {
95 if (
self = [super init]) {
98 _drawableId = drawableId;
104 return self->_texture.texture;
107#pragma clang diagnostic push
108#pragma clang diagnostic ignored "-Wunguarded-availability-new"
109- (CAMetalLayer*)layer {
110 return (
id)
self->_layer;
112#pragma clang diagnostic pop
114- (NSUInteger)drawableID {
115 return self->_drawableId;
123 [_layer presentTexture:self->_texture];
124 self->_presented = YES;
129 [_layer returnTexture:self->_texture];
133- (void)addPresentedHandler:(nonnull MTLDrawablePresentedHandler)block {
134 [FlutterLogger logWarning:@"FlutterMetalLayer drawable does not implement addPresentedHandler:"];
137- (void)presentAtTime:(CFTimeInterval)presentationTime {
138 [FlutterLogger logWarning:@"FlutterMetalLayer drawable does not implement presentAtTime:"];
141- (void)presentAfterMinimumDuration:(CFTimeInterval)duration {
143 logWarning:@"FlutterMetalLayer drawable does not implement presentAfterMinimumDuration:"];
146- (void)flutterPrepareForPresent:(nonnull
id<MTLCommandBuffer>)commandBuffer {
149 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
150 texture.waitingForCompletion = NO;
164 if (
self = [super init]) {
170- (void)onDisplayLink:(CADisplayLink*)link {
171 [_layer onDisplayLink:link];
178- (instancetype)init {
179 if (
self = [super init]) {
180 _preferredDevice = MTLCreateSystemDefaultDevice();
182 self.pixelFormat = MTLPixelFormatBGRA8Unorm;
183 _availableTextures = [[NSMutableSet alloc] init];
187 _displayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(onDisplayLink:)];
188 [
self setMaxRefreshRate:FlutterDisplayLinkManager.displayRefreshRate forceMax:NO];
189 [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
190 [[NSNotificationCenter defaultCenter] addObserver:self
191 selector:@selector(didEnterBackground:)
192 name:UIApplicationDidEnterBackgroundNotification
199 [_displayLink invalidate];
200 [[NSNotificationCenter defaultCenter] removeObserver:self];
203- (void)setMaxRefreshRate:(
double)refreshRate forceMax:(
BOOL)forceMax {
208 if (!FlutterDisplayLinkManager.maxRefreshRateEnabledOnIPhone) {
211 double maxFrameRate = fmax(refreshRate, 60);
212 double minFrameRate = fmax(maxFrameRate / 2, 60);
213 if (@available(iOS 15.0, *)) {
215 CAFrameRateRangeMake(forceMax ? maxFrameRate : minFrameRate, maxFrameRate, maxFrameRate);
221- (void)onDisplayLink:(CADisplayLink*)link {
222 _didSetContentsDuringThisDisplayLinkPeriod = NO;
224 if (_displayLinkPauseCountdown == 3) {
226 if (_displayLinkForcedMaxRate) {
227 [
self setMaxRefreshRate:FlutterDisplayLinkManager.displayRefreshRate forceMax:NO];
228 _displayLinkForcedMaxRate = NO;
231 ++_displayLinkPauseCountdown;
235- (
BOOL)isKindOfClass:(Class)aClass {
236#pragma clang diagnostic push
237#pragma clang diagnostic ignored "-Wunguarded-availability-new"
239 if ([aClass isEqual:[CAMetalLayer class]]) {
242#pragma clang diagnostic pop
243 return [
super isKindOfClass:aClass];
246- (void)setDrawableSize:(CGSize)drawableSize {
247 @
synchronized(
self) {
248 [_availableTextures removeAllObjects];
251 _drawableSize = drawableSize;
255- (void)didEnterBackground:(
id)notification {
256 @
synchronized(
self) {
257 [_availableTextures removeAllObjects];
258 _totalTextures = _front != nil ? 1 : 0;
263- (CGSize)drawableSize {
264 @
synchronized(
self) {
265 return _drawableSize;
269- (IOSurface*)createIOSurface {
270 unsigned pixelFormat;
271 unsigned bytesPerElement;
272 if (
self.pixelFormat == MTLPixelFormatRGBA16Float) {
273 pixelFormat = kCVPixelFormatType_64RGBAHalf;
275 }
else if (
self.pixelFormat == MTLPixelFormatBGRA8Unorm) {
276 pixelFormat = kCVPixelFormatType_32BGRA;
278 }
else if (
self.pixelFormat == MTLPixelFormatBGRA10_XR) {
279 pixelFormat = kCVPixelFormatType_40ARGBLEWideGamut;
282 NSString* errorMessage =
283 [NSString stringWithFormat:@"Unsupported pixel format: %lu", self.pixelFormat];
284 [FlutterLogger logError:errorMessage];
288 IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, _drawableSize.width * bytesPerElement);
290 IOSurfaceAlignProperty(kIOSurfaceAllocSize, _drawableSize.height * bytesPerRow);
291 NSDictionary* options = @{
292 (
id)kIOSurfaceWidth : @(_drawableSize.
width),
293 (
id)kIOSurfaceHeight : @(_drawableSize.
height),
294 (
id)kIOSurfacePixelFormat : @(pixelFormat),
295 (
id)kIOSurfaceBytesPerElement : @(bytesPerElement),
296 (
id)kIOSurfaceBytesPerRow : @(bytesPerRow),
297 (
id)kIOSurfaceAllocSize : @(totalBytes),
300 IOSurfaceRef res = IOSurfaceCreate((CFDictionaryRef)options);
302 NSString* errorMessage = [NSString
303 stringWithFormat:@"Failed to create IOSurface with options %@", options.debugDescription];
304 [FlutterLogger logError:errorMessage];
308 if (
self.colorspace != nil) {
309 CFStringRef
name = CGColorSpaceGetName(
self.colorspace);
310 IOSurfaceSetValue(res, kIOSurfaceColorSpace,
name);
312 IOSurfaceSetValue(res, kIOSurfaceColorSpace, kCGColorSpaceSRGB);
314 return (__bridge_transfer IOSurface*)res;
318 CFTimeInterval
start = CACurrentMediaTime();
324 CFTimeInterval elapsed = CACurrentMediaTime() -
start;
326 NSLog(
@"Waited %f seconds for a drawable, giving up.", elapsed);
333 @
synchronized(
self) {
334 if (_front != nil && _front.waitingForCompletion) {
337 if (_totalTextures < 3) {
339 IOSurface*
surface = [
self createIOSurface];
340 if (surface == nil) {
343 MTLTextureDescriptor* textureDescriptor =
344 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:_pixelFormat
345 width:_drawableSize.width
346 height:_drawableSize.height
349 if (_framebufferOnly) {
350 textureDescriptor.usage = MTLTextureUsageRenderTarget;
352 textureDescriptor.usage =
353 MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
355 id<MTLTexture>
texture = [
self.device newTextureWithDescriptor:textureDescriptor
356 iosurface:(__bridge IOSurfaceRef)surface
360 return flutterTexture;
384 [_availableTextures removeObject:res];
391- (
id<CAMetalDrawable>)nextDrawable {
398 drawableId:_nextDrawableId++];
403 if (
texture.texture.width != _drawableSize.width ||
404 texture.texture.height != _drawableSize.height) {
413 [
self setNeedsDisplay];
415 [CATransaction begin];
416 [CATransaction setDisableActions:YES];
418 [CATransaction commit];
420 _displayLinkPauseCountdown = 0;
421 if (!_didSetContentsDuringThisDisplayLinkPeriod) {
422 _didSetContentsDuringThisDisplayLinkPeriod = YES;
423 }
else if (!_displayLinkForcedMaxRate) {
424 _displayLinkForcedMaxRate = YES;
425 [
self setMaxRefreshRate:FlutterDisplayLinkManager.displayRefreshRate forceMax:YES];
430 @
synchronized(
self) {
431 if (
texture.texture.width != _drawableSize.width ||
432 texture.texture.height != _drawableSize.height) {
436 [_availableTextures addObject:_front];
440 if ([NSThread isMainThread]) {
441 [
self presentOnMainThread:texture];
444 dispatch_async(dispatch_get_main_queue(), ^{
445 [
self presentOnMainThread:texture];
455 @
synchronized(
self) {
456 if (
texture.texture.width == _drawableSize.width &&
457 texture.texture.height == _drawableSize.height) {
458 [_availableTextures addObject:texture];
464 static BOOL enabled = YES;
465 static BOOL didCheckInfoPlist = NO;
466 if (!didCheckInfoPlist) {
467 didCheckInfoPlist = YES;
468 NSNumber* use_flutter_metal_layer =
469 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTUseFlutterMetalLayer"];
470 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