From 5526884e7878d0e1ce3eb650c1ec6769fc59ba9c Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Sat, 11 May 2019 15:21:26 -0700 Subject: [PATCH] Wire up the Skia Metal backend on iOS. (#8936) --- ci/licenses_golden/licenses_flutter | 4 + shell/common/BUILD.gn | 1 + shell/config.gni | 1 + shell/gpu/BUILD.gn | 9 ++ shell/gpu/gpu.gni | 6 + shell/gpu/gpu_surface_metal.h | 50 ++++++ shell/gpu/gpu_surface_metal.mm | 145 ++++++++++++++++++ shell/platform/android/BUILD.gn | 1 + shell/platform/darwin/ios/BUILD.gn | 15 +- .../ios/framework/Source/FlutterView.mm | 29 +++- shell/platform/darwin/ios/ios_surface_gl.h | 6 +- shell/platform/darwin/ios/ios_surface_metal.h | 44 ++++++ .../platform/darwin/ios/ios_surface_metal.mm | 34 ++++ shell/platform/embedder/BUILD.gn | 1 + tools/gn | 12 ++ 15 files changed, 352 insertions(+), 6 deletions(-) create mode 100644 shell/gpu/gpu_surface_metal.h create mode 100644 shell/gpu/gpu_surface_metal.mm create mode 100644 shell/platform/darwin/ios/ios_surface_metal.h create mode 100644 shell/platform/darwin/ios/ios_surface_metal.mm diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 6d405c255..da8f3c259 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -498,6 +498,8 @@ FILE: ../../../flutter/shell/gpu/gpu_surface_gl.cc FILE: ../../../flutter/shell/gpu/gpu_surface_gl.h FILE: ../../../flutter/shell/gpu/gpu_surface_gl_delegate.cc FILE: ../../../flutter/shell/gpu/gpu_surface_gl_delegate.h +FILE: ../../../flutter/shell/gpu/gpu_surface_metal.h +FILE: ../../../flutter/shell/gpu/gpu_surface_metal.mm FILE: ../../../flutter/shell/gpu/gpu_surface_software.cc FILE: ../../../flutter/shell/gpu/gpu_surface_software.h FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan.cc @@ -719,6 +721,8 @@ FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_gl.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_gl.mm +FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_metal.h +FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_metal.mm FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_software.h FILE: ../../../flutter/shell/platform/darwin/ios/ios_surface_software.mm FILE: ../../../flutter/shell/platform/darwin/ios/platform_view_ios.h diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index b5103cca0..9df00c6ea 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -145,6 +145,7 @@ shell_gpu_configuration("shell_unittests_gpu_configuration") { enable_software = true enable_vulkan = false enable_gl = false + enable_metal = false } test_fixtures("shell_unittests_fixtures") { diff --git a/shell/config.gni b/shell/config.gni index 3088b2cc6..b57895162 100644 --- a/shell/config.gni +++ b/shell/config.gni @@ -4,4 +4,5 @@ declare_args() { shell_enable_vulkan = false + shell_enable_metal = false } diff --git a/shell/gpu/BUILD.gn b/shell/gpu/BUILD.gn index 93e944296..dc8d8c3f4 100644 --- a/shell/gpu/BUILD.gn +++ b/shell/gpu/BUILD.gn @@ -45,3 +45,12 @@ source_set("gpu_surface_vulkan") { "$flutter_root/vulkan", ] } + +source_set("gpu_surface_metal") { + sources = [ + "$gpu_dir/gpu_surface_metal.h", + "$gpu_dir/gpu_surface_metal.mm", + ] + + deps = gpu_common_deps + [ "//third_party/skia" ] +} diff --git a/shell/gpu/gpu.gni b/shell/gpu/gpu.gni index 3a5771f47..f3a258892 100644 --- a/shell/gpu/gpu.gni +++ b/shell/gpu/gpu.gni @@ -11,6 +11,8 @@ template("shell_gpu_configuration") { "Caller must declare if the Vulkan backend must be enabled.") assert(defined(invoker.enable_gl), "Caller must declare if the Open GL backend must be enabled.") + assert(defined(invoker.enable_metal), + "Caller must declare if the Metal backend must be enabled.") group(target_name) { public_deps = [] @@ -26,5 +28,9 @@ template("shell_gpu_configuration") { if (invoker.enable_vulkan) { public_deps += [ "$flutter_root/shell/gpu:gpu_surface_vulkan" ] } + + if (invoker.enable_metal) { + public_deps += [ "$flutter_root/shell/gpu:gpu_surface_metal" ] + } } } diff --git a/shell/gpu/gpu_surface_metal.h b/shell/gpu/gpu_surface_metal.h new file mode 100644 index 000000000..894aab6d8 --- /dev/null +++ b/shell/gpu/gpu_surface_metal.h @@ -0,0 +1,50 @@ +// 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. + +#ifndef FLUTTER_SHELL_GPU_GPU_SURFACE_METAL_H_ +#define FLUTTER_SHELL_GPU_GPU_SURFACE_METAL_H_ + +#include + +#include "flutter/fml/macros.h" +#include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/shell/common/surface.h" +#include "third_party/skia/include/gpu/GrContext.h" + +@class CAMetalLayer; + +namespace flutter { + +class GPUSurfaceMetal : public Surface { + public: + GPUSurfaceMetal(fml::scoped_nsobject layer); + + ~GPUSurfaceMetal() override; + + private: + fml::scoped_nsobject layer_; + sk_sp context_; + fml::scoped_nsprotocol> command_queue_; + + // |Surface| + bool IsValid() override; + + // |Surface| + std::unique_ptr AcquireFrame(const SkISize& size) override; + + // |Surface| + SkMatrix GetRootTransformation() const override; + + // |Surface| + GrContext* GetContext() override; + + // |Surface| + bool MakeRenderContextCurrent() override; + + FML_DISALLOW_COPY_AND_ASSIGN(GPUSurfaceMetal); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_GPU_GPU_SURFACE_METAL_H_ diff --git a/shell/gpu/gpu_surface_metal.mm b/shell/gpu/gpu_surface_metal.mm new file mode 100644 index 000000000..744dd36b0 --- /dev/null +++ b/shell/gpu/gpu_surface_metal.mm @@ -0,0 +1,145 @@ +// 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/gpu/gpu_surface_metal.h" + +#include + +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/gpu/GrBackendSurface.h" + +namespace flutter { + +GPUSurfaceMetal::GPUSurfaceMetal(fml::scoped_nsobject layer) + : layer_(std::move(layer)) { + if (!layer_) { + FML_LOG(ERROR) << "Could not create metal surface because of invalid layer."; + return; + } + + layer.get().pixelFormat = MTLPixelFormatBGRA8Unorm; + + auto metal_device = fml::scoped_nsprotocol>([layer_.get().device retain]); + auto metal_queue = fml::scoped_nsprotocol>([metal_device newCommandQueue]); + + if (!metal_device || !metal_queue) { + FML_LOG(ERROR) << "Could not create metal device or queue."; + return; + } + + command_queue_ = metal_queue; + + // The context creation routine accepts arguments using transfer semantics. + auto context = GrContext::MakeMetal(metal_device.release(), metal_queue.release()); + if (!context) { + FML_LOG(ERROR) << "Could not create Skia metal context."; + return; + } + + context_ = context; +} + +GPUSurfaceMetal::~GPUSurfaceMetal() = default; + +// |Surface| +bool GPUSurfaceMetal::IsValid() { + return layer_ && context_ && command_queue_; +} + +// |Surface| +std::unique_ptr GPUSurfaceMetal::AcquireFrame(const SkISize& size) { + if (!IsValid()) { + FML_LOG(ERROR) << "Metal surface was invalid."; + return nullptr; + } + + if (size.isEmpty()) { + FML_LOG(ERROR) << "Metal surface was asked for an empty frame."; + return nullptr; + } + + const auto bounds = layer_.get().bounds.size; + if (bounds.width <= 0.0 || bounds.height <= 0.0) { + FML_LOG(ERROR) << "Metal layer bounds were invalid."; + return nullptr; + } + + const auto scale = layer_.get().contentsScale; + + auto next_drawable = fml::scoped_nsprotocol>([[layer_ nextDrawable] retain]); + if (!next_drawable) { + FML_LOG(ERROR) << "Could not acquire next metal drawable."; + return nullptr; + } + + auto metal_texture = fml::scoped_nsprotocol>([next_drawable.get().texture retain]); + if (!metal_texture) { + FML_LOG(ERROR) << "Could not acquire metal texture from drawable."; + return nullptr; + } + + GrMtlTextureInfo metal_texture_info; + metal_texture_info.fTexture = metal_texture.get(); + + GrBackendRenderTarget metal_render_target(bounds.width * scale, // width + bounds.height * scale, // height + 1, // sample count + metal_texture_info // metal texture info + ); + + auto command_buffer = + fml::scoped_nsprotocol>([[command_queue_.get() commandBuffer] retain]); + + SkSurface::RenderTargetReleaseProc release_proc = [](SkSurface::ReleaseContext context) { + [reinterpret_cast(context) release]; + }; + + auto surface = + SkSurface::MakeFromBackendRenderTarget(context_.get(), // context + metal_render_target, // backend render target + kTopLeft_GrSurfaceOrigin, // origin + kBGRA_8888_SkColorType, // color type + nullptr, // colorspace + nullptr, // surface properties + release_proc, // release proc + metal_texture.release() // release context (texture) + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not create the SkSurface from the metal texture."; + return nullptr; + } + + auto submit_callback = [drawable = next_drawable, command_buffer]( + const SurfaceFrame& surface_frame, SkCanvas* canvas) -> bool { + canvas->flush(); + [command_buffer.get() presentDrawable:drawable.get()]; + [command_buffer.get() commit]; + return true; + }; + + return std::make_unique(std::move(surface), submit_callback); +} + +// |Surface| +SkMatrix GPUSurfaceMetal::GetRootTransformation() const { + // This backend does not currently support root surface transformations. Just + // return identity. + SkMatrix matrix; + matrix.reset(); + return matrix; +} + +// |Surface| +GrContext* GPUSurfaceMetal::GetContext() { + return context_.get(); +} + +// |Surface| +bool GPUSurfaceMetal::MakeRenderContextCurrent() { + // This backend has no such concept. + return true; +} + +} // namespace flutter diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index e117640bc..01311f4a4 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -11,6 +11,7 @@ shell_gpu_configuration("android_gpu_configuration") { enable_software = true enable_vulkan = shell_enable_vulkan enable_gl = true + enable_metal = false } shared_library("flutter_shell_native") { diff --git a/shell/platform/darwin/ios/BUILD.gn b/shell/platform/darwin/ios/BUILD.gn index ae426f5b9..009fc4dae 100644 --- a/shell/platform/darwin/ios/BUILD.gn +++ b/shell/platform/darwin/ios/BUILD.gn @@ -14,6 +14,7 @@ shell_gpu_configuration("ios_gpu_configuration") { enable_software = true enable_gl = true enable_vulkan = false + enable_metal = shell_enable_metal } # The headers that will be copied to the Flutter.framework and be accessed @@ -100,6 +101,13 @@ shared_library("create_flutter_framework_dylib") { "platform_view_ios.mm", ] + if (shell_enable_metal) { + sources += [ + "ios_surface_metal.h", + "ios_surface_metal.mm", + ] + } + sources += _flutter_framework_headers deps = [ @@ -117,7 +125,11 @@ shared_library("create_flutter_framework_dylib") { public_configs = [ "$flutter_root:config" ] - defines = [ "FLUTTER_FRAMEWORK" ] + defines = [ "FLUTTER_FRAMEWORK=1" ] + + if (shell_enable_metal) { + defines += [ "FLUTTER_SHELL_ENABLE_METAL=1" ] + } libs = [ "CoreMedia.framework", @@ -241,6 +253,7 @@ copy("copy_license") { shared_library("copy_and_verify_framework_headers") { visibility = [ ":*" ] include_dirs = [ "$_flutter_framework_headers_copy_dir" ] + sources = [ "framework/Source/FlutterUmbrellaImport.m", ] diff --git a/shell/platform/darwin/ios/framework/Source/FlutterView.mm b/shell/platform/darwin/ios/framework/Source/FlutterView.mm index 61b5db9c5..0d53b44b7 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterView.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterView.mm @@ -17,6 +17,10 @@ #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 _delegate; @@ -60,14 +64,26 @@ id _delegate; layer.rasterizationScale = screenScale; } +#if FLUTTER_SHELL_ENABLE_METAL + if ([self.layer isKindOfClass:[CAMetalLayer class]]) { + CGFloat screenScale = [UIScreen mainScreen].scale; + self.layer.contentsScale = screenScale; + self.layer.rasterizationScale = screenScale; + } + +#endif // FLUTTER_SHELL_ENABLE_METAL [super layoutSubviews]; } + (Class)layerClass { #if TARGET_IPHONE_SIMULATOR return [CALayer class]; -#else // TARGET_IPHONE_SIMULATOR +#else // TARGET_IPHONE_SIMULATOR +#if FLUTTER_SHELL_ENABLE_METAL + return [CAMetalLayer class]; +#else // FLUTTER_SHELL_ENABLE_METAL return [CAEAGLLayer class]; +#endif // FLUTTER_SHELL_ENABLE_METAL #endif // TARGET_IPHONE_SIMULATOR } @@ -87,7 +103,16 @@ id _delegate; } return std::make_unique(context, std::move(eagl_layer), [_delegate platformViewsController]); - } else { + } +#if FLUTTER_SHELL_ENABLE_METAL + else if ([self.layer isKindOfClass:[CAMetalLayer class]]) { + return std::make_unique( + fml::scoped_nsobject(reinterpret_cast([self.layer retain])), + [_delegate platformViewsController]); + } +#endif // FLUTTER_SHELL_ENABLE_METAL + + else { fml::scoped_nsobject layer(reinterpret_cast([self.layer retain])); return std::make_unique(std::move(layer), [_delegate platformViewsController]); diff --git a/shell/platform/darwin/ios/ios_surface_gl.h b/shell/platform/darwin/ios/ios_surface_gl.h index e0a2f52e2..0090634a9 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.h +++ b/shell/platform/darwin/ios/ios_surface_gl.h @@ -16,9 +16,9 @@ namespace flutter { -class IOSSurfaceGL : public IOSSurface, - public GPUSurfaceGLDelegate, - public flutter::ExternalViewEmbedder { +class IOSSurfaceGL final : public IOSSurface, + public GPUSurfaceGLDelegate, + public flutter::ExternalViewEmbedder { public: IOSSurfaceGL(std::shared_ptr context, fml::scoped_nsobject layer, diff --git a/shell/platform/darwin/ios/ios_surface_metal.h b/shell/platform/darwin/ios/ios_surface_metal.h new file mode 100644 index 000000000..38340e5dc --- /dev/null +++ b/shell/platform/darwin/ios/ios_surface_metal.h @@ -0,0 +1,44 @@ +// 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. + +#ifndef FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS_SURFACE_METAL_H_ +#define FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS_SURFACE_METAL_H_ + +#include "flutter/fml/macros.h" +#include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/shell/gpu/gpu_surface_metal.h" +#include "flutter/shell/platform/darwin/ios/ios_surface.h" + +@class CAMetalLayer; + +namespace flutter { + +class IOSSurfaceMetal final : public IOSSurface { + public: + IOSSurfaceMetal(fml::scoped_nsobject layer, + FlutterPlatformViewsController* platform_views_controller); + + ~IOSSurfaceMetal() override; + + private: + fml::scoped_nsobject layer_; + + // |IOSSurface| + bool IsValid() const override; + + // |IOSSurface| + bool ResourceContextMakeCurrent() override; + + // |IOSSurface| + void UpdateStorageSizeIfNecessary() override; + + // |IOSSurface| + std::unique_ptr CreateGPUSurface() override; + + FML_DISALLOW_COPY_AND_ASSIGN(IOSSurfaceMetal); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_IOS_SURFACE_METAL_H_ diff --git a/shell/platform/darwin/ios/ios_surface_metal.mm b/shell/platform/darwin/ios/ios_surface_metal.mm new file mode 100644 index 000000000..665049ac8 --- /dev/null +++ b/shell/platform/darwin/ios/ios_surface_metal.mm @@ -0,0 +1,34 @@ +// 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/ios_surface_metal.h" +#include "flutter/shell/gpu/gpu_surface_metal.h" + +namespace flutter { + +IOSSurfaceMetal::IOSSurfaceMetal(fml::scoped_nsobject layer, + FlutterPlatformViewsController* platform_views_controller) + : IOSSurface(platform_views_controller), layer_(std::move(layer)) {} + +IOSSurfaceMetal::~IOSSurfaceMetal() = default; + +// |IOSSurface| +bool IOSSurfaceMetal::IsValid() const { + return layer_; +} + +// |IOSSurface| +bool IOSSurfaceMetal::ResourceContextMakeCurrent() { + return false; +} + +// |IOSSurface| +void IOSSurfaceMetal::UpdateStorageSizeIfNecessary() {} + +// |IOSSurface| +std::unique_ptr IOSSurfaceMetal::CreateGPUSurface() { + return std::make_unique(layer_); +} + +} // namespace flutter diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index b85632885..41b8d2567 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -11,6 +11,7 @@ shell_gpu_configuration("embedder_gpu_configuration") { enable_software = true enable_vulkan = false enable_gl = true + enable_metal = false } source_set("embedder") { diff --git a/tools/gn b/tools/gn index ba88e7be2..8c5b8ed21 100755 --- a/tools/gn +++ b/tools/gn @@ -43,6 +43,9 @@ def get_out_dir(args): if args.enable_vulkan: target_dir.append('vulkan') + if args.enable_metal: + target_dir.append('metal') + return os.path.join(args.out_dir, 'out', '_'.join(target_dir)) def to_command_line(gn_args): @@ -71,6 +74,9 @@ def to_gn_args(args): if args.target_os != 'android' and args.enable_vulkan: raise Exception('--enable-vulkan is only supported on Android') + if args.target_os != 'ios' and args.enable_metal: + raise Exception('--enable-metal is only supported on iOS') + runtime_mode = args.runtime_mode if args.dynamic and runtime_mode in ['profile', 'release']: runtime_mode = 'dynamic_' + runtime_mode @@ -215,6 +221,11 @@ def to_gn_args(args): gn_args['use_goma'] = False gn_args['goma_dir'] = None + if args.enable_metal: + gn_args['skia_use_metal'] = True + gn_args['shell_enable_metal'] = True + gn_args['ios_deployment_target'] = '11' + if args.enable_vulkan: # Enable vulkan in the Flutter shell. gn_args['shell_enable_vulkan'] = True @@ -285,6 +296,7 @@ def parse_args(args): parser.add_argument('--operator-new-alignment', dest='operator_new_alignment', type=str, default=None) parser.add_argument('--enable-vulkan', action='store_true', default=False) + parser.add_argument('--enable-metal', action='store_true', default=False) parser.add_argument('--embedder-for-target', dest='embedder_for_target', action='store_true', default=False) -- GitLab