7 #include <CoreMedia/CoreMedia.h>
8 #include <IOSurface/IOSurfaceObjC.h>
9 #include <Metal/Metal.h>
10 #include <UIKit/UIKit.h>
12 #include "flutter/fml/logging.h"
54 - (void)onDisplayLink:(CADisplayLink*)link;
62 @property(readonly, nonatomic) id<MTLTexture> texture;
63 @property(readonly, nonatomic) IOSurface* surface;
64 @property(readwrite, nonatomic) CFTimeInterval presentedTime;
65 @property(readwrite, atomic) BOOL waitingForCompletion;
71 - (instancetype)initWithTexture:(
id<MTLTexture>)texture surface:(IOSurface*)surface {
72 if (
self = [super init]) {
90 drawableId:(NSUInteger)drawableId;
98 drawableId:(NSUInteger)drawableId {
99 if (
self = [super init]) {
107 - (id<MTLTexture>)texture {
108 return self->_texture.texture;
111 #pragma clang diagnostic push
112 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
113 - (CAMetalLayer*)layer {
114 return (
id)
self->_layer;
116 #pragma clang diagnostic pop
118 - (NSUInteger)drawableID {
119 return self->_drawableId;
122 - (CFTimeInterval)presentedTime {
127 [_layer presentTexture:self->_texture];
128 self->_presented = YES;
133 [_layer returnTexture:self->_texture];
137 - (void)addPresentedHandler:(nonnull MTLDrawablePresentedHandler)block {
138 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement addPresentedHandler:";
141 - (void)presentAtTime:(CFTimeInterval)presentationTime {
142 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement presentAtTime:";
145 - (void)presentAfterMinimumDuration:(CFTimeInterval)duration {
146 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement presentAfterMinimumDuration:";
149 - (void)flutterPrepareForPresent:(nonnull
id<MTLCommandBuffer>)commandBuffer {
152 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
153 texture.waitingForCompletion = NO;
167 if (
self = [super init]) {
173 - (void)onDisplayLink:(CADisplayLink*)link {
174 [_layer onDisplayLink:link];
181 - (instancetype)init {
182 if (
self = [super init]) {
183 _preferredDevice = MTLCreateSystemDefaultDevice();
184 self.device =
self.preferredDevice;
185 self.pixelFormat = MTLPixelFormatBGRA8Unorm;
186 _availableTextures = [[NSMutableSet alloc] init];
190 _displayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(onDisplayLink:)];
191 [
self setMaxRefreshRate:DisplayLinkManager.displayRefreshRate forceMax:NO];
192 [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
193 [[NSNotificationCenter defaultCenter] addObserver:self
194 selector:@selector(didEnterBackground:)
195 name:UIApplicationDidEnterBackgroundNotification
202 [_displayLink invalidate];
203 [[NSNotificationCenter defaultCenter] removeObserver:self];
206 - (void)setMaxRefreshRate:(
double)refreshRate forceMax:(BOOL)forceMax {
214 double maxFrameRate = fmax(refreshRate, 60);
215 double minFrameRate = fmax(maxFrameRate / 2, 60);
216 if (@available(iOS 15.0, *)) {
218 CAFrameRateRangeMake(forceMax ? maxFrameRate : minFrameRate, maxFrameRate, maxFrameRate);
224 - (void)onDisplayLink:(CADisplayLink*)link {
225 _didSetContentsDuringThisDisplayLinkPeriod = NO;
227 if (_displayLinkPauseCountdown == 3) {
229 if (_displayLinkForcedMaxRate) {
230 [
self setMaxRefreshRate:DisplayLinkManager.displayRefreshRate forceMax:NO];
231 _displayLinkForcedMaxRate = NO;
234 ++_displayLinkPauseCountdown;
238 - (BOOL)isKindOfClass:(Class)aClass {
239 #pragma clang diagnostic push
240 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
242 if ([aClass isEqual:[CAMetalLayer
class]]) {
245 #pragma clang diagnostic pop
246 return [
super isKindOfClass:aClass];
249 - (void)setDrawableSize:(CGSize)drawableSize {
250 [_availableTextures removeAllObjects];
256 - (void)didEnterBackground:(
id)notification {
257 [_availableTextures removeAllObjects];
258 _totalTextures = _front != nil ? 1 : 0;
263 return _drawableSize;
266 - (IOSurface*)createIOSurface {
268 unsigned bytesPerElement;
269 if (
self.
pixelFormat == MTLPixelFormatRGBA16Float) {
272 }
else if (
self.
pixelFormat == MTLPixelFormatBGRA8Unorm) {
275 }
else if (
self.
pixelFormat == MTLPixelFormatBGRA10_XR) {
276 pixelFormat = kCVPixelFormatType_40ARGBLEWideGamut;
279 FML_LOG(ERROR) <<
"Unsupported pixel format: " <<
self.pixelFormat;
283 IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, _drawableSize.width * bytesPerElement);
285 IOSurfaceAlignProperty(kIOSurfaceAllocSize, _drawableSize.height * bytesPerRow);
286 NSDictionary* options = @{
287 (id)kIOSurfaceWidth : @(_drawableSize.width),
288 (id)kIOSurfaceHeight : @(_drawableSize.height),
290 (id)kIOSurfaceBytesPerElement : @(bytesPerElement),
291 (id)kIOSurfaceBytesPerRow : @(bytesPerRow),
292 (id)kIOSurfaceAllocSize : @(totalBytes),
295 IOSurfaceRef res = IOSurfaceCreate((CFDictionaryRef)options);
297 FML_LOG(ERROR) <<
"Failed to create IOSurface with options "
298 << options.debugDescription.UTF8String;
303 CFStringRef name = CGColorSpaceGetName(
self.
colorspace);
304 IOSurfaceSetValue(res, kIOSurfaceColorSpace, name);
306 IOSurfaceSetValue(res, kIOSurfaceColorSpace, kCGColorSpaceSRGB);
308 return (__bridge_transfer IOSurface*)res;
312 CFTimeInterval start = CACurrentMediaTime();
315 if (texture != nil) {
318 CFTimeInterval elapsed = CACurrentMediaTime() - start;
320 NSLog(
@"Waited %f seconds for a drawable, giving up.", elapsed);
327 @
synchronized(
self) {
328 if (_front != nil && _front.waitingForCompletion) {
331 if (_totalTextures < 3) {
333 IOSurface* surface = [
self createIOSurface];
334 if (surface == nil) {
337 MTLTextureDescriptor* textureDescriptor =
338 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:_pixelFormat
339 width:_drawableSize.width
340 height:_drawableSize.height
343 if (_framebufferOnly) {
344 textureDescriptor.usage = MTLTextureUsageRenderTarget;
346 textureDescriptor.usage =
347 MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
349 id<MTLTexture> texture = [
self.device newTextureWithDescriptor:textureDescriptor
350 iosurface:(__bridge IOSurfaceRef)surface
354 return flutterTexture;
378 [_availableTextures removeObject:res];
387 if (texture == nil) {
392 drawableId:_nextDrawableId++];
399 [
self setNeedsDisplay];
401 [CATransaction begin];
402 [CATransaction setDisableActions:YES];
403 self.contents = texture.
surface;
404 [CATransaction commit];
406 _displayLinkPauseCountdown = 0;
407 if (!_didSetContentsDuringThisDisplayLinkPeriod) {
408 _didSetContentsDuringThisDisplayLinkPeriod = YES;
409 }
else if (!_displayLinkForcedMaxRate) {
410 _displayLinkForcedMaxRate = YES;
411 [
self setMaxRefreshRate:DisplayLinkManager.displayRefreshRate forceMax:YES];
416 @
synchronized(
self) {
418 [_availableTextures addObject:_front];
422 if ([NSThread isMainThread]) {
423 [
self presentOnMainThread:texture];
426 dispatch_async(dispatch_get_main_queue(), ^{
427 [
self presentOnMainThread:texture];
434 @
synchronized(
self) {
435 [_availableTextures addObject:texture];
441 static BOOL didCheckInfoPlist = NO;
442 if (!didCheckInfoPlist) {
443 didCheckInfoPlist = YES;
444 NSNumber* use_flutter_metal_layer =
445 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTUseFlutterMetalLayer"];
446 if (use_flutter_metal_layer != nil && ![use_flutter_metal_layer boolValue]) {