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]) {
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 {
148 texture.waitingForCompletion = YES;
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();
181 self.device =
self.preferredDevice;
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 _displayLink.preferredFrameRateRange =
214 CAFrameRateRangeMake(forceMax ? maxFrameRate : minFrameRate, maxFrameRate, maxFrameRate);
217 - (void)onDisplayLink:(CADisplayLink*)link {
218 _didSetContentsDuringThisDisplayLinkPeriod = NO;
220 if (_displayLinkPauseCountdown == 3) {
221 _displayLink.paused = YES;
222 if (_displayLinkForcedMaxRate) {
223 [
self setMaxRefreshRate:FlutterDisplayLinkManager.displayRefreshRate forceMax:NO];
224 _displayLinkForcedMaxRate = NO;
227 ++_displayLinkPauseCountdown;
231 - (BOOL)isKindOfClass:(Class)aClass {
232 #pragma clang diagnostic push
233 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
235 if ([aClass isEqual:[CAMetalLayer
class]]) {
238 #pragma clang diagnostic pop
239 return [
super isKindOfClass:aClass];
242 - (void)setDrawableSize:(CGSize)drawableSize {
243 @
synchronized(
self) {
244 [_availableTextures removeAllObjects];
251 - (void)didEnterBackground:(
id)notification {
252 @
synchronized(
self) {
253 [_availableTextures removeAllObjects];
254 _totalTextures = _front != nil ? 1 : 0;
256 _displayLink.paused = YES;
260 @
synchronized(
self) {
261 return _drawableSize;
265 - (IOSurface*)createIOSurface {
267 unsigned bytesPerElement;
268 if (
self.
pixelFormat == MTLPixelFormatRGBA16Float) {
271 }
else if (
self.
pixelFormat == MTLPixelFormatBGRA8Unorm) {
274 }
else if (
self.
pixelFormat == MTLPixelFormatBGRA10_XR) {
275 pixelFormat = kCVPixelFormatType_40ARGBLEWideGamut;
278 NSString* errorMessage =
279 [NSString stringWithFormat:@"Unsupported pixel format: %lu", self.pixelFormat];
280 [FlutterLogger logError:errorMessage];
284 IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, _drawableSize.width * bytesPerElement);
286 IOSurfaceAlignProperty(kIOSurfaceAllocSize, _drawableSize.height * bytesPerRow);
287 NSDictionary* options = @{
288 (id)kIOSurfaceWidth : @(_drawableSize.width),
289 (id)kIOSurfaceHeight : @(_drawableSize.height),
291 (id)kIOSurfaceBytesPerElement : @(bytesPerElement),
292 (id)kIOSurfaceBytesPerRow : @(bytesPerRow),
293 (id)kIOSurfaceAllocSize : @(totalBytes),
296 IOSurfaceRef res = IOSurfaceCreate((CFDictionaryRef)options);
298 NSString* errorMessage = [NSString
299 stringWithFormat:@"Failed to create IOSurface with options %@", options.debugDescription];
300 [FlutterLogger logError:errorMessage];
305 CFStringRef name = CGColorSpaceGetName(
self.
colorspace);
306 IOSurfaceSetValue(res, kIOSurfaceColorSpace, name);
308 IOSurfaceSetValue(res, kIOSurfaceColorSpace, kCGColorSpaceSRGB);
310 return (__bridge_transfer IOSurface*)res;
314 CFTimeInterval start = CACurrentMediaTime();
320 CFTimeInterval elapsed = CACurrentMediaTime() - start;
322 NSLog(
@"Waited %f seconds for a drawable, giving up.", elapsed);
329 @
synchronized(
self) {
330 if (_front != nil && _front.waitingForCompletion) {
333 if (_totalTextures < 3) {
335 IOSurface*
surface = [
self createIOSurface];
339 MTLTextureDescriptor* textureDescriptor =
340 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:_pixelFormat
341 width:_drawableSize.width
342 height:_drawableSize.height
345 if (_framebufferOnly) {
346 textureDescriptor.usage = MTLTextureUsageRenderTarget;
348 textureDescriptor.usage =
349 MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
351 id<MTLTexture>
texture = [
self.device newTextureWithDescriptor:textureDescriptor
352 iosurface:(__bridge IOSurfaceRef)surface
356 return flutterTexture;
380 [_availableTextures removeObject:res];
394 drawableId:_nextDrawableId++];
399 if (
texture.texture.width != _drawableSize.width ||
400 texture.texture.height != _drawableSize.height) {
409 [
self setNeedsDisplay];
411 [CATransaction begin];
412 [CATransaction setDisableActions:YES];
413 self.contents =
texture.surface;
414 [CATransaction commit];
415 _displayLink.paused = NO;
416 _displayLinkPauseCountdown = 0;
417 if (!_didSetContentsDuringThisDisplayLinkPeriod) {
418 _didSetContentsDuringThisDisplayLinkPeriod = YES;
419 }
else if (!_displayLinkForcedMaxRate) {
420 _displayLinkForcedMaxRate = YES;
421 [
self setMaxRefreshRate:FlutterDisplayLinkManager.displayRefreshRate forceMax:YES];
426 @
synchronized(
self) {
427 if (
texture.texture.width != _drawableSize.width ||
428 texture.texture.height != _drawableSize.height) {
432 [_availableTextures addObject:_front];
435 texture.presentedTime = CACurrentMediaTime();
436 if ([NSThread isMainThread]) {
437 [
self presentOnMainThread:texture];
440 dispatch_async(dispatch_get_main_queue(), ^{
441 [
self presentOnMainThread:texture];
451 @
synchronized(
self) {
452 if (
texture.texture.width == _drawableSize.width &&
453 texture.texture.height == _drawableSize.height) {
454 [_availableTextures addObject:texture];
461 static BOOL didCheckInfoPlist = NO;
462 if (!didCheckInfoPlist) {
463 didCheckInfoPlist = YES;
464 NSNumber* use_flutter_metal_layer =
465 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTUseFlutterMetalLayer"];
466 if (use_flutter_metal_layer != nil && ![use_flutter_metal_layer boolValue]) {
FlutterTexture * _texture
__weak FlutterMetalLayer * _layer
BOOL waitingForCompletion
CFTimeInterval presentedTime