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;
72 - (instancetype)initWithTexture:(
id<MTLTexture>)texture surface:(IOSurface*)surface {
73 if (
self = [super init]) {
91 drawableId:(NSUInteger)drawableId;
99 drawableId:(NSUInteger)drawableId {
100 if (
self = [super init]) {
108 - (id<MTLTexture>)texture {
112 #pragma clang diagnostic push
113 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
114 - (CAMetalLayer*)layer {
115 return (
id)
self->_layer;
117 #pragma clang diagnostic pop
119 - (NSUInteger)drawableID {
120 return self->_drawableId;
123 - (CFTimeInterval)presentedTime {
128 [_layer presentTexture:self->_texture];
129 self->_presented = YES;
134 [_layer returnTexture:self->_texture];
138 - (void)addPresentedHandler:(nonnull MTLDrawablePresentedHandler)block {
139 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement addPresentedHandler:";
142 - (void)presentAtTime:(CFTimeInterval)presentationTime {
143 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement presentAtTime:";
146 - (void)presentAfterMinimumDuration:(CFTimeInterval)duration {
147 FML_LOG(WARNING) <<
"FlutterMetalLayer drawable does not implement presentAfterMinimumDuration:";
155 @synthesize device = _device;
161 - (instancetype)init {
162 if (
self = [super init]) {
163 _preferredDevice = MTLCreateSystemDefaultDevice();
164 self.device =
self.preferredDevice;
165 self.pixelFormat = MTLPixelFormatBGRA8Unorm;
166 _availableTextures = [[NSMutableSet alloc] init];
168 _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onDisplayLink:)];
170 [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
171 [[NSNotificationCenter defaultCenter] addObserver:self
172 selector:@selector(didEnterBackground:)
173 name:UIApplicationDidEnterBackgroundNotification
180 [[NSNotificationCenter defaultCenter] removeObserver:self];
183 - (void)setMaxRefreshRate:(
double)refreshRate forceMax:(BOOL)forceMax {
191 double maxFrameRate = fmax(refreshRate, 60);
192 double minFrameRate = fmax(maxFrameRate / 2, 60);
193 if (@available(iOS 15.0, *)) {
194 _displayLink.preferredFrameRateRange =
195 CAFrameRateRangeMake(forceMax ? maxFrameRate : minFrameRate, maxFrameRate, maxFrameRate);
197 _displayLink.preferredFramesPerSecond = maxFrameRate;
201 - (void)onDisplayLink:(CADisplayLink*)link {
202 _didSetContentsDuringThisDisplayLinkPeriod = NO;
204 if (_displayLinkPauseCountdown == 3) {
205 _displayLink.paused = YES;
206 if (_displayLinkForcedMaxRate) {
208 _displayLinkForcedMaxRate = NO;
211 ++_displayLinkPauseCountdown;
215 - (BOOL)isKindOfClass:(Class)aClass {
216 #pragma clang diagnostic push
217 #pragma clang diagnostic ignored "-Wunguarded-availability-new"
219 if ([aClass isEqual:[CAMetalLayer
class]]) {
222 #pragma clang diagnostic pop
223 return [
super isKindOfClass:aClass];
226 - (void)setDrawableSize:(CGSize)drawableSize {
227 [_availableTextures removeAllObjects];
233 - (void)didEnterBackground:(
id)notification {
234 [_availableTextures removeAllObjects];
235 _totalTextures = _front != nil ? 1 : 0;
236 _displayLink.paused = YES;
240 return _drawableSize;
243 - (IOSurface*)createIOSurface {
245 unsigned bytesPerElement;
246 if (
self.
pixelFormat == MTLPixelFormatRGBA16Float) {
249 }
else if (
self.
pixelFormat == MTLPixelFormatBGRA8Unorm) {
253 FML_LOG(ERROR) <<
"Unsupported pixel format: " <<
self.pixelFormat;
257 IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, _drawableSize.width * bytesPerElement);
259 IOSurfaceAlignProperty(kIOSurfaceAllocSize, _drawableSize.height * bytesPerRow);
260 NSDictionary* options = @{
261 (id)kIOSurfaceWidth : @(_drawableSize.width),
262 (id)kIOSurfaceHeight : @(_drawableSize.height),
264 (id)kIOSurfaceBytesPerElement : @(bytesPerElement),
265 (id)kIOSurfaceBytesPerRow : @(bytesPerRow),
266 (id)kIOSurfaceAllocSize : @(totalBytes),
269 IOSurfaceRef res = IOSurfaceCreate((CFDictionaryRef)options);
271 FML_LOG(ERROR) <<
"Failed to create IOSurface with options "
272 << options.debugDescription.UTF8String;
277 CFStringRef name = CGColorSpaceGetName(
self.
colorspace);
278 IOSurfaceSetValue(res, CFSTR(
"IOSurfaceColorSpace"), name);
280 IOSurfaceSetValue(res, CFSTR(
"IOSurfaceColorSpace"), kCGColorSpaceSRGB);
282 return (__bridge_transfer IOSurface*)res;
286 @
synchronized(
self) {
287 if (_totalTextures < 3) {
289 IOSurface* surface = [
self createIOSurface];
290 if (surface == nil) {
293 MTLTextureDescriptor* textureDescriptor =
294 [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:_pixelFormat
295 width:_drawableSize.width
296 height:_drawableSize.height
299 if (_framebufferOnly) {
300 textureDescriptor.usage = MTLTextureUsageRenderTarget;
302 textureDescriptor.usage =
303 MTLTextureUsageRenderTarget | MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
305 id<MTLTexture> texture = [
self.device newTextureWithDescriptor:textureDescriptor
306 iosurface:(__bridge IOSurfaceRef)surface
310 return flutterTexture;
313 if (_availableTextures.count == 0) {
314 CFTimeInterval start = CACurrentMediaTime();
315 while (_availableTextures.count == 0 && CACurrentMediaTime() - start < 1.0) {
318 CFTimeInterval elapsed = CACurrentMediaTime() - start;
319 if (_availableTextures.count == 0) {
320 NSLog(
@"Waited %f seconds for a drawable, giving up.", elapsed);
323 NSLog(
@"Had to wait %f seconds for a drawable", elapsed);
348 [_availableTextures removeObject:res];
356 if (texture == nil) {
361 drawableId:_nextDrawableId++];
368 [
self setNeedsDisplay];
370 [CATransaction begin];
371 [CATransaction setDisableActions:YES];
372 self.contents = texture.
surface;
374 [CATransaction commit];
375 _displayLink.paused = NO;
376 _displayLinkPauseCountdown = 0;
377 if (!_didSetContentsDuringThisDisplayLinkPeriod) {
378 _didSetContentsDuringThisDisplayLinkPeriod = YES;
379 }
else if (!_displayLinkForcedMaxRate) {
380 _displayLinkForcedMaxRate = YES;
386 @
synchronized(
self) {
388 [_availableTextures addObject:_front];
391 if ([NSThread isMainThread]) {
392 [
self presentOnMainThread:texture];
395 dispatch_async(dispatch_get_main_queue(), ^{
396 [
self presentOnMainThread:texture];
403 @
synchronized(
self) {
404 [_availableTextures addObject:texture];
410 static BOOL didCheckInfoPlist = NO;
411 if (!didCheckInfoPlist) {
412 didCheckInfoPlist = YES;
413 NSNumber* use_flutter_metal_layer =
414 [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FLTUseFlutterMetalLayer"];
415 if (use_flutter_metal_layer != nil && [use_flutter_metal_layer boolValue]) {
417 FML_LOG(WARNING) <<
"Using FlutterMetalLayer. This is an experimental feature.";