diff --git a/engine/src/flutter/shell/testing/BUILD.gn b/engine/src/flutter/shell/testing/BUILD.gn index 79dd5084ffe..93104a9280a 100644 --- a/engine/src/flutter/shell/testing/BUILD.gn +++ b/engine/src/flutter/shell/testing/BUILD.gn @@ -13,7 +13,7 @@ shell_gpu_configuration("tester_gpu_configuration") { enable_software = true enable_gl = true enable_vulkan = true - enable_metal = false + enable_metal = shell_enable_metal } executable("testing") { @@ -26,6 +26,7 @@ executable("testing") { sources = [ "tester_context.h", + "tester_context_mtl_factory.h", "tester_context_vk_factory.h", "tester_main.cc", ] @@ -61,6 +62,9 @@ executable("testing") { "//flutter/third_party/swiftshader/src/Vulkan:swiftshader_libvulkan_static", ] sources += [ "tester_context_vk_factory.cc" ] + if (shell_enable_metal) { + sources += [ "tester_context_mtl_factory.mm" ] + } } metadata = { diff --git a/engine/src/flutter/shell/testing/tester_context_mtl_factory.h b/engine/src/flutter/shell/testing/tester_context_mtl_factory.h new file mode 100644 index 00000000000..1ae76033839 --- /dev/null +++ b/engine/src/flutter/shell/testing/tester_context_mtl_factory.h @@ -0,0 +1,24 @@ +// 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_TESTING_TESTER_CONTEXT_MTL_FACTORY_H_ +#define FLUTTER_SHELL_TESTING_TESTER_CONTEXT_MTL_FACTORY_H_ + +#include +#include "flutter/shell/testing/tester_context.h" + +// Impeller should only be enabled if the Metal backend is enabled. +#define TESTER_ENABLE_METAL \ + (IMPELLER_SUPPORTS_RENDERING && IMPELLER_ENABLE_METAL) + +namespace flutter { + +class TesterContextMTLFactory { + public: + static std::unique_ptr Create(); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_TESTING_TESTER_CONTEXT_MTL_FACTORY_H_ diff --git a/engine/src/flutter/shell/testing/tester_context_mtl_factory.mm b/engine/src/flutter/shell/testing/tester_context_mtl_factory.mm new file mode 100644 index 00000000000..31476c3a536 --- /dev/null +++ b/engine/src/flutter/shell/testing/tester_context_mtl_factory.mm @@ -0,0 +1,133 @@ +// 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. + +#define FML_USED_ON_EMBEDDER + +#include "flutter/shell/testing/tester_context_mtl_factory.h" + +#include +#include +#include +#include +#include +#include + +#include "flutter/fml/mapping.h" +#include "flutter/fml/synchronization/sync_switch.h" +#include "flutter/shell/gpu/gpu_surface_metal_impeller.h" +#include "impeller/base/validation.h" +#include "impeller/display_list/aiks_context.h" +#include "impeller/entity/mtl/entity_shaders.h" +#include "impeller/entity/mtl/framebuffer_blend_shaders.h" +#include "impeller/entity/mtl/modern_shaders.h" +#include "impeller/renderer/backend/metal/context_mtl.h" +#include "impeller/typographer/backends/skia/typographer_context_skia.h" +#include "impeller/typographer/typographer_context.h" + +namespace flutter { + +namespace { + +class TesterGPUSurfaceMetalDelegate : public GPUSurfaceMetalDelegate { + public: + explicit TesterGPUSurfaceMetalDelegate(id device) + : GPUSurfaceMetalDelegate(MTLRenderTargetType::kCAMetalLayer) { + layer_ = [[CAMetalLayer alloc] init]; + layer_.device = device; + layer_.pixelFormat = MTLPixelFormatBGRA8Unorm; + } + + ~TesterGPUSurfaceMetalDelegate() = default; + + GPUCAMetalLayerHandle GetCAMetalLayer(const DlISize& frame_info) const override { + layer_.drawableSize = CGSizeMake(frame_info.width, frame_info.height); + return (__bridge GPUCAMetalLayerHandle)(layer_); + } + + bool PresentDrawable(GrMTLHandle drawable) const override { return true; } + + GPUMTLTextureInfo GetMTLTexture(const DlISize& frame_info) const override { return {}; } + + bool PresentTexture(GPUMTLTextureInfo texture) const override { return true; } + + bool AllowsDrawingWhenGpuDisabled() const override { return true; } + + private: + CAMetalLayer* layer_ = nil; +}; + +std::vector> ShaderLibraryMappings() { + return { + std::make_shared(impeller_entity_shaders_data, + impeller_entity_shaders_length), + std::make_shared(impeller_modern_shaders_data, + impeller_modern_shaders_length), + std::make_shared(impeller_framebuffer_blend_shaders_data, + impeller_framebuffer_blend_shaders_length), + }; +} + +} // namespace + +class TesterContextMTL : public TesterContext { + public: + TesterContextMTL() = default; + + ~TesterContextMTL() override { + if (context_) { + context_->Shutdown(); + } + } + + bool Initialize() { + std::vector> shader_mappings = ShaderLibraryMappings(); + auto sync_switch = std::make_shared(false); + context_ = impeller::ContextMTL::Create(impeller::Flags{}, shader_mappings, sync_switch, + "Impeller Library"); + + if (!context_ || !context_->IsValid()) { + VALIDATION_LOG << "Could not create Metal context."; + return false; + } + + auto device = context_->GetMTLDevice(); + if (!device) { + VALIDATION_LOG << "Could not get Metal device."; + return false; + } + + delegate_ = std::make_unique(device); + aiks_context_ = std::make_shared( + context_, /*typographer_context=*/impeller::TypographerContextSkia::Make()); + + return true; + } + + // |TesterContext| + std::shared_ptr GetImpellerContext() const override { return context_; } + + // |TesterContext| + std::unique_ptr CreateRenderingSurface() override { + auto surface = std::make_unique(delegate_.get(), aiks_context_); + if (!surface->IsValid()) { + return nullptr; + } + return surface; + } + + private: + std::shared_ptr context_; + std::unique_ptr delegate_; + std::shared_ptr aiks_context_; +}; + +std::unique_ptr TesterContextMTLFactory::Create() { + auto context = std::make_unique(); + if (!context->Initialize()) { + return nullptr; + } + return context; +} + +} // namespace flutter diff --git a/engine/src/flutter/shell/testing/tester_main.cc b/engine/src/flutter/shell/testing/tester_main.cc index b162816977f..dd385869c86 100644 --- a/engine/src/flutter/shell/testing/tester_main.cc +++ b/engine/src/flutter/shell/testing/tester_main.cc @@ -31,6 +31,7 @@ #include "third_party/skia/include/core/SkSurface.h" #include "flutter/shell/testing/tester_context.h" +#include "flutter/shell/testing/tester_context_mtl_factory.h" #include "flutter/shell/testing/tester_context_vk_factory.h" #if defined(FML_OS_WIN) @@ -48,8 +49,14 @@ static absl::NoDestructor> g_shell; namespace { std::unique_ptr CreateTesterContext(const Settings& settings) { std::unique_ptr tester_context; +#if TESTER_ENABLE_METAL + if (settings.enable_impeller && + settings.requested_rendering_backend == "metal") { + tester_context = TesterContextMTLFactory::Create(); + } +#endif #if TESTER_ENABLE_VULKAN - if (settings.enable_impeller) { + if (settings.enable_impeller && !tester_context) { tester_context = TesterContextVKFactory::Create(settings.enable_vulkan_validation); } diff --git a/engine/src/flutter/testing/run_tests.py b/engine/src/flutter/testing/run_tests.py index 3fa7a2c39d0..7c7ed066282 100755 --- a/engine/src/flutter/testing/run_tests.py +++ b/engine/src/flutter/testing/run_tests.py @@ -651,13 +651,13 @@ class FlutterTesterOptions(): def __init__( # pylint: disable=too-many-arguments self, multithreaded: bool = False, - enable_impeller: bool = False, + impeller_backend: str = '', enable_vm_service: bool = False, enable_microtask_profiling: bool = False, expect_failure: bool = False ) -> None: self.multithreaded = multithreaded - self.enable_impeller = enable_impeller + self.impeller_backend = impeller_backend self.enable_vm_service = enable_vm_service self.enable_microtask_profiling = enable_microtask_profiling self.expect_failure = expect_failure @@ -666,8 +666,10 @@ class FlutterTesterOptions(): if not self.enable_vm_service: command_args.append('--disable-vm-service') - if self.enable_impeller: - command_args += ['--enable-impeller', '--enable-flutter-gpu'] + if self.impeller_backend != '': + command_args += [ + '--enable-impeller', '--enable-flutter-gpu', f'--impeller-backend={self.impeller_backend}' + ] else: command_args += ['--no-enable-impeller'] @@ -683,8 +685,8 @@ class FlutterTesterOptions(): return 'single-threaded' def impeller_enabled(self) -> str: - if self.enable_impeller: - return 'impeller swiftshader' + if self.impeller_backend != '': + return f'impeller {self.impeller_backend}' return 'skia software' @@ -935,12 +937,12 @@ def gather_dart_tests( else: _logger.info("Gathering dart test '%s' with VM service enabled", dart_test_file) for multithreaded in [False, True]: - for enable_impeller in [False, True]: + for impeller in ['', 'vulkan', 'metal']: yield gather_dart_test( build_dir, dart_test_file, FlutterTesterOptions( multithreaded=multithreaded, - enable_impeller=enable_impeller, + impeller_backend=impeller, enable_vm_service=True, enable_microtask_profiling=dart_test_basename == 'microtask_profiling_test.dart', @@ -953,10 +955,10 @@ def gather_dart_tests( else: _logger.info("Gathering dart test '%s'", dart_test_file) for multithreaded in [False, True]: - for enable_impeller in [False, True]: + for impeller in ['', 'vulkan', 'metal']: yield gather_dart_test( build_dir, dart_test_file, - FlutterTesterOptions(multithreaded=multithreaded, enable_impeller=enable_impeller) + FlutterTesterOptions(multithreaded=multithreaded, impeller_backend=impeller) )