Files

213 lines
8.3 KiB
Plaintext

// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
#include "flutter/common/settings.h"
#include "flutter/common/task_runners.h"
#include "flutter/flow/layers/layer_tree.h"
#include "flutter/fml/platform/darwin/cf_utils.h"
#include "flutter/fml/synchronization/waitable_event.h"
#include "flutter/fml/trace_event.h"
#include "flutter/shell/common/platform_view.h"
#include "flutter/shell/common/rasterizer.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
#include "flutter/shell/platform/darwin/ios/ios_surface_gl.h"
#include "flutter/shell/platform/darwin/ios/ios_surface_software.h"
#include "third_party/skia/include/utils/mac/SkCGUtils.h"
#if FLUTTER_SHELL_ENABLE_METAL
#include "flutter/shell/platform/darwin/ios/ios_surface_metal.h"
#endif // FLUTTER_SHELL_ENABLE_METAL
@implementation FlutterView
id<FlutterViewEngineDelegate> _delegate;
- (instancetype)init {
@throw([NSException exceptionWithName:@"FlutterView must initWithDelegate"
reason:nil
userInfo:nil]);
}
- (instancetype)initWithFrame:(CGRect)frame {
@throw([NSException exceptionWithName:@"FlutterView must initWithDelegate"
reason:nil
userInfo:nil]);
}
- (instancetype)initWithCoder:(NSCoder*)aDecoder {
@throw([NSException exceptionWithName:@"FlutterView must initWithDelegate"
reason:nil
userInfo:nil]);
}
- (instancetype)initWithDelegate:(id<FlutterViewEngineDelegate>)delegate opaque:(BOOL)opaque {
FML_DCHECK(delegate) << "Delegate must not be nil.";
self = [super initWithFrame:CGRectNull];
if (self) {
_delegate = delegate;
self.layer.opaque = opaque;
}
return self;
}
- (void)layoutSubviews {
if ([self.layer isKindOfClass:[CAEAGLLayer class]]) {
CAEAGLLayer* layer = reinterpret_cast<CAEAGLLayer*>(self.layer);
layer.allowsGroupOpacity = YES;
CGFloat screenScale = [UIScreen mainScreen].scale;
layer.contentsScale = screenScale;
layer.rasterizationScale = screenScale;
}
#if FLUTTER_SHELL_ENABLE_METAL
if ([self.layer isKindOfClass:[CAMetalLayer class]]) {
const CGFloat screenScale = [UIScreen mainScreen].scale;
auto metal_layer = reinterpret_cast<CAMetalLayer*>(self.layer);
metal_layer.contentsScale = screenScale;
metal_layer.rasterizationScale = screenScale;
const auto layer_size = self.bounds.size;
metal_layer.drawableSize =
CGSizeMake(layer_size.width * screenScale, layer_size.height * screenScale);
}
#endif // FLUTTER_SHELL_ENABLE_METAL
[super layoutSubviews];
}
#if FLUTTER_SHELL_ENABLE_METAL
static bool UseMetalRenderer() {
// If there is a command line argument that says Metal should not be used, that takes precedence
// over everything else. This allows disabling Metal on a per run basis to check for regressions
// on an application that has otherwise opted into Metal on an iOS version that supports it.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--disable-metal"]) {
return false;
}
// If the application wants to use metal on a per run basis with disregard for version checks or
// plist based opt ins, respect that opinion. This allows selectively testing features on older
// version of iOS than those explicitly stated as being supported.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--force-metal"]) {
return true;
}
// This is just a version we picked that is easy to support and has all necessary Metal features.
bool ios_version_supports_metal = false;
if (@available(iOS 11.0, *)) {
ios_version_supports_metal = true;
}
// The application must opt-in by default to use Metal without command line flags.
bool application_opts_into_metal =
[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"io.flutter.metal_preview"] boolValue];
return ios_version_supports_metal && application_opts_into_metal;
}
#endif // FLUTTER_SHELL_ENABLE_METAL
+ (Class)layerClass {
#if TARGET_IPHONE_SIMULATOR
return [CALayer class];
#else // TARGET_IPHONE_SIMULATOR
#if FLUTTER_SHELL_ENABLE_METAL
if (UseMetalRenderer()) {
return [CAMetalLayer class];
} else {
return [CAEAGLLayer class];
}
#else // FLUTTER_SHELL_ENABLE_METAL
return [CAEAGLLayer class];
#endif // FLUTTER_SHELL_ENABLE_METAL
#endif // TARGET_IPHONE_SIMULATOR
}
- (std::unique_ptr<flutter::IOSSurface>)createSurface:
(std::shared_ptr<flutter::IOSGLContext>)context {
if ([self.layer isKindOfClass:[CAEAGLLayer class]]) {
fml::scoped_nsobject<CAEAGLLayer> eagl_layer(
reinterpret_cast<CAEAGLLayer*>([self.layer retain]));
if (flutter::IsIosEmbeddedViewsPreviewEnabled()) {
if (@available(iOS 9.0, *)) {
// TODO(amirh): only do this if there's an embedded view.
// https://github.com/flutter/flutter/issues/24133
eagl_layer.get().presentsWithTransaction = YES;
}
}
return std::make_unique<flutter::IOSSurfaceGL>(context, std::move(eagl_layer),
[_delegate platformViewsController]);
#if FLUTTER_SHELL_ENABLE_METAL
} else if ([self.layer isKindOfClass:[CAMetalLayer class]]) {
fml::scoped_nsobject<CAMetalLayer> metalLayer(
reinterpret_cast<CAMetalLayer*>([self.layer retain]));
if (flutter::IsIosEmbeddedViewsPreviewEnabled()) {
if (@available(iOS 8.0, *)) {
// TODO(amirh): only do this if there's an embedded view.
// https://github.com/flutter/flutter/issues/24133
metalLayer.get().presentsWithTransaction = YES;
}
}
return std::make_unique<flutter::IOSSurfaceMetal>(std::move(metalLayer),
[_delegate platformViewsController]);
#endif // FLUTTER_SHELL_ENABLE_METAL
} else {
fml::scoped_nsobject<CALayer> layer(reinterpret_cast<CALayer*>([self.layer retain]));
return std::make_unique<flutter::IOSSurfaceSoftware>(std::move(layer),
[_delegate platformViewsController]);
}
}
- (void)drawLayer:(CALayer*)layer inContext:(CGContextRef)context {
TRACE_EVENT0("flutter", "SnapshotFlutterView");
if (layer != self.layer || context == nullptr) {
return;
}
auto screenshot = [_delegate takeScreenshot:flutter::Rasterizer::ScreenshotType::UncompressedImage
asBase64Encoded:NO];
if (!screenshot.data || screenshot.data->isEmpty() || screenshot.frame_size.isEmpty()) {
return;
}
NSData* data = [NSData dataWithBytes:const_cast<void*>(screenshot.data->data())
length:screenshot.data->size()];
fml::CFRef<CGDataProviderRef> image_data_provider(
CGDataProviderCreateWithCFData(reinterpret_cast<CFDataRef>(data)));
fml::CFRef<CGColorSpaceRef> colorspace(CGColorSpaceCreateDeviceRGB());
fml::CFRef<CGImageRef> image(CGImageCreate(
screenshot.frame_size.width(), // size_t width
screenshot.frame_size.height(), // size_t height
8, // size_t bitsPerComponent
32, // size_t bitsPerPixel,
4 * screenshot.frame_size.width(), // size_t bytesPerRow
colorspace, // CGColorSpaceRef space
static_cast<CGBitmapInfo>(kCGImageAlphaPremultipliedLast |
kCGBitmapByteOrder32Big), // CGBitmapInfo bitmapInfo
image_data_provider, // CGDataProviderRef provider
nullptr, // const CGFloat* decode
false, // bool shouldInterpolate
kCGRenderingIntentDefault // CGColorRenderingIntent intent
));
const CGRect frame_rect =
CGRectMake(0.0, 0.0, screenshot.frame_size.width(), screenshot.frame_size.height());
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, CGBitmapContextGetHeight(context));
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, frame_rect, image);
CGContextRestoreGState(context);
}
@end