7 #include <IOSurface/IOSurfaceObjC.h>
8 #include <Metal/Metal.h>
9 #include <UIKit/UIKit.h>
11 #include "flutter/fml/logging.h"
53 - (void)onDisplayLink:(CADisplayLink*)link;
65 @property(readonly, nonatomic) id<MTLTexture> texture;
66 @property(readonly, nonatomic) IOSurface* surface;
67 @property(readwrite, nonatomic) CFTimeInterval presentedTime;
68 @property(readwrite, atomic) BOOL waitingForCompletion;
79 - (instancetype)initWithTexture:(
id<MTLTexture>)texture surface:(IOSurface*)surface {
80 if (
self = [super init]) {
98 drawableId:(NSUInteger)drawableId;
106 drawableId:(NSUInteger)drawableId {
107 if (
self = [super init]) {
115 - (id<MTLTexture>)texture {
119 #pragma clang diagnostic push
120 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
121 - (CAMetalLayer*)layer {
122 return (
id)
self->_layer;
124 #pragma clang diagnostic pop
126 - (NSUInteger)drawableID {
127 return self->_drawableId;
130 - (CFTimeInterval)presentedTime {
135 [_layer presentTexture:self->_texture];
136 self->_presented = YES;
141 [_layer returnTexture:self->_texture];
145 - (void)addPresentedHandler:(nonnull MTLDrawablePresentedHandler)block {
146 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement addPresentedHandler:";
149 - (void)presentAtTime:(CFTimeInterval)presentationTime {
150 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement presentAtTime:";
153 - (void)presentAfterMinimumDuration:(CFTimeInterval)duration {
154 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement presentAfterMinimumDuration:";
157 - (void)flutterPrepareForPresent:(nonnull
id<MTLCommandBuffer>)commandBuffer {
160 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
161 texture.waitingForCompletion = NO;
175 if (
self = [super init]) {
181 - (void)onDisplayLink:(CADisplayLink*)link {
182 [_layer onDisplayLink:link];
190 @synthesize device = _device;
196 - (instancetype)init {
197 if (
self = [super init]) {
198 _preferredDevice = MTLCreateSystemDefaultDevice();
199 self.device =
self.preferredDevice;
200 self.pixelFormat = MTLPixelFormatBGRA8Unorm;
201 _availableTextures = [[NSMutableSet alloc] init];
205 _displayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(onDisplayLink:)];
206 [
self setMaxRefreshRate:DisplayLinkManager.displayRefreshRate forceMax:NO];
207 [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
208 [[NSNotificationCenter defaultCenter] addObserver:self
209 selector:@selector(didEnterBackground:)
210 name:UIApplicationDidEnterBackgroundNotification
217 [_displayLink invalidate];
218 [[NSNotificationCenter defaultCenter] removeObserver:self];
221 - (void)setMaxRefreshRate:(
double)refreshRate forceMax:(BOOL)forceMax {
229 double maxFrameRate = fmax(refreshRate, 60);
230 double minFrameRate = fmax(maxFrameRate / 2, 60);
231 if (@available(iOS 15.0, *)) {
233 CAFrameRateRangeMake(forceMax ? maxFrameRate : minFrameRate, maxFrameRate, maxFrameRate);
239 - (void)onDisplayLink:(CADisplayLink*)link {
240 _didSetContentsDuringThisDisplayLinkPeriod = NO;
242 if (_displayLinkPauseCountdown == 3) {
244 if (_displayLinkForcedMaxRate) {
245 [
self setMaxRefreshRate:DisplayLinkManager.displayRefreshRate forceMax:NO];
246 _displayLinkForcedMaxRate = NO;
249 ++_displayLinkPauseCountdown;
253 - (BOOL)isKindOfClass:(Class)aClass {
254 #pragma clang diagnostic push
255 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
257 if ([aClass isEqual:[CAMetalLayer
class]]) {
260 #pragma clang diagnostic pop
261 return [
super isKindOfClass:aClass];
264 - (void)setDrawableSize:(CGSize)drawableSize {
265 [_availableTextures removeAllObjects];
271 - (void)didEnterBackground:(
id)notification {
272 [_availableTextures removeAllObjects];
273 _totalTextures = _front != nil ? 1 : 0;
278 return _drawableSize;
281 - (IOSurface*)createIOSurface {
283 unsigned bytesPerElement;
284 if (
self.
pixelFormat == MTLPixelFormatRGBA16Float) {
287 }
else if (
self.
pixelFormat == MTLPixelFormatBGRA8Unorm) {
290 }
else if (
self.
pixelFormat == MTLPixelFormatBGRA10_XR) {
291 pixelFormat = kCVPixelFormatType_40ARGBLEWideGamut;
294 FML_LOG(ERROR) <<
"Unsupported pixel format: " <<
self.pixelFormat;
298 IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, _drawableSize.width * bytesPerElement);
300 IOSurfaceAlignProperty(kIOSurfaceAllocSize, _drawableSize.height * bytesPerRow);
301 NSDictionary* options = @{
302 (id)kIOSurfaceWidth : @(_drawableSize.width),
303 (id)kIOSurfaceHeight : @(_drawableSize.height),
305 (id)kIOSurfaceBytesPerElement : @(bytesPerElement),
306 (id)kIOSurfaceBytesPerRow : @(bytesPerRow),
307 (id)kIOSurfaceAllocSize : @(totalBytes),
310 IOSurfaceRef res = IOSurfaceCreate((CFDictionaryRef)options);
312 FML_LOG(ERROR) <<
"Failed to create IOSurface with options "
313 << options.debugDescription.UTF8String;
318 CFStringRef name = CGColorSpaceGetName(
self.
colorspace);
319 IOSurfaceSetValue(res, CFSTR(
"IOSurfaceColorSpace"), name);
321 IOSurfaceSetValue(res, CFSTR(
"IOSurfaceColorSpace"), kCGColorSpaceSRGB);
323 return (__bridge_transfer IOSurface*)res;
327 CFTimeInterval start = CACurrentMediaTime();
330 if (texture != nil) {
333 CFTimeInterval elapsed = CACurrentMediaTime() - start;
335 NSLog(
@"Waited %f seconds for a drawable, giving up.", elapsed);
342 @
synchronized(
self) {
343 if (_front != nil && _front.waitingForCompletion) {
346 if (_totalTextures < 3) {
348 IOSurface* surface = [
self createIOSurface];
349 if (surface == nil) {
352 MTLTextureDescriptor* textureDescriptor =
353 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:_pixelFormat
354 width:_drawableSize.width
355 height:_drawableSize.height
358 if (_framebufferOnly) {
359 textureDescriptor.usage = MTLTextureUsageRenderTarget;
361 textureDescriptor.usage =
362 MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
364 id<MTLTexture> texture = [
self.device newTextureWithDescriptor:textureDescriptor
365 iosurface:(__bridge IOSurfaceRef)surface
369 return flutterTexture;
393 [_availableTextures removeObject:res];
402 if (texture == nil) {
407 drawableId:_nextDrawableId++];
414 [
self setNeedsDisplay];
416 [CATransaction begin];
417 [CATransaction setDisableActions:YES];
418 self.contents = texture.
surface;
419 [CATransaction commit];
421 _displayLinkPauseCountdown = 0;
422 if (!_didSetContentsDuringThisDisplayLinkPeriod) {
423 _didSetContentsDuringThisDisplayLinkPeriod = YES;
424 }
else if (!_displayLinkForcedMaxRate) {
425 _displayLinkForcedMaxRate = YES;
426 [
self setMaxRefreshRate:DisplayLinkManager.displayRefreshRate forceMax:YES];
431 @
synchronized(
self) {
433 [_availableTextures addObject:_front];
437 if ([NSThread isMainThread]) {
438 [
self presentOnMainThread:texture];
441 dispatch_async(dispatch_get_main_queue(), ^{
442 [
self presentOnMainThread:texture];
449 @
synchronized(
self) {
450 [_availableTextures addObject:texture];
456 static BOOL didCheckInfoPlist = NO;
457 if (!didCheckInfoPlist) {
458 didCheckInfoPlist = YES;
459 NSNumber* use_flutter_metal_layer =
460 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTUseFlutterMetalLayer"];
461 if (use_flutter_metal_layer != nil && ![use_flutter_metal_layer boolValue]) {