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]) {
BOOL maxRefreshRateEnabledOnIPhone
Whether the max refresh rate on iPhone ProMotion devices are enabled. This reflects the value of CADi...
double displayRefreshRate
The display refresh rate used for reporting purposes. The engine does not care about this for frame s...
FlutterTexture * _texture
__weak FlutterMetalLayer * _layer
BOOL waitingForCompletion
CFTimeInterval presentedTime
CADisplayLink * _displayLink