5 #include <IOSurface/IOSurfaceObjC.h>
6 #include <Metal/Metal.h>
7 #include <UIKit/UIKit.h>
9 #include "flutter/fml/logging.h"
60 @property(readonly, nonatomic) id<MTLTexture> texture;
61 @property(readonly, nonatomic) IOSurface* surface;
62 @property(readwrite, nonatomic) CFTimeInterval presentedTime;
63 @property(readwrite, atomic) BOOL waitingForCompletion;
74 - (instancetype)initWithTexture:(
id<MTLTexture>)texture surface:(IOSurface*)surface {
75 if (
self = [super init]) {
93 drawableId:(NSUInteger)drawableId;
101 drawableId:(NSUInteger)drawableId {
102 if (
self = [super init]) {
110 - (id<MTLTexture>)texture {
114 #pragma clang diagnostic push
115 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
116 - (CAMetalLayer*)layer {
117 return (
id)
self->_layer;
119 #pragma clang diagnostic pop
121 - (NSUInteger)drawableID {
122 return self->_drawableId;
125 - (CFTimeInterval)presentedTime {
130 [_layer presentTexture:self->_texture];
131 self->_presented = YES;
136 [_layer returnTexture:self->_texture];
140 - (void)addPresentedHandler:(nonnull MTLDrawablePresentedHandler)block {
141 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement addPresentedHandler:";
144 - (void)presentAtTime:(CFTimeInterval)presentationTime {
145 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement presentAtTime:";
148 - (void)presentAfterMinimumDuration:(CFTimeInterval)duration {
149 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement presentAfterMinimumDuration:";
152 - (void)flutterPrepareForPresent:(nonnull
id<MTLCommandBuffer>)commandBuffer {
155 [commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
156 texture.waitingForCompletion = NO;
165 @synthesize device = _device;
171 - (instancetype)init {
172 if (
self = [super init]) {
173 _preferredDevice = MTLCreateSystemDefaultDevice();
174 self.device =
self.preferredDevice;
175 self.pixelFormat = MTLPixelFormatBGRA8Unorm;
176 _availableTextures = [[NSMutableSet alloc] init];
178 _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)];
180 [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
181 [[NSNotificationCenter defaultCenter] addObserver:self
182 selector:@selector(didEnterBackground:)
183 name:UIApplicationDidEnterBackgroundNotification
190 [[NSNotificationCenter defaultCenter] removeObserver:self];
193 - (void)setMaxRefreshRate:(
double)refreshRate forceMax:(BOOL)forceMax {
201 double maxFrameRate = fmax(refreshRate, 60);
202 double minFrameRate = fmax(maxFrameRate / 2, 60);
203 if (@available(iOS 15.0, *)) {
204 _displayLink.preferredFrameRateRange =
205 CAFrameRateRangeMake(forceMax ? maxFrameRate : minFrameRate, maxFrameRate, maxFrameRate);
207 _displayLink.preferredFramesPerSecond = maxFrameRate;
211 - (void)onDisplayLink:(CADisplayLink*)link {
212 _didSetContentsDuringThisDisplayLinkPeriod = NO;
214 if (_displayLinkPauseCountdown == 3) {
215 _displayLink.paused = YES;
216 if (_displayLinkForcedMaxRate) {
218 _displayLinkForcedMaxRate = NO;
221 ++_displayLinkPauseCountdown;
225 - (BOOL)isKindOfClass:(Class)aClass {
226 #pragma clang diagnostic push
227 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
229 if ([aClass isEqual:[CAMetalLayer
class]]) {
232 #pragma clang diagnostic pop
233 return [
super isKindOfClass:aClass];
236 - (void)setDrawableSize:(CGSize)drawableSize {
237 [_availableTextures removeAllObjects];
243 - (void)didEnterBackground:(
id)notification {
244 [_availableTextures removeAllObjects];
245 _totalTextures = _front != nil ? 1 : 0;
246 _displayLink.paused = YES;
250 return _drawableSize;
253 - (IOSurface*)createIOSurface {
255 unsigned bytesPerElement;
256 if (
self.
pixelFormat == MTLPixelFormatRGBA16Float) {
259 }
else if (
self.
pixelFormat == MTLPixelFormatBGRA8Unorm) {
263 FML_LOG(ERROR) <<
"Unsupported pixel format: " <<
self.pixelFormat;
267 IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, _drawableSize.width * bytesPerElement);
269 IOSurfaceAlignProperty(kIOSurfaceAllocSize, _drawableSize.height * bytesPerRow);
270 NSDictionary* options = @{
271 (id)kIOSurfaceWidth : @(_drawableSize.width),
272 (id)kIOSurfaceHeight : @(_drawableSize.height),
274 (id)kIOSurfaceBytesPerElement : @(bytesPerElement),
275 (id)kIOSurfaceBytesPerRow : @(bytesPerRow),
276 (id)kIOSurfaceAllocSize : @(totalBytes),
279 IOSurfaceRef res = IOSurfaceCreate((CFDictionaryRef)options);
281 FML_LOG(ERROR) <<
"Failed to create IOSurface with options "
282 << options.debugDescription.UTF8String;
287 CFStringRef name = CGColorSpaceGetName(
self.
colorspace);
288 IOSurfaceSetValue(res, CFSTR(
"IOSurfaceColorSpace"), name);
290 IOSurfaceSetValue(res, CFSTR(
"IOSurfaceColorSpace"), kCGColorSpaceSRGB);
292 return (__bridge_transfer IOSurface*)res;
296 CFTimeInterval start = CACurrentMediaTime();
299 if (texture != nil) {
302 CFTimeInterval elapsed = CACurrentMediaTime() - start;
304 NSLog(
@"Waited %f seconds for a drawable, giving up.", elapsed);
311 @
synchronized(
self) {
312 if (_front != nil && _front.waitingForCompletion) {
315 if (_totalTextures < 3) {
317 IOSurface* surface = [
self createIOSurface];
318 if (surface == nil) {
321 MTLTextureDescriptor* textureDescriptor =
322 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:_pixelFormat
323 width:_drawableSize.width
324 height:_drawableSize.height
327 if (_framebufferOnly) {
328 textureDescriptor.usage = MTLTextureUsageRenderTarget;
330 textureDescriptor.usage =
331 MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
333 id<MTLTexture> texture = [
self.device newTextureWithDescriptor:textureDescriptor
334 iosurface:(__bridge IOSurfaceRef)surface
338 return flutterTexture;
362 [_availableTextures removeObject:res];
371 if (texture == nil) {
376 drawableId:_nextDrawableId++];
383 [
self setNeedsDisplay];
385 [CATransaction begin];
386 [CATransaction setDisableActions:YES];
387 self.contents = texture.
surface;
388 [CATransaction commit];
389 _displayLink.paused = NO;
390 _displayLinkPauseCountdown = 0;
391 if (!_didSetContentsDuringThisDisplayLinkPeriod) {
392 _didSetContentsDuringThisDisplayLinkPeriod = YES;
393 }
else if (!_displayLinkForcedMaxRate) {
394 _displayLinkForcedMaxRate = YES;
400 @
synchronized(
self) {
402 [_availableTextures addObject:_front];
406 if ([NSThread isMainThread]) {
407 [
self presentOnMainThread:texture];
410 dispatch_async(dispatch_get_main_queue(), ^{
411 [
self presentOnMainThread:texture];
418 @
synchronized(
self) {
419 [_availableTextures addObject:texture];
425 static BOOL didCheckInfoPlist = NO;
426 if (!didCheckInfoPlist) {
427 didCheckInfoPlist = YES;
428 NSNumber* use_flutter_metal_layer =
429 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTUseFlutterMetalLayer"];
430 if (use_flutter_metal_layer != nil && [use_flutter_metal_layer boolValue]) {
432 FML_LOG(WARNING) <<
"Using FlutterMetalLayer. This is an experimental feature.";