From 70f070263addb17abf95db4bc6ed1a8cfe2c3cbe Mon Sep 17 00:00:00 2001 From: Kaushik Iska Date: Fri, 1 Jan 2021 22:39:36 -0600 Subject: [PATCH] [embedder] [metal] Add support for Metal Renderer Config in the embedder API (#22854) This change adds a FlutterMetalRendererConfig that lets embedders specify metal as rendering api. Also adds a test that validates rendering a gradient using metal. --- ci/licenses_golden/licenses_flutter | 3 + shell/gpu/gpu_surface_metal.mm | 4 +- shell/gpu/gpu_surface_metal_delegate.h | 6 +- shell/platform/darwin/ios/ios_surface_metal.h | 2 +- .../platform/darwin/ios/ios_surface_metal.mm | 2 +- shell/platform/embedder/BUILD.gn | 23 ++++ shell/platform/embedder/embedder.cc | 96 +++++++++++++ shell/platform/embedder/embedder.h | 54 ++++++++ .../embedder/embedder_surface_metal.h | 68 ++++++++++ .../embedder/embedder_surface_metal.mm | 74 ++++++++++ .../embedder/fixtures/gradient_metal.png | Bin 0 -> 45425 bytes .../embedder/platform_view_embedder.cc | 13 ++ .../embedder/platform_view_embedder.h | 14 ++ .../embedder/tests/embedder_a11y_unittests.cc | 2 +- .../embedder/tests/embedder_config_builder.cc | 57 ++++++++ .../embedder/tests/embedder_config_builder.h | 6 + .../platform/embedder/tests/embedder_test.cc | 17 ++- shell/platform/embedder/tests/embedder_test.h | 9 +- .../embedder/tests/embedder_test_context.h | 8 ++ .../tests/embedder_test_context_gl.cc | 13 +- .../embedder/tests/embedder_test_context_gl.h | 3 + .../tests/embedder_test_context_metal.cc | 53 ++++++++ .../tests/embedder_test_context_metal.h | 50 +++++++ .../tests/embedder_test_context_software.cc | 4 + .../tests/embedder_test_context_software.h | 3 + .../embedder/tests/embedder_unittests.cc | 54 ++++---- .../embedder/tests/embedder_unittests_gl.cc | 100 +++++++------- .../tests/embedder_unittests_metal.cc | 50 +++++++ .../embedder/tests/embedder_unittests_util.cc | 30 ++++- testing/BUILD.gn | 41 +++--- testing/test_metal_context.h | 51 +++++++ testing/test_metal_context.mm | 126 ++++++++++++++++++ testing/test_metal_surface.cc | 27 ++-- testing/test_metal_surface.h | 11 +- testing/test_metal_surface_impl.h | 16 ++- testing/test_metal_surface_impl.mm | 124 ++++++++--------- testing/test_metal_surface_unittests.cc | 12 +- tools/gn | 13 ++ 38 files changed, 1042 insertions(+), 197 deletions(-) create mode 100644 shell/platform/embedder/embedder_surface_metal.h create mode 100644 shell/platform/embedder/embedder_surface_metal.mm create mode 100644 shell/platform/embedder/fixtures/gradient_metal.png create mode 100644 shell/platform/embedder/tests/embedder_test_context_metal.cc create mode 100644 shell/platform/embedder/tests/embedder_test_context_metal.h create mode 100644 shell/platform/embedder/tests/embedder_unittests_metal.cc create mode 100644 testing/test_metal_context.h create mode 100644 testing/test_metal_context.mm diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 177517471df03..463bcba2f2dcd 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1898,6 +1898,8 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_surface.cc FILE: ../../../flutter/shell/platform/embedder/embedder_surface.h FILE: ../../../flutter/shell/platform/embedder/embedder_surface_gl.cc FILE: ../../../flutter/shell/platform/embedder/embedder_surface_gl.h +FILE: ../../../flutter/shell/platform/embedder/embedder_surface_metal.h +FILE: ../../../flutter/shell/platform/embedder/embedder_surface_metal.mm FILE: ../../../flutter/shell/platform/embedder/embedder_surface_software.cc FILE: ../../../flutter/shell/platform/embedder/embedder_surface_software.h FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.cc @@ -1913,6 +1915,7 @@ FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor_with_root_lay FILE: ../../../flutter/shell/platform/embedder/fixtures/dpr_noxform.png FILE: ../../../flutter/shell/platform/embedder/fixtures/dpr_xform.png FILE: ../../../flutter/shell/platform/embedder/fixtures/gradient.png +FILE: ../../../flutter/shell/platform/embedder/fixtures/gradient_metal.png FILE: ../../../flutter/shell/platform/embedder/fixtures/gradient_xform.png FILE: ../../../flutter/shell/platform/embedder/fixtures/main.dart FILE: ../../../flutter/shell/platform/embedder/fixtures/scene_without_custom_compositor.png diff --git a/shell/gpu/gpu_surface_metal.mm b/shell/gpu/gpu_surface_metal.mm index 0a1922e793025..96ec00a325837 100644 --- a/shell/gpu/gpu_surface_metal.mm +++ b/shell/gpu/gpu_surface_metal.mm @@ -125,7 +125,7 @@ return nullptr; } - auto submit_callback = [texture_id = texture.texture_id, delegate = delegate_]( + auto submit_callback = [texture = texture, delegate = delegate_]( const SurfaceFrame& surface_frame, SkCanvas* canvas) -> bool { TRACE_EVENT0("flutter", "GPUSurfaceMetal::PresentTexture"); if (canvas == nullptr) { @@ -135,7 +135,7 @@ canvas->flush(); - return delegate->PresentTexture(texture_id); + return delegate->PresentTexture(texture); }; return std::make_unique(std::move(surface), true, submit_callback); diff --git a/shell/gpu/gpu_surface_metal_delegate.h b/shell/gpu/gpu_surface_metal_delegate.h index aa88ecfda48d9..4185e409e4d17 100644 --- a/shell/gpu/gpu_surface_metal_delegate.h +++ b/shell/gpu/gpu_surface_metal_delegate.h @@ -24,10 +24,10 @@ typedef void* GPUMTLCommandQueueHandle; typedef void* GPUCAMetalLayerHandle; // expected to be id -typedef void* GPUMTLTextureHandle; +typedef const void* GPUMTLTextureHandle; struct GPUMTLTextureInfo { - intptr_t texture_id; + int64_t texture_id; GPUMTLTextureHandle texture; }; @@ -87,7 +87,7 @@ class GPUSurfaceMetalDelegate { /// /// @see |GPUSurfaceMetalDelegate::GetMTLTexture| /// - virtual bool PresentTexture(intptr_t texture_id) const = 0; + virtual bool PresentTexture(GPUMTLTextureInfo texture) const = 0; MTLRenderTargetType GetRenderTargetType(); diff --git a/shell/platform/darwin/ios/ios_surface_metal.h b/shell/platform/darwin/ios/ios_surface_metal.h index 2ebcfee3834f1..c2e9b8c50d0da 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.h +++ b/shell/platform/darwin/ios/ios_surface_metal.h @@ -47,7 +47,7 @@ class SK_API_AVAILABLE_CA_METAL_LAYER IOSSurfaceMetal final : public IOSSurface, GPUMTLTextureInfo GetMTLTexture(const SkISize& frame_info) const override; // |GPUSurfaceMetalDelegate| - bool PresentTexture(intptr_t texture_id) const override; + bool PresentTexture(GPUMTLTextureInfo texture) const override; FML_DISALLOW_COPY_AND_ASSIGN(IOSSurfaceMetal); }; diff --git a/shell/platform/darwin/ios/ios_surface_metal.mm b/shell/platform/darwin/ios/ios_surface_metal.mm index 176f50313b927..35304938d1efe 100644 --- a/shell/platform/darwin/ios/ios_surface_metal.mm +++ b/shell/platform/darwin/ios/ios_surface_metal.mm @@ -93,7 +93,7 @@ } // |GPUSurfaceMetalDelegate| -bool IOSSurfaceMetal::PresentTexture(intptr_t texture_id) const { +bool IOSSurfaceMetal::PresentTexture(GPUMTLTextureInfo texture) const { FML_CHECK(false) << "render to texture not supported on ios"; return false; } diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index 7868f0c144ed4..7cbba62bbc5c5 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -88,6 +88,18 @@ template("embedder_source_set") { "//third_party/skia", ] + if (embedder_enable_metal) { + sources += [ + "embedder_surface_metal.h", + "embedder_surface_metal.mm", + ] + + cflags_objc = flutter_cflags_objc + cflags_objcc = flutter_cflags_objcc + + deps += [ "//flutter/shell/platform/darwin/graphics" ] + } + public_deps = [ ":embedder_headers" ] public_configs += [ @@ -143,6 +155,7 @@ test_fixtures("fixtures") { "fixtures/dpr_noxform.png", "fixtures/dpr_xform.png", "fixtures/gradient.png", + "fixtures/gradient_metal.png", "fixtures/gradient_xform.png", "fixtures/scene_without_custom_compositor.png", "fixtures/scene_without_custom_compositor_with_xform.png", @@ -208,6 +221,16 @@ if (enable_unittests) { deps += [ "//flutter/testing:opengl" ] } + + if (test_enable_metal) { + sources += [ + "tests/embedder_test_context_metal.cc", + "tests/embedder_test_context_metal.h", + "tests/embedder_unittests_metal.cc", + ] + + deps += [ "//flutter/testing:metal" ] + } } # Tests the build in FLUTTER_ENGINE_NO_PROTOTYPES mode. diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 1dc3ea6b448ed..b2b9f26a53909 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -61,6 +61,10 @@ extern const intptr_t kPlatformStrongDillSize; #include "flutter/shell/platform/embedder/embedder_external_texture_gl.h" #endif +#ifdef SHELL_ENABLE_METAL +#include "flutter/shell/platform/embedder/embedder_surface_metal.h" +#endif + const int32_t kFlutterSemanticsNodeIdBatchEnd = -1; const int32_t kFlutterSemanticsCustomActionIdBatchEnd = -1; @@ -121,6 +125,24 @@ static bool IsSoftwareRendererConfigValid(const FlutterRendererConfig* config) { return true; } +static bool IsMetalRendererConfigValid(const FlutterRendererConfig* config) { + if (config->type != kMetal) { + return false; + } + + const FlutterMetalRendererConfig* metal_config = &config->metal; + + bool device = SAFE_ACCESS(metal_config, device, nullptr); + bool command_queue = + SAFE_ACCESS(metal_config, present_command_queue, nullptr); + + bool present = SAFE_ACCESS(metal_config, present_drawable_callback, nullptr); + bool get_texture = + SAFE_ACCESS(metal_config, get_next_drawable_callback, nullptr); + + return device && command_queue && present && get_texture; +} + static bool IsRendererValid(const FlutterRendererConfig* config) { if (config == nullptr) { return false; @@ -131,6 +153,8 @@ static bool IsRendererValid(const FlutterRendererConfig* config) { return IsOpenGLRendererConfigValid(config); case kSoftware: return IsSoftwareRendererConfigValid(config); + case kMetal: + return IsMetalRendererConfigValid(config); default: return false; } @@ -275,6 +299,74 @@ InferOpenGLPlatformViewCreationCallback( #endif } +static flutter::Shell::CreateCallback +InferMetalPlatformViewCreationCallback( + const FlutterRendererConfig* config, + void* user_data, + flutter::PlatformViewEmbedder::PlatformDispatchTable + platform_dispatch_table, + std::unique_ptr + external_view_embedder) { + if (config->type != kMetal) { + return nullptr; + } + +#ifdef SHELL_ENABLE_METAL + std::function metal_present = + [ptr = config->metal.present_drawable_callback, + user_data](flutter::GPUMTLTextureInfo texture) { + FlutterMetalTexture embedder_texture; + embedder_texture.struct_size = sizeof(FlutterMetalTexture); + embedder_texture.texture = texture.texture; + embedder_texture.texture_id = texture.texture_id; + return ptr(user_data, &embedder_texture); + }; + auto metal_get_texture = + [ptr = config->metal.get_next_drawable_callback, + user_data](const SkISize& frame_size) -> flutter::GPUMTLTextureInfo { + FlutterFrameInfo frame_info = {}; + frame_info.struct_size = sizeof(FlutterFrameInfo); + frame_info.size = {static_cast(frame_size.width()), + static_cast(frame_size.height())}; + flutter::GPUMTLTextureInfo texture_info; + + FlutterMetalTexture metal_texture = ptr(user_data, &frame_info); + texture_info.texture_id = metal_texture.texture_id; + texture_info.texture = metal_texture.texture; + return texture_info; + }; + + flutter::EmbedderSurfaceMetal::MetalDispatchTable metal_dispatch_table = { + .present = metal_present, + .get_texture = metal_get_texture, + }; + + std::shared_ptr view_embedder = + std::move(external_view_embedder); + + std::unique_ptr embedder_surface = + std::make_unique( + const_cast(config->metal.device), + const_cast( + config->metal.present_command_queue), + metal_dispatch_table, view_embedder); + + return fml::MakeCopyable( + [embedder_surface = std::move(embedder_surface), platform_dispatch_table, + external_view_embedder = view_embedder](flutter::Shell& shell) mutable { + return std::make_unique( + shell, // delegate + shell.GetTaskRunners(), // task runners + std::move(embedder_surface), // embedder surface + platform_dispatch_table, // platform dispatch table + std::move(external_view_embedder) // external view embedder + ); + }); +#else + return nullptr; +#endif +} + static flutter::Shell::CreateCallback InferSoftwarePlatformViewCreationCallback( const FlutterRendererConfig* config, @@ -333,6 +425,10 @@ InferPlatformViewCreationCallback( return InferSoftwarePlatformViewCreationCallback( config, user_data, platform_dispatch_table, std::move(external_view_embedder)); + case kMetal: + return InferMetalPlatformViewCreationCallback( + config, user_data, platform_dispatch_table, + std::move(external_view_embedder)); default: return nullptr; } diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 4819aa87fa611..71228696308cd 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -72,6 +72,10 @@ typedef enum { typedef enum { kOpenGL, kSoftware, + /// Metal is only supported on Darwin platforms (macOS / iOS). + /// iOS version >= 10.0 (device), 13.0 (simulator) + /// macOS version >= 10.14 + kMetal, } FlutterRendererType; /// Additional accessibility features that may be enabled by the platform. @@ -434,6 +438,55 @@ typedef struct { BoolPresentInfoCallback present_with_info; } FlutterOpenGLRendererConfig; +/// Alias for id. +typedef const void* FlutterMetalDeviceHandle; + +/// Alias for id. +typedef const void* FlutterMetalCommandQueueHandle; + +/// Alias for id. +typedef const void* FlutterMetalTextureHandle; + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterMetalTexture). + size_t struct_size; + /// Embedder provided unique identifier to the texture buffer. Given that the + /// `texture` handle is passed to the engine to render to, the texture buffer + /// is itseld owned by the embedder. This `texture_id` is then also given to + /// the embedder in the present callback. + int64_t texture_id; + /// Handle to the MTLTexture that is owned by the embedder. Engine will render + /// the frame into this texture. + FlutterMetalTextureHandle texture; +} FlutterMetalTexture; + +/// Callback for when a metal texture is requested. +typedef FlutterMetalTexture (*FlutterMetalTextureCallback)( + void* /* user data */, + const FlutterFrameInfo* /* frame info */); + +/// Callback for when a metal texture is presented. The texture_id here +/// corresponds to the texture_id provided by the embedder in the +/// `FlutterMetalTextureCallback` callback. +typedef bool (*FlutterMetalPresentCallback)( + void* /* user data */, + const FlutterMetalTexture* /* texture */); + +typedef struct { + /// The size of this struct. Must be sizeof(FlutterMetalRendererConfig). + size_t struct_size; + /// Alias for id. + FlutterMetalDeviceHandle device; + /// Alias for id. + FlutterMetalCommandQueueHandle present_command_queue; + /// The callback that gets invoked when the engine requests the embedder for a + /// texture to render to. + FlutterMetalTextureCallback get_next_drawable_callback; + /// The callback presented to the embedder to present a fully populated metal + /// texture to the user. + FlutterMetalPresentCallback present_drawable_callback; +} FlutterMetalRendererConfig; + typedef struct { /// The size of this struct. Must be sizeof(FlutterSoftwareRendererConfig). size_t struct_size; @@ -449,6 +502,7 @@ typedef struct { union { FlutterOpenGLRendererConfig open_gl; FlutterSoftwareRendererConfig software; + FlutterMetalRendererConfig metal; }; } FlutterRendererConfig; diff --git a/shell/platform/embedder/embedder_surface_metal.h b/shell/platform/embedder/embedder_surface_metal.h new file mode 100644 index 0000000000000..ba0d28130fb20 --- /dev/null +++ b/shell/platform/embedder/embedder_surface_metal.h @@ -0,0 +1,68 @@ +// 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_EMBEDDER_EMBEDDER_SURFACE_METAL_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_METAL_H_ + +#include "flutter/fml/macros.h" +#include "flutter/shell/gpu/gpu_surface_metal.h" +#include "flutter/shell/gpu/gpu_surface_metal_delegate.h" +#include "flutter/shell/platform/embedder/embedder_external_view_embedder.h" +#include "flutter/shell/platform/embedder/embedder_surface.h" + +namespace flutter { + +class EmbedderSurfaceMetal final : public EmbedderSurface, + public GPUSurfaceMetalDelegate { + public: + struct MetalDispatchTable { + std::function present; // required + std::function + get_texture; // required + }; + + EmbedderSurfaceMetal( + GPUMTLDeviceHandle device, + GPUMTLCommandQueueHandle command_queue, + MetalDispatchTable dispatch_table, + std::shared_ptr external_view_embedder); + + ~EmbedderSurfaceMetal() override; + + private: + bool valid_ = false; + MetalDispatchTable metal_dispatch_table_; + std::shared_ptr external_view_embedder_; + sk_sp surface_; + sk_sp main_context_; + sk_sp resource_context_; + + // |EmbedderSurface| + bool IsValid() const override; + + // |EmbedderSurface| + std::unique_ptr CreateGPUSurface() override; + + // |EmbedderSurface| + sk_sp CreateResourceContext() const override; + + // |GPUSurfaceMetalDelegate| + GPUCAMetalLayerHandle GetCAMetalLayer( + const SkISize& frame_size) const override; + + // |GPUSurfaceMetalDelegate| + bool PresentDrawable(GrMTLHandle drawable) const override; + + // |GPUSurfaceMetalDelegate| + GPUMTLTextureInfo GetMTLTexture(const SkISize& frame_size) const override; + + // |GPUSurfaceMetalDelegate| + bool PresentTexture(GPUMTLTextureInfo texture) const override; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderSurfaceMetal); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_METAL_H_ diff --git a/shell/platform/embedder/embedder_surface_metal.mm b/shell/platform/embedder/embedder_surface_metal.mm new file mode 100644 index 0000000000000..bce856164dd6e --- /dev/null +++ b/shell/platform/embedder/embedder_surface_metal.mm @@ -0,0 +1,74 @@ +// 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/embedder/embedder_surface_metal.h" + +#include "flutter/fml/logging.h" +#include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/shell/gpu/gpu_surface_metal_delegate.h" +#import "flutter/shell/platform/darwin/graphics/FlutterDarwinContextMetal.h" +#include "third_party/skia/include/gpu/GrDirectContext.h" + +namespace flutter { + +EmbedderSurfaceMetal::EmbedderSurfaceMetal( + GPUMTLDeviceHandle device, + GPUMTLCommandQueueHandle command_queue, + MetalDispatchTable metal_dispatch_table, + std::shared_ptr external_view_embedder) + : GPUSurfaceMetalDelegate(MTLRenderTargetType::kMTLTexture), + metal_dispatch_table_(metal_dispatch_table), + external_view_embedder_(external_view_embedder) { + auto darwin_metal_context = + fml::scoped_nsobject{[[[FlutterDarwinContextMetal alloc] + initWithMTLDevice:(id)device + commandQueue:(id)command_queue] retain]}; + main_context_ = darwin_metal_context.get().mainContext; + resource_context_ = darwin_metal_context.get().resourceContext; + valid_ = main_context_ && resource_context_; +} + +EmbedderSurfaceMetal::~EmbedderSurfaceMetal() = default; + +bool EmbedderSurfaceMetal::IsValid() const { + return valid_; +} + +std::unique_ptr EmbedderSurfaceMetal::CreateGPUSurface() { + if (!IsValid()) { + return nullptr; + } + + auto surface = std::make_unique(this, main_context_); + + if (!surface->IsValid()) { + return nullptr; + } + + return surface; +} + +sk_sp EmbedderSurfaceMetal::CreateResourceContext() const { + return resource_context_; +} + +GPUCAMetalLayerHandle EmbedderSurfaceMetal::GetCAMetalLayer(const SkISize& frame_info) const { + FML_CHECK(false) << "Only rendering to MTLTexture is supported."; + return nullptr; +} + +bool EmbedderSurfaceMetal::PresentDrawable(GrMTLHandle drawable) const { + FML_CHECK(false) << "Only rendering to MTLTexture is supported."; + return false; +} + +GPUMTLTextureInfo EmbedderSurfaceMetal::GetMTLTexture(const SkISize& frame_info) const { + return metal_dispatch_table_.get_texture(frame_info); +} + +bool EmbedderSurfaceMetal::PresentTexture(GPUMTLTextureInfo texture) const { + return metal_dispatch_table_.present(texture); +} + +} // namespace flutter diff --git a/shell/platform/embedder/fixtures/gradient_metal.png b/shell/platform/embedder/fixtures/gradient_metal.png new file mode 100644 index 0000000000000000000000000000000000000000..618a31cbfad98a37ec24766ef263956094f8a9ab GIT binary patch literal 45425 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

+<@P|Ey>>0zSX)PoJ%hMf z`|&<$l~O}VOZmjF>gg3+M>wCZ^B7|AV3T(`!LKigJ~jNLg}Y5JEIEVBKT?oW9I^z4 z5`!5(NP?y9yid998-!*>vNJIdi>oER+tuQA4uw8)sBIwz$aIr>O{sfp%V`mo%;8$! zPK}>n=~?-obD8!5@lZL5JyA1EZgg9*n6CA=D@+93+avrWPeA3X{g^^6*-nZZMicL) z*OD5cBpxx|DKc!lTK6pUbG=Vq!oPIN1y$X|&^uzvHFsHWhF?>?q<={4T;a~v4vy6x zmfO#&>@QQrTIoYl|2Ow%TZ88>SIq@fzSPJT+N3VzKB{xK(=7J!HAn6mhD^qpB0m_H z|J4yXaLWL{{GOur&38UTr3$-B939=Np2G?U&dk$WonoJW{3Sz4T6${OA71tmMgr3w ze7D{CRK$NUE}IMUe{hku;@(_luKGe4<*f0nUYv{HtEw@jYQh0o5wJ-ndLR|xH#j=dqFOYIAv*44Xi!z;-uflob2sRcgim_1C_gYX{YX1g{A;*mH{Vg_@PiT?gM;=Cf4O-tsB^CD zy?0he0;|sYMK#bQ5Uj$1CCLmQ^F0eH8e#Ae#!ug|7OpMkd4-{;hf(@GDJk;a?d z#mQrvvVfaPe5bujKBkTTg%4QQ@l(;0(@YF-keniW(S>_0tpD2}NNSb8OL0LMCy3;{Y7v4c0`hTSNWf|U6ljv^txK$h~?yO7XThAN>L{nT-42k*qdt1c?rpHdGDRDg?f=+{?PR)oyXS5+#&x&rVVwRF2(u21iGIzMY!sxd}BA$2&K;Z)tC3Aka|!9()WAwK|9C-f+j%<8*v@|^`-cT zx%*jfr@SUT=css8?Hsp&#vjl)>728D2ui3HIAHn~KRBIC@00?;DGk3f_cHdM#r1$a zHHIv}1uE)*){oTVceqW15?8tVRKWOq?TL2TME$Txuej^sZ~I;oc#xF?lx4p0!Dd#E-B8Kjv#Nq3KB`?OpS(l(BR`%67Rd| z?e2tQ80Lc7+eY;A-y=T14baOA(4T?J(KppyZW|}8Ll{pO;k_sN(A|7Al`NoGVGz9b$vT57gX~9=Sr-}~hFUS)2dY|- z!FNifwQO8LSKG2S+_{wd(0e;aV!v?PuhIjM=}TaP?YyhdjZbJ$d{ky|Y|8Cd%NZx3!_#V}AtXopd$9wO z$`2}b5NVM8{@HecR(+4h2vi{CpQ}lWbhuU+PZ{M*(C_-%CU0lZ%ppBZJ+{TN@`For@~e=u+@ zAPO*ZZ4w)UZ)#6nl(cp$@9NJ5(yxY_>*Vj;znEtm)G?ZNyfb$3!Y(k~E(hUq^G47o zBvy^-eO`DrbFKn;w;X%CK@BVaWWs zq^WG7^v9qku%rs@Z>qaI-b9uOlXkgXWi4M(%3EjV zGFsoB@;OYBtm~Wua{xOfoR*1}s}A_ujoHXyY2k3Rg08npG!9raeM~=fE{q4vj0Ixe zT!w;C8COY4q3ebZ3UBDZKZg}hC?p&_cm4kWS$_!) literal 0 HcmV?d00001 diff --git a/shell/platform/embedder/platform_view_embedder.cc b/shell/platform/embedder/platform_view_embedder.cc index d5cbb54d67822..0e566f3782aad 100644 --- a/shell/platform/embedder/platform_view_embedder.cc +++ b/shell/platform/embedder/platform_view_embedder.cc @@ -36,6 +36,19 @@ PlatformViewEmbedder::PlatformViewEmbedder( platform_dispatch_table_(platform_dispatch_table) {} #endif +#ifdef SHELL_ENABLE_METAL +PlatformViewEmbedder::PlatformViewEmbedder( + PlatformView::Delegate& delegate, + flutter::TaskRunners task_runners, + std::unique_ptr embedder_surface, + PlatformDispatchTable platform_dispatch_table, + std::shared_ptr external_view_embedder) + : PlatformView(delegate, std::move(task_runners)), + external_view_embedder_(external_view_embedder), + embedder_surface_(std::move(embedder_surface)), + platform_dispatch_table_(platform_dispatch_table) {} +#endif + PlatformViewEmbedder::~PlatformViewEmbedder() = default; void PlatformViewEmbedder::UpdateSemantics( diff --git a/shell/platform/embedder/platform_view_embedder.h b/shell/platform/embedder/platform_view_embedder.h index 76fc26a99b3b4..e3d89c527869b 100644 --- a/shell/platform/embedder/platform_view_embedder.h +++ b/shell/platform/embedder/platform_view_embedder.h @@ -19,6 +19,10 @@ #include "flutter/shell/platform/embedder/embedder_surface_gl.h" #endif +#ifdef SHELL_ENABLE_METAL +#include "flutter/shell/platform/embedder/embedder_surface_metal.h" +#endif + namespace flutter { class PlatformViewEmbedder final : public PlatformView { @@ -63,6 +67,16 @@ class PlatformViewEmbedder final : public PlatformView { std::shared_ptr external_view_embedder); #endif +#ifdef SHELL_ENABLE_METAL + // Creates a platform view that sets up an metal rasterizer. + PlatformViewEmbedder( + PlatformView::Delegate& delegate, + flutter::TaskRunners task_runners, + std::unique_ptr embedder_surface, + PlatformDispatchTable platform_dispatch_table, + std::shared_ptr external_view_embedder); +#endif + ~PlatformViewEmbedder() override; // |PlatformView| diff --git a/shell/platform/embedder/tests/embedder_a11y_unittests.cc b/shell/platform/embedder/tests/embedder_a11y_unittests.cc index b1d27f8a071a1..bf5b8b6d045fa 100644 --- a/shell/platform/embedder/tests/embedder_a11y_unittests.cc +++ b/shell/platform/embedder/tests/embedder_a11y_unittests.cc @@ -23,7 +23,7 @@ using Embedder11yTest = testing::EmbedderTest; // TODO(52372): De-flake and re-enable. TEST_F(Embedder11yTest, DISABLED_A11yTreeIsConsistent) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); fml::AutoResetWaitableEvent latch; diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index 3b8961cd8d477..05cda13023304 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -13,6 +13,10 @@ #include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" #endif +#ifdef SHELL_ENABLE_METAL +#include "flutter/shell/platform/embedder/tests/embedder_test_context_metal.h" +#endif + namespace flutter { namespace testing { @@ -65,6 +69,10 @@ EmbedderConfigBuilder::EmbedderConfigBuilder( }; #endif +#ifdef SHELL_ENABLE_METAL + InitializeMetalRendererConfig(); +#endif + software_renderer_config_.struct_size = sizeof(FlutterSoftwareRendererConfig); software_renderer_config_.surface_present_callback = [](void* context, const void* allocation, size_t row_bytes, @@ -153,6 +161,14 @@ void EmbedderConfigBuilder::SetOpenGLRendererConfig(SkISize surface_size) { #endif } +void EmbedderConfigBuilder::SetMetalRendererConfig(SkISize surface_size) { +#ifdef SHELL_ENABLE_METAL + renderer_config_.type = FlutterRendererType::kMetal; + renderer_config_.metal = metal_renderer_config_; + context_.SetupSurface(surface_size); +#endif +} + void EmbedderConfigBuilder::SetAssetsPath() { project_args_.assets_path = context_.GetAssetsPath().c_str(); } @@ -356,5 +372,46 @@ UniqueEngine EmbedderConfigBuilder::SetupEngine(bool run) const { return UniqueEngine{engine}; } +#ifdef SHELL_ENABLE_METAL + +void EmbedderConfigBuilder::InitializeMetalRendererConfig() { + if (context_.GetContextType() != EmbedderTestContextType::kMetalContext) { + return; + } + + metal_renderer_config_.struct_size = sizeof(metal_renderer_config_); + EmbedderTestContextMetal& metal_context = + reinterpret_cast(context_); + + metal_renderer_config_.device = + metal_context.GetTestMetalContext()->GetMetalDevice(); + metal_renderer_config_.present_command_queue = + metal_context.GetTestMetalContext()->GetMetalCommandQueue(); + metal_renderer_config_.get_next_drawable_callback = + [](void* user_data, const FlutterFrameInfo* frame_info) { + EmbedderTestContextMetal* metal_context = + reinterpret_cast(user_data); + SkISize surface_size = + SkISize::Make(frame_info->size.width, frame_info->size.height); + TestMetalContext::TextureInfo texture_info = + metal_context->GetTestMetalContext()->CreateMetalTexture( + surface_size); + FlutterMetalTexture texture; + texture.struct_size = sizeof(FlutterMetalTexture); + texture.texture_id = texture_info.texture_id; + texture.texture = + reinterpret_cast(texture_info.texture); + return texture; + }; + metal_renderer_config_.present_drawable_callback = + [](void* user_data, const FlutterMetalTexture* texture) -> bool { + EmbedderTestContextMetal* metal_context = + reinterpret_cast(user_data); + return metal_context->Present(texture->texture_id); + }; +} + +#endif // SHELL_ENABLE_METAL + } // namespace testing } // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index 808719def63f4..b013002acfaf5 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -48,6 +48,8 @@ class EmbedderConfigBuilder { void SetOpenGLRendererConfig(SkISize surface_size); + void SetMetalRendererConfig(SkISize surface_size); + // Used to explicitly set an `open_gl.fbo_callback`. Using this method will // cause your test to fail since the ctor for this class sets // `open_gl.fbo_callback_with_frame_info`. This method exists as a utility to @@ -103,6 +105,10 @@ class EmbedderConfigBuilder { FlutterSoftwareRendererConfig software_renderer_config_ = {}; #ifdef SHELL_ENABLE_GL FlutterOpenGLRendererConfig opengl_renderer_config_ = {}; +#endif +#ifdef SHELL_ENABLE_METAL + void InitializeMetalRendererConfig(); + FlutterMetalRendererConfig metal_renderer_config_ = {}; #endif std::string dart_entrypoint_; FlutterCustomTaskRunners custom_task_runners_ = {}; diff --git a/shell/platform/embedder/tests/embedder_test.cc b/shell/platform/embedder/tests/embedder_test.cc index 36d8f7e446bee..b816ab5d22307 100644 --- a/shell/platform/embedder/tests/embedder_test.cc +++ b/shell/platform/embedder/tests/embedder_test.cc @@ -9,6 +9,10 @@ #include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" #endif +#ifdef SHELL_ENABLE_METAL +#include "flutter/shell/platform/embedder/tests/embedder_test_context_metal.h" +#endif + namespace flutter { namespace testing { @@ -18,21 +22,28 @@ std::string EmbedderTest::GetFixturesDirectory() const { return GetFixturesPath(); } -EmbedderTestContext& EmbedderTest::GetEmbedderContext(ContextType type) { +EmbedderTestContext& EmbedderTest::GetEmbedderContext( + EmbedderTestContextType type) { // Setup the embedder context lazily instead of in the constructor because we // don't to do all the work if the test won't end up using context. if (!embedder_contexts_[type]) { switch (type) { - case ContextType::kSoftwareContext: + case EmbedderTestContextType::kSoftwareContext: embedder_contexts_[type] = std::make_unique( GetFixturesDirectory()); break; #ifdef SHELL_ENABLE_GL - case ContextType::kOpenGLContext: + case EmbedderTestContextType::kOpenGLContext: embedder_contexts_[type] = std::make_unique(GetFixturesDirectory()); break; +#endif +#ifdef SHELL_ENABLE_METAL + case EmbedderTestContextType::kMetalContext: + embedder_contexts_[type] = + std::make_unique(GetFixturesDirectory()); + break; #endif default: FML_DCHECK(false) << "Invalid context type specified."; diff --git a/shell/platform/embedder/tests/embedder_test.h b/shell/platform/embedder/tests/embedder_test.h index 275d47bc8c44f..f00d802e0f23d 100644 --- a/shell/platform/embedder/tests/embedder_test.h +++ b/shell/platform/embedder/tests/embedder_test.h @@ -18,19 +18,14 @@ namespace testing { class EmbedderTest : public ThreadTest { public: - enum class ContextType { - kSoftwareContext, - kOpenGLContext, - }; - EmbedderTest(); std::string GetFixturesDirectory() const; - EmbedderTestContext& GetEmbedderContext(ContextType type); + EmbedderTestContext& GetEmbedderContext(EmbedderTestContextType type); private: - std::map> + std::map> embedder_contexts_; FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTest); diff --git a/shell/platform/embedder/tests/embedder_test_context.h b/shell/platform/embedder/tests/embedder_test_context.h index b44eabe453e7c..39cbbc0c6290b 100644 --- a/shell/platform/embedder/tests/embedder_test_context.h +++ b/shell/platform/embedder/tests/embedder_test_context.h @@ -38,6 +38,12 @@ struct AOTDataDeleter { using UniqueAOTData = std::unique_ptr<_FlutterEngineAOTData, AOTDataDeleter>; +enum class EmbedderTestContextType { + kSoftwareContext, + kOpenGLContext, + kMetalContext, +}; + class EmbedderTestContext { public: EmbedderTestContext(std::string assets_path = ""); @@ -77,6 +83,8 @@ class EmbedderTestContext { virtual size_t GetSurfacePresentCount() const = 0; + virtual EmbedderTestContextType GetContextType() const = 0; + // TODO(gw280): encapsulate these properly for subclasses to use protected: // This allows the builder to access the hooks. diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.cc b/shell/platform/embedder/tests/embedder_test_context_gl.cc index c967794f87671..e5eef963f8636 100644 --- a/shell/platform/embedder/tests/embedder_test_context_gl.cc +++ b/shell/platform/embedder/tests/embedder_test_context_gl.cc @@ -3,13 +3,14 @@ // found in the LICENSE file. #include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" -#include "flutter/shell/platform/embedder/tests/embedder_test_compositor_gl.h" #include "flutter/fml/make_copyable.h" #include "flutter/fml/paths.h" #include "flutter/runtime/dart_vm.h" #include "flutter/shell/platform/embedder/tests/embedder_assertions.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_compositor_gl.h" #include "flutter/testing/testing.h" +#include "tests/embedder_test.h" #include "third_party/dart/runtime/bin/elf_loader.h" #include "third_party/skia/include/core/SkSurface.h" @@ -55,11 +56,7 @@ bool EmbedderTestContextGL::GLPresent(uint32_t fbo_id) { FireRootSurfacePresentCallbackIfPresent( [&]() { return gl_surface_->GetRasterSurfaceSnapshot(); }); - if (!gl_surface_->Present()) { - return false; - } - - return true; + return gl_surface_->Present(); } void EmbedderTestContextGL::SetGLGetFBOCallback(GLGetFBOCallback callback) { @@ -103,6 +100,10 @@ size_t EmbedderTestContextGL::GetSurfacePresentCount() const { return gl_surface_present_count_; } +EmbedderTestContextType EmbedderTestContextGL::GetContextType() const { + return EmbedderTestContextType::kOpenGLContext; +} + uint32_t EmbedderTestContextGL::GetWindowFBOId() const { FML_CHECK(gl_surface_); return gl_surface_->GetWindowFBOId(); diff --git a/shell/platform/embedder/tests/embedder_test_context_gl.h b/shell/platform/embedder/tests/embedder_test_context_gl.h index 370e3d87db607..6ab911f07952c 100644 --- a/shell/platform/embedder/tests/embedder_test_context_gl.h +++ b/shell/platform/embedder/tests/embedder_test_context_gl.h @@ -22,6 +22,9 @@ class EmbedderTestContextGL : public EmbedderTestContext { size_t GetSurfacePresentCount() const override; + // |EmbedderTestContext| + EmbedderTestContextType GetContextType() const override; + //---------------------------------------------------------------------------- /// @brief Sets a callback that will be invoked (on the raster task /// runner) when the engine asks the embedder for a new FBO ID at diff --git a/shell/platform/embedder/tests/embedder_test_context_metal.cc b/shell/platform/embedder/tests/embedder_test_context_metal.cc new file mode 100644 index 0000000000000..3514262b8b022 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_context_metal.cc @@ -0,0 +1,53 @@ +// 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/embedder/tests/embedder_test_context_metal.h" + +#include + +#include "flutter/fml/logging.h" + +namespace flutter { +namespace testing { + +EmbedderTestContextMetal::EmbedderTestContextMetal(std::string assets_path) + : EmbedderTestContext(assets_path) { + metal_context_ = std::make_unique(); +} + +EmbedderTestContextMetal::~EmbedderTestContextMetal() {} + +void EmbedderTestContextMetal::SetupSurface(SkISize surface_size) { + FML_CHECK(surface_size_.isEmpty()); + surface_size_ = surface_size; +} + +size_t EmbedderTestContextMetal::GetSurfacePresentCount() const { + return present_count_; +} + +EmbedderTestContextType EmbedderTestContextMetal::GetContextType() const { + return EmbedderTestContextType::kMetalContext; +} + +void EmbedderTestContextMetal::SetupCompositor() { + FML_CHECK(false) << "Compositor rendering not supported in metal."; +} + +TestMetalContext* EmbedderTestContextMetal::GetTestMetalContext() { + return metal_context_.get(); +} + +bool EmbedderTestContextMetal::Present(int64_t texture_id) { + FireRootSurfacePresentCallbackIfPresent([&]() { + auto metal_surface_ = + TestMetalSurface::Create(*metal_context_, texture_id, surface_size_); + return metal_surface_->GetRasterSurfaceSnapshot(); + }); + present_count_++; + return metal_context_->Present(texture_id); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_test_context_metal.h b/shell/platform/embedder/tests/embedder_test_context_metal.h new file mode 100644 index 0000000000000..788e7bfacfbc0 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_context_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_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONTEXT_METAL_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONTEXT_METAL_H_ + +#include "flutter/shell/platform/embedder/tests/embedder_test_context.h" +#include "flutter/testing/test_metal_context.h" +#include "flutter/testing/test_metal_surface.h" + +namespace flutter { +namespace testing { + +class EmbedderTestContextMetal : public EmbedderTestContext { + public: + explicit EmbedderTestContextMetal(std::string assets_path = ""); + + ~EmbedderTestContextMetal() override; + + // |EmbedderTestContext| + EmbedderTestContextType GetContextType() const override; + + // |EmbedderTestContext| + size_t GetSurfacePresentCount() const override; + + // |EmbedderTestContext| + void SetupCompositor() override; + + bool Present(int64_t texture_id); + + TestMetalContext* GetTestMetalContext(); + + private: + // This allows the builder to access the hooks. + friend class EmbedderConfigBuilder; + + SkISize surface_size_ = SkISize::MakeEmpty(); + std::unique_ptr metal_context_; + size_t present_count_ = 0; + + void SetupSurface(SkISize surface_size) override; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestContextMetal); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_CONTEXT_METAL_H_ diff --git a/shell/platform/embedder/tests/embedder_test_context_software.cc b/shell/platform/embedder/tests/embedder_test_context_software.cc index ae35dd9198c50..9ac6e62d44020 100644 --- a/shell/platform/embedder/tests/embedder_test_context_software.cc +++ b/shell/platform/embedder/tests/embedder_test_context_software.cc @@ -34,6 +34,10 @@ size_t EmbedderTestContextSoftware::GetSurfacePresentCount() const { return software_surface_present_count_; } +EmbedderTestContextType EmbedderTestContextSoftware::GetContextType() const { + return EmbedderTestContextType::kSoftwareContext; +} + void EmbedderTestContextSoftware::SetupSurface(SkISize surface_size) { surface_size_ = surface_size; } diff --git a/shell/platform/embedder/tests/embedder_test_context_software.h b/shell/platform/embedder/tests/embedder_test_context_software.h index 7f146edeffa53..7f5b815c2944a 100644 --- a/shell/platform/embedder/tests/embedder_test_context_software.h +++ b/shell/platform/embedder/tests/embedder_test_context_software.h @@ -18,6 +18,9 @@ class EmbedderTestContextSoftware : public EmbedderTestContext { size_t GetSurfacePresentCount() const override; + // |EmbedderTestContext| + EmbedderTestContextType GetContextType() const override; + bool Present(sk_sp image); protected: diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 538ab9ae15dad..2fe1cb19122d9 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -42,7 +42,7 @@ TEST(EmbedderTestNoFixture, MustNotRunWithInvalidArgs) { } TEST_F(EmbedderTest, CanLaunchAndShutdownWithValidProjectArgs) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); fml::AutoResetWaitableEvent latch; context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); EmbedderConfigBuilder builder(context); @@ -57,7 +57,7 @@ TEST_F(EmbedderTest, CanLaunchAndShutdownWithValidProjectArgs) { // TODO(41999): Disabled because flaky. TEST_F(EmbedderTest, DISABLED_CanLaunchAndShutdownMultipleTimes) { EmbedderConfigBuilder builder( - GetEmbedderContext(ContextType::kSoftwareContext)); + GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); builder.SetSoftwareRendererConfig(); for (size_t i = 0; i < 3; ++i) { auto engine = builder.LaunchEngine(); @@ -67,7 +67,7 @@ TEST_F(EmbedderTest, DISABLED_CanLaunchAndShutdownMultipleTimes) { } TEST_F(EmbedderTest, CanInvokeCustomEntrypoint) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); static fml::AutoResetWaitableEvent latch; Dart_NativeFunction entrypoint = [](Dart_NativeArguments args) { latch.Signal(); @@ -82,7 +82,7 @@ TEST_F(EmbedderTest, CanInvokeCustomEntrypoint) { } TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); fml::AutoResetWaitableEvent latch1; fml::AutoResetWaitableEvent latch2; @@ -125,7 +125,7 @@ TEST_F(EmbedderTest, CanInvokeCustomEntrypointMacro) { std::atomic_size_t EmbedderTestTaskRunner::sEmbedderTaskRunnerIdentifiers = {}; TEST_F(EmbedderTest, CanSpecifyCustomPlatformTaskRunner) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); fml::AutoResetWaitableEvent latch; // Run the test on its own thread with a message loop so that it can safely @@ -197,7 +197,7 @@ TEST(EmbedderTestNoFixture, CanGetCurrentTimeInNanoseconds) { } TEST_F(EmbedderTest, CanReloadSystemFonts) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); auto engine = builder.LaunchEngine(); @@ -208,7 +208,7 @@ TEST_F(EmbedderTest, CanReloadSystemFonts) { } TEST_F(EmbedderTest, IsolateServiceIdSent) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); fml::AutoResetWaitableEvent latch; fml::Thread thread; @@ -251,7 +251,7 @@ TEST_F(EmbedderTest, IsolateServiceIdSent) { /// immediately collects the same. /// TEST_F(EmbedderTest, CanCreateAndCollectCallbacks) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("platform_messages_response"); @@ -289,7 +289,8 @@ TEST_F(EmbedderTest, PlatformMessagesCanReceiveResponse) { CreateNewThread()->PostTask([&]() { captures.thread_id = std::this_thread::get_id(); - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = + GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("platform_messages_response"); @@ -345,7 +346,7 @@ TEST_F(EmbedderTest, PlatformMessagesCanReceiveResponse) { /// callback with the response is invoked to assert integrity. /// TEST_F(EmbedderTest, PlatformMessagesCanBeSentWithoutResponseHandles) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("platform_messages_no_response"); @@ -390,7 +391,7 @@ TEST_F(EmbedderTest, PlatformMessagesCanBeSentWithoutResponseHandles) { /// Tests that a null platform message can be sent. /// TEST_F(EmbedderTest, NullPlatformMessagesCanBeSent) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("null_platform_messages"); @@ -432,7 +433,7 @@ TEST_F(EmbedderTest, NullPlatformMessagesCanBeSent) { /// isn't equals to 0. /// TEST_F(EmbedderTest, InvalidPlatformMessages) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); auto engine = builder.LaunchEngine(); @@ -456,7 +457,7 @@ TEST_F(EmbedderTest, InvalidPlatformMessages) { /// set to true by default in these unit-tests). /// TEST_F(EmbedderTest, VMShutsDownWhenNoEnginesInProcess) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); const auto launch_count = DartVM::GetVMLaunchCount(); @@ -475,7 +476,7 @@ TEST_F(EmbedderTest, VMShutsDownWhenNoEnginesInProcess) { //------------------------------------------------------------------------------ /// TEST_F(EmbedderTest, DartEntrypointArgs) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); builder.AddDartEntrypointArgument("foo"); @@ -509,7 +510,7 @@ TEST_F(EmbedderTest, VMAndIsolateSnapshotSizesAreRedundantInAOTMode) { GTEST_SKIP(); return; } - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); @@ -529,7 +530,7 @@ TEST_F(EmbedderTest, VMAndIsolateSnapshotSizesAreRedundantInAOTMode) { /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownSceneWithSoftwareCompositor) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(SkISize::Make(800, 600)); @@ -701,7 +702,7 @@ TEST_F(EmbedderTest, /// TEST_F(EmbedderTest, CanCreateInitializedEngine) { EmbedderConfigBuilder builder( - GetEmbedderContext(ContextType::kSoftwareContext)); + GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); builder.SetSoftwareRendererConfig(); auto engine = builder.InitializeEngine(); ASSERT_TRUE(engine.is_valid()); @@ -713,7 +714,7 @@ TEST_F(EmbedderTest, CanCreateInitializedEngine) { /// TEST_F(EmbedderTest, CanRunInitializedEngine) { EmbedderConfigBuilder builder( - GetEmbedderContext(ContextType::kSoftwareContext)); + GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); builder.SetSoftwareRendererConfig(); auto engine = builder.InitializeEngine(); ASSERT_TRUE(engine.is_valid()); @@ -728,7 +729,7 @@ TEST_F(EmbedderTest, CanRunInitializedEngine) { /// TEST_F(EmbedderTest, CaDeinitializeAnEngine) { EmbedderConfigBuilder builder( - GetEmbedderContext(ContextType::kSoftwareContext)); + GetEmbedderContext(EmbedderTestContextType::kSoftwareContext)); builder.SetSoftwareRendererConfig(); auto engine = builder.InitializeEngine(); ASSERT_TRUE(engine.is_valid()); @@ -751,7 +752,7 @@ TEST_F(EmbedderTest, CaDeinitializeAnEngine) { } TEST_F(EmbedderTest, CanUpdateLocales) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); builder.SetDartEntrypoint("can_receive_locale_updates"); @@ -810,7 +811,7 @@ TEST_F(EmbedderTest, CanUpdateLocales) { } TEST_F(EmbedderTest, LocalizationCallbacksCalled) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); fml::AutoResetWaitableEvent latch; context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); EmbedderConfigBuilder builder(context); @@ -842,7 +843,7 @@ TEST_F(EmbedderTest, CanQueryDartAOTMode) { } TEST_F(EmbedderTest, VerifyB143464703WithSoftwareBackend) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(SkISize::Make(1024, 600)); @@ -961,7 +962,7 @@ TEST_F(EmbedderTest, VerifyB143464703WithSoftwareBackend) { } TEST_F(EmbedderTest, CanSendLowMemoryNotification) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); @@ -990,7 +991,8 @@ TEST_F(EmbedderTest, CanPostTaskToAllNativeThreads) { auto platform_task_runner = CreateNewThread("platform_thread"); platform_task_runner->PostTask([&]() { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = + GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetSoftwareRendererConfig(); @@ -1128,7 +1130,7 @@ TEST_F(EmbedderTest, MustNotRunWithMultipleAOTSources) { GTEST_SKIP(); return; } - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder( context, @@ -1170,7 +1172,7 @@ TEST_F(EmbedderTest, CanLaunchAndShutdownWithAValidElfSource) { GTEST_SKIP(); return; } - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); fml::AutoResetWaitableEvent latch; context.AddIsolateCreateCallback([&latch]() { latch.Signal(); }); diff --git a/shell/platform/embedder/tests/embedder_unittests_gl.cc b/shell/platform/embedder/tests/embedder_unittests_gl.cc index 0b7b4e3312a90..a7fc2e93160f3 100644 --- a/shell/platform/embedder/tests/embedder_unittests_gl.cc +++ b/shell/platform/embedder/tests/embedder_unittests_gl.cc @@ -36,7 +36,7 @@ using EmbedderTest = testing::EmbedderTest; TEST_F(EmbedderTest, CanCreateOpenGLRenderingEngine) { EmbedderConfigBuilder builder( - GetEmbedderContext(ContextType::kOpenGLContext)); + GetEmbedderContext(EmbedderTestContextType::kOpenGLContext)); builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); auto engine = builder.LaunchEngine(); ASSERT_TRUE(engine.is_valid()); @@ -49,7 +49,7 @@ TEST_F(EmbedderTest, CanCreateOpenGLRenderingEngine) { /// TEST_F(EmbedderTest, MustPreventEngineLaunchWhenRequiredCompositorArgsAreAbsent) { - auto& context = GetEmbedderContext(ContextType::kSoftwareContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(1, 1)); builder.SetCompositor(); @@ -65,7 +65,7 @@ TEST_F(EmbedderTest, /// complete OpenGL textures. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLFramebuffer) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -156,7 +156,7 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLFramebuffer) { /// Layers in a hierarchy containing a platform view should not be cached. The /// other layers in the hierarchy should be, however. TEST_F(EmbedderTest, RasterCacheDisabledWithPlatformViews) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -260,7 +260,7 @@ TEST_F(EmbedderTest, RasterCacheDisabledWithPlatformViews) { /// The RasterCache should normally be enabled. /// TEST_F(EmbedderTest, RasterCacheEnabled) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -331,7 +331,7 @@ TEST_F(EmbedderTest, RasterCacheEnabled) { /// the individual layers are OpenGL textures. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLTexture) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -423,7 +423,7 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLTexture) { /// individual layers are software buffers. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToSoftwareBuffer) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -514,7 +514,7 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToSoftwareBuffer) { /// Test the layer structure and pixels rendered when using a custom compositor. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownScene) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -685,7 +685,7 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownScene) { /// thread merging mechanism must not interfere with the custom compositor. /// TEST_F(EmbedderTest, CustomCompositorMustWorkWithCustomTaskRunner) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); @@ -810,7 +810,7 @@ TEST_F(EmbedderTest, CustomCompositorMustWorkWithCustomTaskRunner) { /// and a single layer. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithRootLayerOnly) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -877,7 +877,7 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithRootLayerOnly) { /// and ensure that a redundant layer is not added. /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithPlatformLayerOnBottom) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -990,7 +990,7 @@ TEST_F(EmbedderTest, CompositorMustBeAbleToRenderWithPlatformLayerOnBottom) { /// TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownSceneWithRootSurfaceTransformation) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); @@ -1164,7 +1164,7 @@ TEST_F(EmbedderTest, } TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositor) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); @@ -1190,7 +1190,7 @@ TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositor) { } TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositorWithTransformation) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); const auto root_surface_transformation = SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); @@ -1223,7 +1223,7 @@ TEST_F(EmbedderTest, CanRenderSceneWithoutCustomCompositorWithTransformation) { } TEST_F(EmbedderTest, CanRenderGradientWithoutCompositor) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); @@ -1248,7 +1248,7 @@ TEST_F(EmbedderTest, CanRenderGradientWithoutCompositor) { } TEST_F(EmbedderTest, CanRenderGradientWithoutCompositorWithXform) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); const auto root_surface_transformation = SkMatrix().preTranslate(0, 800).preRotate(-90, 0, 0); @@ -1281,7 +1281,7 @@ TEST_F(EmbedderTest, CanRenderGradientWithoutCompositorWithXform) { } TEST_F(EmbedderTest, CanRenderGradientWithCompositor) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); @@ -1309,7 +1309,7 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositor) { } TEST_F(EmbedderTest, CanRenderGradientWithCompositorWithXform) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); // This must match the transformation provided in the // |CanRenderGradientWithoutCompositorWithXform| test to ensure that @@ -1346,7 +1346,7 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositorWithXform) { } TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayer) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); @@ -1451,7 +1451,7 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayer) { } TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayerWithXform) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); // This must match the transformation provided in the // |CanRenderGradientWithoutCompositorWithXform| test to ensure that @@ -1565,7 +1565,7 @@ TEST_F(EmbedderTest, CanRenderGradientWithCompositorOnNonRootLayerWithXform) { } TEST_F(EmbedderTest, VerifyB141980393) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); @@ -1686,7 +1686,7 @@ TEST_F(EmbedderTest, CanCreateEmbedderWithCustomRenderTaskRunner) { } }); EmbedderConfigBuilder builder( - GetEmbedderContext(ContextType::kOpenGLContext)); + GetEmbedderContext(EmbedderTestContextType::kOpenGLContext)); builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); builder.SetRenderTaskRunner( @@ -1745,7 +1745,7 @@ TEST_F(EmbedderTest, platform_task_runner->PostTask([&]() { EmbedderConfigBuilder builder( - GetEmbedderContext(ContextType::kOpenGLContext)); + GetEmbedderContext(EmbedderTestContextType::kOpenGLContext)); builder.SetDartEntrypoint("can_render_scene_without_custom_compositor"); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); builder.SetRenderTaskRunner( @@ -1799,7 +1799,7 @@ TEST_F(EmbedderTest, TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownScenePixelRatioOnSurface) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -1890,7 +1890,7 @@ TEST_F(EmbedderTest, TEST_F( EmbedderTest, CompositorMustBeAbleToRenderKnownScenePixelRatioOnSurfaceWithRootSurfaceXformation) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(600, 800)); @@ -1984,7 +1984,7 @@ TEST_F( TEST_F(EmbedderTest, PushingMutlipleFramesSetsUpNewRecordingCanvasWithCustomCompositor) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); @@ -2026,7 +2026,7 @@ TEST_F(EmbedderTest, TEST_F(EmbedderTest, PushingMutlipleFramesSetsUpNewRecordingCanvasWithoutCustomCompositor) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); @@ -2063,7 +2063,7 @@ TEST_F(EmbedderTest, } TEST_F(EmbedderTest, PlatformViewMutatorsAreValid) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -2159,7 +2159,7 @@ TEST_F(EmbedderTest, PlatformViewMutatorsAreValid) { } TEST_F(EmbedderTest, PlatformViewMutatorsAreValidWithPixelRatio) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -2256,7 +2256,7 @@ TEST_F(EmbedderTest, PlatformViewMutatorsAreValidWithPixelRatio) { TEST_F(EmbedderTest, PlatformViewMutatorsAreValidWithPixelRatioAndRootSurfaceTransformation) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -2358,7 +2358,7 @@ TEST_F(EmbedderTest, } TEST_F(EmbedderTest, EmptySceneIsAcceptable) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -2384,7 +2384,7 @@ TEST_F(EmbedderTest, EmptySceneIsAcceptable) { } TEST_F(EmbedderTest, SceneWithNoRootContainerIsAcceptable) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -2414,7 +2414,7 @@ TEST_F(EmbedderTest, SceneWithNoRootContainerIsAcceptable) { // Verifies that https://skia-review.googlesource.com/c/skia/+/259174 is pulled // into the engine. TEST_F(EmbedderTest, ArcEndCapsAreDrawnCorrectly) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 1024)); @@ -2448,7 +2448,7 @@ TEST_F(EmbedderTest, ArcEndCapsAreDrawnCorrectly) { } TEST_F(EmbedderTest, ClipsAreCorrectlyCalculated) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(400, 300)); @@ -2527,7 +2527,7 @@ TEST_F(EmbedderTest, ClipsAreCorrectlyCalculated) { } TEST_F(EmbedderTest, ComplexClipsAreCorrectlyCalculated) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(1024, 600)); @@ -2611,7 +2611,7 @@ TEST_F(EmbedderTest, ComplexClipsAreCorrectlyCalculated) { } TEST_F(EmbedderTest, ObjectsCanBePostedViaPorts) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 1024)); builder.SetDartEntrypoint("objects_can_be_posted"); @@ -2810,7 +2810,7 @@ TEST_F(EmbedderTest, ObjectsCanBePostedViaPorts) { } TEST_F(EmbedderTest, CompositorCanPostZeroLayersForPresentation) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); @@ -2843,7 +2843,7 @@ TEST_F(EmbedderTest, CompositorCanPostZeroLayersForPresentation) { } TEST_F(EmbedderTest, CompositorCanPostOnlyPlatformViews) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); @@ -2906,7 +2906,7 @@ TEST_F(EmbedderTest, CompositorCanPostOnlyPlatformViews) { } TEST_F(EmbedderTest, CompositorRenderTargetsAreRecycled) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); @@ -2951,7 +2951,7 @@ TEST_F(EmbedderTest, CompositorRenderTargetsAreRecycled) { } TEST_F(EmbedderTest, CompositorRenderTargetsAreInStableOrder) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); @@ -3020,7 +3020,7 @@ TEST_F(EmbedderTest, CompositorRenderTargetsAreInStableOrder) { } TEST_F(EmbedderTest, FrameInfoContainsValidWidthAndHeight) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); @@ -3063,7 +3063,7 @@ TEST_F(EmbedderTest, FrameInfoContainsValidWidthAndHeight) { } TEST_F(EmbedderTest, MustNotRunWithBothFBOCallbacksSet) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); @@ -3074,7 +3074,7 @@ TEST_F(EmbedderTest, MustNotRunWithBothFBOCallbacksSet) { } TEST_F(EmbedderTest, MustNotRunWithBothPresentCallbacksSet) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); @@ -3085,7 +3085,7 @@ TEST_F(EmbedderTest, MustNotRunWithBothPresentCallbacksSet) { } TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(600, 1024)); @@ -3128,7 +3128,7 @@ TEST_F(EmbedderTest, PresentInfoContainsValidFBOId) { } TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithDisplayId) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -3170,7 +3170,7 @@ TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithDisplayId) { } TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithoutDisplayId) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -3212,7 +3212,7 @@ TEST_F(EmbedderTest, SetSingleDisplayConfigurationWithoutDisplayId) { } TEST_F(EmbedderTest, SetValidMultiDisplayConfiguration) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -3261,7 +3261,7 @@ TEST_F(EmbedderTest, SetValidMultiDisplayConfiguration) { } TEST_F(EmbedderTest, MultipleDisplaysWithSingleDisplayTrueIsInvalid) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -3307,7 +3307,7 @@ TEST_F(EmbedderTest, MultipleDisplaysWithSingleDisplayTrueIsInvalid) { } TEST_F(EmbedderTest, MultipleDisplaysWithSameDisplayIdIsInvalid) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(800, 600)); @@ -3353,7 +3353,7 @@ TEST_F(EmbedderTest, MultipleDisplaysWithSameDisplayIdIsInvalid) { } TEST_F(EmbedderTest, CompositorRenderTargetsNotRecycledWhenAvoidsCacheSet) { - auto& context = GetEmbedderContext(ContextType::kOpenGLContext); + auto& context = GetEmbedderContext(EmbedderTestContextType::kOpenGLContext); EmbedderConfigBuilder builder(context); builder.SetOpenGLRendererConfig(SkISize::Make(300, 200)); diff --git a/shell/platform/embedder/tests/embedder_unittests_metal.cc b/shell/platform/embedder/tests/embedder_unittests_metal.cc new file mode 100644 index 0000000000000..d95d0b9c725c1 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_unittests_metal.cc @@ -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. + +#define FML_USED_ON_EMBEDDER + +#include +#include + +#include "embedder.h" +#include "flutter/shell/platform/embedder/tests/embedder_assertions.h" +#include "flutter/shell/platform/embedder/tests/embedder_config_builder.h" +#include "flutter/shell/platform/embedder/tests/embedder_test.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_context_gl.h" +#include "flutter/shell/platform/embedder/tests/embedder_unittests_util.h" +#include "flutter/testing/assertions_skia.h" +#include "flutter/testing/testing.h" + +namespace flutter { +namespace testing { + +using EmbedderTest = testing::EmbedderTest; + +TEST_F(EmbedderTest, CanRenderGradientWithMetal) { + auto& context = GetEmbedderContext(EmbedderTestContextType::kMetalContext); + + EmbedderConfigBuilder builder(context); + + builder.SetDartEntrypoint("render_gradient"); + builder.SetMetalRendererConfig(SkISize::Make(800, 600)); + + auto renderered_scene = context.GetNextSceneImage(); + + auto engine = builder.LaunchEngine(); + ASSERT_TRUE(engine.is_valid()); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + event.pixel_ratio = 1.0; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + + ASSERT_TRUE(ImageMatchesFixture("gradient_metal.png", renderered_scene)); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_unittests_util.cc b/shell/platform/embedder/tests/embedder_unittests_util.cc index 5058e2c30815e..59479a808b391 100644 --- a/shell/platform/embedder/tests/embedder_unittests_util.cc +++ b/shell/platform/embedder/tests/embedder_unittests_util.cc @@ -4,6 +4,8 @@ #define FML_USED_ON_EMBEDDER +#include + #include "flutter/shell/platform/embedder/tests/embedder_unittests_util.h" namespace flutter { @@ -58,6 +60,30 @@ bool RasterImagesAreSame(sk_sp a, sk_sp b) { return false; } + if (pixmapA.info() != pixmapB.info()) { + // Given that the sizes are equal but the infos aren't equal + // we have a situation where the color-type, color-space and + // alpha type are potentially mismatched. We fallback to compating + // pixel-wise values for alpha and color. + for (int x = 0; x < pixmapA.width(); x++) { + for (int y = 0; y < pixmapA.height(); y++) { + SkColor color_a = pixmapA.getColor(x, y); + SkColor color_b = pixmapB.getColor(x, y); + if (color_a != color_b) { + return false; + } + float alpha_a = pixmapA.getAlphaf(x, y); + float alpha_b = pixmapB.getAlphaf(x, y); + if (std::abs(alpha_a - alpha_b) > + std::numeric_limits::epsilon()) { + return false; + } + } + } + + return true; + } + return ::memcmp(pixmapA.addr(), pixmapB.addr(), sizeA) == 0; } @@ -68,7 +94,7 @@ bool WriteImageToDisk(const fml::UniqueFD& directory, return false; } - auto data = image->encodeToData(SkEncodedImageFormat::kPNG, 100); + auto data = image->encodeToData(); if (!data) { return false; @@ -94,6 +120,8 @@ bool ImageMatchesFixture(const std::string& fixture_file_name, FML_CHECK(fixture_image) << "Could not create image from fixture: " << fixture_file_name; + FML_CHECK(scene_image) << "Invalid scene image."; + auto scene_image_subset = scene_image->makeSubset( SkIRect::MakeWH(fixture_image->width(), fixture_image->height())); diff --git a/testing/BUILD.gn b/testing/BUILD.gn index 71fc74b08b501..3b1c3bd19de11 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -137,24 +137,23 @@ if (enable_unittests) { # backend. This way, all tests compile on all platforms but the Metal backend # is exercised on platforms where Metal itself is available. source_set("metal") { - testonly = true - - sources = [ - "test_metal_surface.cc", - "test_metal_surface.h", - ] - - defines = [] - if (shell_enable_metal) { - sources += [ "test_metal_surface_impl.mm" ] - defines += [ "TESTING_ENABLE_METAL" ] + sources = [ + "test_metal_context.h", + "test_metal_context.mm", + "test_metal_surface.cc", + "test_metal_surface.h", + "test_metal_surface_impl.h", + "test_metal_surface_impl.mm", + ] + + deps = [ + ":skia", + "//flutter/fml", + ] } - deps = [ - ":skia", - "//flutter/fml", - ] + testonly = true } test_fixtures("testing_fixtures") { @@ -166,17 +165,19 @@ if (enable_unittests) { executable("testing_unittests") { testonly = true - sources = [ - "mock_canvas_unittests.cc", - "test_metal_surface_unittests.cc", - ] + sources = [ "mock_canvas_unittests.cc" ] deps = [ - ":metal", ":skia", ":testing", ":testing_fixtures", "//flutter/runtime:libdart", ] + + if (shell_enable_metal) { + sources += [ "test_metal_surface_unittests.cc" ] + + deps += [ ":metal" ] + } } } diff --git a/testing/test_metal_context.h b/testing/test_metal_context.h new file mode 100644 index 0000000000000..936f66fcb2f1c --- /dev/null +++ b/testing/test_metal_context.h @@ -0,0 +1,51 @@ +// 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_TESTING_TEST_METAL_CONTEXT_H_ +#define FLUTTER_TESTING_TEST_METAL_CONTEXT_H_ + +#include +#include + +#include "third_party/skia/include/gpu/GrDirectContext.h" +#include "third_party/skia/include/gpu/mtl/GrMtlTypes.h" + +namespace flutter { + +class TestMetalContext { + public: + struct TextureInfo { + int64_t texture_id; + void* texture; + }; + + TestMetalContext(); + + ~TestMetalContext(); + + void* GetMetalDevice() const; + + void* GetMetalCommandQueue() const; + + sk_sp GetSkiaContext() const; + + /// Returns texture_id = -1 when texture creation fails. + TextureInfo CreateMetalTexture(const SkISize& size); + + bool Present(int64_t texture_id); + + TextureInfo GetTextureInfo(int64_t texture_id); + + private: + void* device_; + void* command_queue_; + sk_sp skia_context_; + std::mutex textures_mutex; + int64_t texture_id_ctr_ = 1; // guarded by textures_mutex + std::map> textures_; // guarded by textures_mutex +}; + +} // namespace flutter + +#endif // FLUTTER_TESTING_TEST_METAL_CONTEXT_H_ diff --git a/testing/test_metal_context.mm b/testing/test_metal_context.mm new file mode 100644 index 0000000000000..e6f5e480d971e --- /dev/null +++ b/testing/test_metal_context.mm @@ -0,0 +1,126 @@ +// 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/testing/test_metal_context.h" + +#include +#include + +#include "flutter/fml/logging.h" +#include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "third_party/skia/include/core/SkSurface.h" + +namespace flutter { + +TestMetalContext::TestMetalContext() { + auto device = fml::scoped_nsprotocol{[MTLCreateSystemDefaultDevice() retain]}; + if (!device) { + FML_LOG(ERROR) << "Could not acquire Metal device."; + return; + } + + auto command_queue = fml::scoped_nsobject{[device.get() newCommandQueue]}; + if (!command_queue) { + FML_LOG(ERROR) << "Could not create the default command queue."; + return; + } + + [command_queue.get() setLabel:@"Flutter Test Queue"]; + + // Skia expect arguments to `MakeMetal` transfer ownership of the reference in for release later + // when the GrDirectContext is collected. + skia_context_ = GrDirectContext::MakeMetal([device.get() retain], [command_queue.get() retain]); + if (!skia_context_) { + FML_LOG(ERROR) << "Could not create the GrDirectContext from the Metal Device " + "and command queue."; + } + + device_ = [device.get() retain]; + command_queue_ = [command_queue.get() retain]; +} + +TestMetalContext::~TestMetalContext() { + std::scoped_lock lock(textures_mutex); + textures_.clear(); + if (device_) { + [(__bridge id)device_ release]; + } + if (command_queue_) { + [(__bridge id)command_queue_ release]; + } +} + +void* TestMetalContext::GetMetalDevice() const { + return device_; +} + +void* TestMetalContext::GetMetalCommandQueue() const { + return command_queue_; +} + +sk_sp TestMetalContext::GetSkiaContext() const { + return skia_context_; +} + +TestMetalContext::TextureInfo TestMetalContext::CreateMetalTexture(const SkISize& size) { + std::scoped_lock lock(textures_mutex); + auto texture_descriptor = fml::scoped_nsobject{ + [[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm + width:size.width() + height:size.height() + mipmapped:NO] retain]}; + + // The most pessimistic option and disables all optimizations but allows tests + // the most flexible access to the surface. They may read and write to the + // surface from shaders or use as a pixel view. + texture_descriptor.get().usage = MTLTextureUsageUnknown; + + if (!texture_descriptor) { + FML_CHECK(false) << "Invalid texture descriptor."; + return {.texture_id = -1, .texture = nullptr}; + } + + id device = (__bridge id)GetMetalDevice(); + sk_cf_obj texture = + sk_cf_obj{[[device newTextureWithDescriptor:texture_descriptor.get()] retain]}; + + if (!texture) { + FML_CHECK(false) << "Could not create texture from texture descriptor."; + return {.texture_id = -1, .texture = nullptr}; + } + + const int64_t texture_id = texture_id_ctr_++; + textures_[texture_id] = texture; + std::cout << "inserting texture with id: " << texture_id << std::endl; + + return { + .texture_id = texture_id, + .texture = texture.get(), + }; +} + +// Don't remove the texture from the map here. +bool TestMetalContext::Present(int64_t texture_id) { + std::scoped_lock lock(textures_mutex); + if (textures_.find(texture_id) == textures_.end()) { + return false; + } else { + return true; + } +} + +TestMetalContext::TextureInfo TestMetalContext::GetTextureInfo(int64_t texture_id) { + std::scoped_lock lock(textures_mutex); + if (textures_.find(texture_id) == textures_.end()) { + FML_CHECK(false) << "Invalid texture id: " << texture_id; + return {.texture_id = -1, .texture = nullptr}; + } else { + return { + .texture_id = texture_id, + .texture = textures_[texture_id].get(), + }; + } +} + +} // namespace flutter diff --git a/testing/test_metal_surface.cc b/testing/test_metal_surface.cc index 9d93715b7c107..e7136d8d0880f 100644 --- a/testing/test_metal_surface.cc +++ b/testing/test_metal_surface.cc @@ -4,27 +4,28 @@ #include "flutter/testing/test_metal_surface.h" -#if TESTING_ENABLE_METAL +#include "flutter/fml/logging.h" #include "flutter/testing/test_metal_surface_impl.h" -#endif // TESTING_ENABLE_METAL namespace flutter { bool TestMetalSurface::PlatformSupportsMetal() { -#if TESTING_ENABLE_METAL return true; -#else // TESTING_ENABLE_METAL - return false; -#endif // TESTING_ENABLE_METAL } std::unique_ptr TestMetalSurface::Create( + const TestMetalContext& test_metal_context, SkISize surface_size) { -#if TESTING_ENABLE_METAL - return std::make_unique(surface_size); -#else // TESTING_ENABLE_METAL - return nullptr; -#endif // TESTING_ENABLE_METAL + return std::make_unique(test_metal_context, + surface_size); +} + +std::unique_ptr TestMetalSurface::Create( + const TestMetalContext& test_metal_context, + int64_t texture_id, + SkISize surface_size) { + return std::make_unique(test_metal_context, texture_id, + surface_size); } TestMetalSurface::TestMetalSurface() = default; @@ -43,4 +44,8 @@ sk_sp TestMetalSurface::GetSurface() const { return impl_ ? impl_->GetSurface() : nullptr; } +sk_sp TestMetalSurface::GetRasterSurfaceSnapshot() { + return impl_ ? impl_->GetRasterSurfaceSnapshot() : nullptr; +} + } // namespace flutter diff --git a/testing/test_metal_surface.h b/testing/test_metal_surface.h index ed81b4bf3346b..bf40f8effa657 100644 --- a/testing/test_metal_surface.h +++ b/testing/test_metal_surface.h @@ -6,6 +6,7 @@ #define FLUTTER_TESTING_TEST_METAL_SURFACE_H_ #include "flutter/fml/macros.h" +#include "flutter/testing/test_metal_context.h" #include "third_party/skia/include/core/SkSize.h" #include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/GrDirectContext.h" @@ -21,6 +22,12 @@ class TestMetalSurface { static bool PlatformSupportsMetal(); static std::unique_ptr Create( + const TestMetalContext& test_metal_context, + SkISize surface_size = SkISize::MakeEmpty()); + + static std::unique_ptr Create( + const TestMetalContext& test_metal_context, + int64_t texture_id, SkISize surface_size = SkISize::MakeEmpty()); virtual ~TestMetalSurface(); @@ -31,13 +38,15 @@ class TestMetalSurface { virtual sk_sp GetSurface() const; + virtual sk_sp GetRasterSurfaceSnapshot(); + protected: TestMetalSurface(); private: std::unique_ptr impl_; - TestMetalSurface(std::unique_ptr impl); + explicit TestMetalSurface(std::unique_ptr impl); FML_DISALLOW_COPY_AND_ASSIGN(TestMetalSurface); }; diff --git a/testing/test_metal_surface_impl.h b/testing/test_metal_surface_impl.h index 5ef5e63d2bcc4..3eba1840b361b 100644 --- a/testing/test_metal_surface_impl.h +++ b/testing/test_metal_surface_impl.h @@ -6,20 +6,29 @@ #define FLUTTER_TESTING_TEST_METAL_SURFACE_IMPL_H_ #include "flutter/fml/macros.h" +#include "flutter/testing/test_metal_context.h" #include "flutter/testing/test_metal_surface.h" namespace flutter { class TestMetalSurfaceImpl : public TestMetalSurface { public: - TestMetalSurfaceImpl(SkISize surface_size); + TestMetalSurfaceImpl(const TestMetalContext& test_metal_context, + const SkISize& surface_size); + + TestMetalSurfaceImpl(const TestMetalContext& test_metal_context, + int64_t texture_id, + const SkISize& surface_size); // |TestMetalSurface| ~TestMetalSurfaceImpl() override; private: + void Init(const TestMetalContext::TextureInfo& texture_info, + const SkISize& surface_size); + + const TestMetalContext& test_metal_context_; bool is_valid_ = false; - sk_sp context_; sk_sp surface_; // |TestMetalSurface| @@ -31,6 +40,9 @@ class TestMetalSurfaceImpl : public TestMetalSurface { // |TestMetalSurface| sk_sp GetSurface() const override; + // |TestMetalSurface| + sk_sp GetRasterSurfaceSnapshot() override; + FML_DISALLOW_COPY_AND_ASSIGN(TestMetalSurfaceImpl); }; diff --git a/testing/test_metal_surface_impl.mm b/testing/test_metal_surface_impl.mm index 9ba283545f6c1..2da07333e54e3 100644 --- a/testing/test_metal_surface_impl.mm +++ b/testing/test_metal_surface_impl.mm @@ -8,28 +8,13 @@ #include "flutter/fml/logging.h" #include "flutter/fml/platform/darwin/scoped_nsobject.h" +#include "flutter/testing/test_metal_context.h" #include "third_party/skia/include/core/SkSurface.h" namespace flutter { -TestMetalSurfaceImpl::TestMetalSurfaceImpl(SkISize surface_size) { - if (surface_size.isEmpty()) { - FML_LOG(ERROR) << "Size of test Metal surface was empty."; - return; - } - - auto device = fml::scoped_nsobject{[MTLCreateSystemDefaultDevice() retain]}; - if (!device) { - FML_LOG(ERROR) << "Could not acquire Metal device."; - return; - } - - auto command_queue = fml::scoped_nsobject{[device.get() newCommandQueue]}; - if (!command_queue) { - FML_LOG(ERROR) << "Could not create the default command queue."; - return; - } - +void TestMetalSurfaceImpl::Init(const TestMetalContext::TextureInfo& texture_info, + const SkISize& surface_size) { auto texture_descriptor = fml::scoped_nsobject{ [[MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm width:surface_size.width() @@ -46,58 +31,75 @@ return; } - auto texture = - fml::scoped_nsobject{[device.get() newTextureWithDescriptor:texture_descriptor.get()]}; + id texture = (__bridge id)texture_info.texture; + + GrMtlTextureInfo skia_texture_info; + skia_texture_info.fTexture.reset([texture retain]); + GrBackendTexture backend_texture(surface_size.width(), surface_size.height(), GrMipmapped::kNo, + skia_texture_info); - if (!texture) { - FML_LOG(ERROR) << "Could not create texture from texture descriptor."; + sk_sp surface = SkSurface::MakeFromBackendTexture( + test_metal_context_.GetSkiaContext().get(), backend_texture, kTopLeft_GrSurfaceOrigin, 1, + kBGRA_8888_SkColorType, nullptr, nullptr); + + if (!surface) { + FML_LOG(ERROR) << "Could not create Skia surface from a Metal texture."; return; } - auto skia_context = GrDirectContext::MakeMetal(device.get(), command_queue.get()); - - if (skia_context) { - // Skia wants ownership of the device and queue. If a context was created, - // we now no longer own the argument. Release the arguments only on - // successful creation of the context. - FML_ALLOW_UNUSED_LOCAL(device.release()); - FML_ALLOW_UNUSED_LOCAL(command_queue.release()); - } else { - FML_LOG(ERROR) << "Could not create the GrDirectContext from the Metal Device " - "and command queue."; + surface_ = std::move(surface); + is_valid_ = true; +} + +TestMetalSurfaceImpl::TestMetalSurfaceImpl(const TestMetalContext& test_metal_context, + int64_t texture_id, + const SkISize& surface_size) + : test_metal_context_(test_metal_context) { + TestMetalContext::TextureInfo texture_info = + const_cast(test_metal_context_).GetTextureInfo(texture_id); + Init(texture_info, surface_size); +} + +TestMetalSurfaceImpl::TestMetalSurfaceImpl(const TestMetalContext& test_metal_context, + const SkISize& surface_size) + : test_metal_context_(test_metal_context) { + if (surface_size.isEmpty()) { + FML_LOG(ERROR) << "Size of test Metal surface was empty."; return; } + TestMetalContext::TextureInfo texture_info = + const_cast(test_metal_context_).CreateMetalTexture(surface_size); + Init(texture_info, surface_size); +} - GrMtlTextureInfo skia_texture_info; - skia_texture_info.fTexture = sk_cf_obj{[texture.get() retain]}; - - auto backend_render_target = GrBackendRenderTarget{ - surface_size.width(), // width - surface_size.height(), // height - 1, // sample count - skia_texture_info // texture info - }; - - auto surface = SkSurface::MakeFromBackendRenderTarget( - skia_context.get(), // context - backend_render_target, // backend render target - kTopLeft_GrSurfaceOrigin, // surface origin - kBGRA_8888_SkColorType, // color type - nullptr, // color space - nullptr, // surface properties - nullptr, // release proc (texture is already ref counted in sk_cf_obj) - nullptr // release context - ); +sk_sp TestMetalSurfaceImpl::GetRasterSurfaceSnapshot() { + if (!IsValid()) { + return nullptr; + } - if (!surface) { - FML_LOG(ERROR) << "Could not create Skia surface from a Metal texture."; - return; + if (!surface_) { + FML_LOG(ERROR) << "Aborting snapshot because of on-screen surface " + "acquisition failure."; + return nullptr; } - surface_ = std::move(surface); - context_ = std::move(skia_context); + auto device_snapshot = surface_->makeImageSnapshot(); - is_valid_ = true; + if (!device_snapshot) { + FML_LOG(ERROR) << "Could not create the device snapshot while attempting " + "to snapshot the Metal surface."; + return nullptr; + } + + auto host_snapshot = device_snapshot->makeRasterImage(); + + if (!host_snapshot) { + FML_LOG(ERROR) << "Could not create the host snapshot while attempting to " + "snapshot the Metal surface."; + return nullptr; + } + + return host_snapshot; } // |TestMetalSurface| @@ -107,10 +109,12 @@ bool TestMetalSurfaceImpl::IsValid() const { return is_valid_; } + // |TestMetalSurface| sk_sp TestMetalSurfaceImpl::GetGrContext() const { - return IsValid() ? context_ : nullptr; + return IsValid() ? test_metal_context_.GetSkiaContext() : nullptr; } + // |TestMetalSurface| sk_sp TestMetalSurfaceImpl::GetSurface() const { return IsValid() ? surface_ : nullptr; diff --git a/testing/test_metal_surface_unittests.cc b/testing/test_metal_surface_unittests.cc index 5765e6e656cd6..daf53170dc9fd 100644 --- a/testing/test_metal_surface_unittests.cc +++ b/testing/test_metal_surface_unittests.cc @@ -2,18 +2,22 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flutter/testing/test_metal_context.h" #include "flutter/testing/test_metal_surface.h" #include "flutter/testing/testing.h" namespace flutter { namespace testing { +#ifdef SHELL_ENABLE_METAL + TEST(TestMetalSurface, EmptySurfaceIsInvalid) { if (!TestMetalSurface::PlatformSupportsMetal()) { GTEST_SKIP(); } - auto surface = TestMetalSurface::Create(); + TestMetalContext metal_context = TestMetalContext(); + auto surface = TestMetalSurface::Create(metal_context); ASSERT_NE(surface, nullptr); ASSERT_FALSE(surface->IsValid()); } @@ -23,12 +27,16 @@ TEST(TestMetalSurface, CanCreateValidTestMetalSurface) { GTEST_SKIP(); } - auto surface = TestMetalSurface::Create(SkISize::Make(100, 100)); + TestMetalContext metal_context = TestMetalContext(); + auto surface = + TestMetalSurface::Create(metal_context, SkISize::Make(100, 100)); ASSERT_NE(surface, nullptr); ASSERT_TRUE(surface->IsValid()); ASSERT_NE(surface->GetSurface(), nullptr); ASSERT_NE(surface->GetGrContext(), nullptr); } +#endif + } // namespace testing } // namespace flutter diff --git a/tools/gn b/tools/gn index e65a60abed577..9ba1b15604ea9 100755 --- a/tools/gn +++ b/tools/gn @@ -247,6 +247,18 @@ def to_gn_args(args): # attributes in release modes till the toolchain is updated. gn_args['skia_enable_api_available_macro'] = args.runtime_mode != "release" + if sys.platform == 'darwin' and args.macos_enable_metal: + # OpenGL is deprecated on macOS > 10.11. + # This is not neccessarily needed but enabling this until we have a way to + # build a macOS metal only shell and a gl only shell. + gn_args['allow_deprecated_api_calls'] = True + gn_args['skia_use_metal'] = True + gn_args['shell_enable_metal'] = True + # Skia has Metal support on macOS version >= 10.14. + MACOS_SKIA_METAL_SUPPORTED_MIN_VERSION = '10.14' + gn_args['mac_sdk_min'] = MACOS_SKIA_METAL_SUPPORTED_MIN_VERSION + gn_args['mac_deployment_target'] = MACOS_SKIA_METAL_SUPPORTED_MIN_VERSION + if args.enable_vulkan: # Enable vulkan in the Flutter shell. gn_args['shell_enable_vulkan'] = True @@ -365,6 +377,7 @@ def parse_args(args): parser.add_argument('--target-triple', type=str) parser.add_argument('--operator-new-alignment', dest='operator_new_alignment', type=str, default=None) + parser.add_argument('--macos-enable-metal', action='store_true', default=False) parser.add_argument('--enable-vulkan', action='store_true', default=False) parser.add_argument('--enable-fontconfig', action='store_true', default=False)