From e8f954409d7cbc3cdd00ae51436ef911e0227ee2 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Tue, 13 Aug 2019 14:53:19 -0700 Subject: [PATCH] Allow embedder controlled composition of Flutter layers. (#10195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch allows embedders to split the Flutter layer tree into multiple chunks. These chunks are meant to be composed one on top of another. This gives embedders a chance to interleave their own contents between these chunks. The Flutter embedder API already provides hooks for the specification of textures for the Flutter engine to compose within its own hierarchy (for camera feeds, video, etc..). However, not all embedders can render the contents of such sources into textures the Flutter engine can accept. Moreover, this composition model may have overheads that are non-trivial for certain use cases. In such cases, the embedder may choose to specify multiple render target for Flutter to render into instead of just one. The use of this API allows embedders to perform composition very similar to the iOS embedder. This composition model is used on that platform for the embedding of UIKit view such and web view and map views within the Flutter hierarchy. However, do note that iOS also has threading configurations that are currently not available to custom embedders. The embedder API updates in this patch are ABI stable and existing embedders will continue to work are normal. For embedders that want to enable this composition mode, the API is designed to make it easy to opt into the same in an incremental manner. Rendering of contents into the “root” rendering surface remains unchanged. However, now the application can push “platform views” via a scene builder. These platform views need to handled by a FlutterCompositor specified in a new field at the end of the FlutterProjectArgs struct. When a new platform view in introduced within the layer tree, the compositor will ask the embedder to create a new render target for that platform view. Render targets can currently be OpenGL framebuffers, OpenGL textures or software buffers. The type of the render target returned by the embedder must be compatible with the root render surface. That is, if the root render surface is an OpenGL framebuffer, the render target for each platform view must either be a texture or a framebuffer in the same OpenGL context. New render target types as well as root renderers for newer APIs like Metal & Vulkan can and will be added in the future. The addition of these APIs will be done in an ABI & API stable manner. As Flutter renders frames, it gives the embedder a callback with information about the position of the various platform views in the effective hierarchy. The embedder is then meant to put the contents of the render targets that it setup and had previously given to the engine onto the screen (of course interleaving the contents of the platform views). Unit-tests have been added that test not only the structure and properties of layer hierarchy given to the compositor, but also the contents of the texels rendered by a test compositor using both the OpenGL and software rendering backends. Fixes b/132812775 Fixes flutter/flutter#35410 --- ci/licenses_golden/licenses_flutter | 7 + flow/embedded_views.h | 13 +- fml/closure.h | 40 ++ fml/mapping.cc | 11 + fml/mapping.h | 9 +- lib/ui/painting/image_decoder_unittests.cc | 2 +- shell/common/rasterizer.cc | 31 +- shell/common/shell_test.cc | 7 +- shell/common/shell_test.h | 3 + shell/gpu/BUILD.gn | 2 + shell/gpu/gpu_surface_gl.cc | 27 +- shell/gpu/gpu_surface_gl.h | 13 +- shell/gpu/gpu_surface_gl_delegate.cc | 4 - shell/gpu/gpu_surface_gl_delegate.h | 2 +- shell/gpu/gpu_surface_software.cc | 5 - shell/gpu/gpu_surface_software.h | 12 +- shell/gpu/gpu_surface_software_delegate.cc | 13 + shell/gpu/gpu_surface_software_delegate.h | 65 ++ shell/platform/android/android_surface_gl.cc | 7 +- shell/platform/android/android_surface_gl.h | 3 + .../android/android_surface_software.cc | 5 + .../android/android_surface_software.h | 3 + shell/platform/darwin/ios/ios_surface_gl.h | 23 +- shell/platform/darwin/ios/ios_surface_gl.mm | 21 +- .../darwin/ios/ios_surface_software.h | 21 +- .../darwin/ios/ios_surface_software.mm | 22 +- shell/platform/embedder/BUILD.gn | 11 +- shell/platform/embedder/embedder.cc | 299 +++++++++- shell/platform/embedder/embedder.h | 228 ++++++- .../embedder_external_view_embedder.cc | 267 +++++++++ .../embedder_external_view_embedder.h | 130 ++++ .../embedder/embedder_render_target.cc | 37 ++ .../embedder/embedder_render_target.h | 78 +++ shell/platform/embedder/embedder_surface.h | 1 + .../platform/embedder/embedder_surface_gl.cc | 20 +- shell/platform/embedder/embedder_surface_gl.h | 12 +- .../embedder/embedder_surface_software.cc | 11 +- .../embedder/embedder_surface_software.h | 9 +- .../platform/embedder/fixtures/compositor.png | Bin 0 -> 1707 bytes shell/platform/embedder/fixtures/main.dart | 76 +++ .../embedder/platform_view_embedder.cc | 18 +- .../embedder/platform_view_embedder.h | 15 +- .../embedder/tests/embedder_assertions.h | 250 ++++++++ .../embedder/tests/embedder_config_builder.cc | 65 +- .../embedder/tests/embedder_config_builder.h | 12 +- .../platform/embedder/tests/embedder_test.cc | 4 +- shell/platform/embedder/tests/embedder_test.h | 6 +- .../tests/embedder_test_compositor.cc | 305 ++++++++++ .../embedder/tests/embedder_test_compositor.h | 85 +++ ...er_context.cc => embedder_test_context.cc} | 105 +++- ...dder_context.h => embedder_test_context.h} | 19 +- .../embedder/tests/embedder_unittests.cc | 561 +++++++++++++++++- testing/BUILD.gn | 8 +- testing/assertions.h | 21 + testing/test_gl_surface.cc | 96 ++- testing/test_gl_surface.h | 11 +- testing/testing.cc | 17 +- testing/testing.h | 3 + 58 files changed, 2942 insertions(+), 209 deletions(-) create mode 100644 shell/gpu/gpu_surface_software_delegate.cc create mode 100644 shell/gpu/gpu_surface_software_delegate.h create mode 100644 shell/platform/embedder/embedder_external_view_embedder.cc create mode 100644 shell/platform/embedder/embedder_external_view_embedder.h create mode 100644 shell/platform/embedder/embedder_render_target.cc create mode 100644 shell/platform/embedder/embedder_render_target.h create mode 100644 shell/platform/embedder/fixtures/compositor.png create mode 100644 shell/platform/embedder/tests/embedder_assertions.h create mode 100644 shell/platform/embedder/tests/embedder_test_compositor.cc create mode 100644 shell/platform/embedder/tests/embedder_test_compositor.h rename shell/platform/embedder/tests/{embedder_context.cc => embedder_test_context.cc} (52%) rename shell/platform/embedder/tests/{embedder_context.h => embedder_test_context.h} (83%) create mode 100644 testing/assertions.h diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index df416b3b1c9db..63be8038afcfa 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -521,6 +521,8 @@ FILE: ../../../flutter/shell/gpu/gpu_surface_metal.h FILE: ../../../flutter/shell/gpu/gpu_surface_metal.mm FILE: ../../../flutter/shell/gpu/gpu_surface_software.cc FILE: ../../../flutter/shell/gpu/gpu_surface_software.h +FILE: ../../../flutter/shell/gpu/gpu_surface_software_delegate.cc +FILE: ../../../flutter/shell/gpu/gpu_surface_software_delegate.h FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan.cc FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan.h FILE: ../../../flutter/shell/platform/android/AndroidManifest.xml @@ -810,9 +812,13 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_engine.cc FILE: ../../../flutter/shell/platform/embedder/embedder_engine.h FILE: ../../../flutter/shell/platform/embedder/embedder_external_texture_gl.cc FILE: ../../../flutter/shell/platform/embedder/embedder_external_texture_gl.h +FILE: ../../../flutter/shell/platform/embedder/embedder_external_view_embedder.cc +FILE: ../../../flutter/shell/platform/embedder/embedder_external_view_embedder.h FILE: ../../../flutter/shell/platform/embedder/embedder_include.c FILE: ../../../flutter/shell/platform/embedder/embedder_platform_message_response.cc FILE: ../../../flutter/shell/platform/embedder/embedder_platform_message_response.h +FILE: ../../../flutter/shell/platform/embedder/embedder_render_target.cc +FILE: ../../../flutter/shell/platform/embedder/embedder_render_target.h FILE: ../../../flutter/shell/platform/embedder/embedder_safe_access.h FILE: ../../../flutter/shell/platform/embedder/embedder_surface.cc FILE: ../../../flutter/shell/platform/embedder/embedder_surface.h @@ -824,6 +830,7 @@ FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.cc FILE: ../../../flutter/shell/platform/embedder/embedder_task_runner.h FILE: ../../../flutter/shell/platform/embedder/embedder_thread_host.cc FILE: ../../../flutter/shell/platform/embedder/embedder_thread_host.h +FILE: ../../../flutter/shell/platform/embedder/fixtures/compositor.png FILE: ../../../flutter/shell/platform/embedder/fixtures/main.dart FILE: ../../../flutter/shell/platform/embedder/platform_view_embedder.cc FILE: ../../../flutter/shell/platform/embedder/platform_view_embedder.h diff --git a/flow/embedded_views.h b/flow/embedded_views.h index f2ac5b49ca91b..afaf9037a1cc0 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -15,6 +15,7 @@ #include "third_party/skia/include/core/SkRRect.h" #include "third_party/skia/include/core/SkRect.h" #include "third_party/skia/include/core/SkSize.h" +#include "third_party/skia/include/core/SkSurface.h" namespace flutter { @@ -194,11 +195,19 @@ class ExternalViewEmbedder { public: ExternalViewEmbedder() = default; + virtual ~ExternalViewEmbedder() = default; + + // Usually, the root surface is not owned by the view embedder. However, if + // the view embedder wants to provide a surface to the rasterizer, it may + // return one here. This surface takes priority over the surface materialized + // from the on-screen render target. + virtual sk_sp GetRootSurface() = 0; + // Call this in-lieu of |SubmitFrame| to clear pre-roll state and // sets the stage for the next pre-roll. virtual void CancelFrame() = 0; - virtual void BeginFrame(SkISize frame_size) = 0; + virtual void BeginFrame(SkISize frame_size, GrContext* context) = 0; virtual void PrerollCompositeEmbeddedView( int view_id, @@ -220,8 +229,6 @@ class ExternalViewEmbedder { virtual bool SubmitFrame(GrContext* context); - virtual ~ExternalViewEmbedder() = default; - FML_DISALLOW_COPY_AND_ASSIGN(ExternalViewEmbedder); }; // ExternalViewEmbedder diff --git a/fml/closure.h b/fml/closure.h index fa0afe0104271..c67e4d4554f95 100644 --- a/fml/closure.h +++ b/fml/closure.h @@ -7,10 +7,50 @@ #include +#include "flutter/fml/macros.h" + namespace fml { using closure = std::function; +//------------------------------------------------------------------------------ +/// @brief Wraps a closure that is invoked in the destructor unless +/// released by the caller. +/// +/// This is especially useful in dealing with APIs that return a +/// resource by accepting ownership of a sub-resource and a closure +/// that releases that resource. When such APIs are chained, each +/// link in the chain must check that the next member in the chain +/// has accepted the resource. If not, it must invoke the closure +/// eagerly. Not doing this results in a resource leak in the +/// erroneous case. Using this wrapper, the closure can be released +/// once the next call in the chain has successfully accepted +/// ownership of the resource. If not, the closure gets invoked +/// automatically at the end of the scope. This covers the cases +/// where there are early returns as well. +/// +class ScopedCleanupClosure { + public: + ScopedCleanupClosure(fml::closure closure) : closure_(closure) {} + + ~ScopedCleanupClosure() { + if (closure_) { + closure_(); + } + } + + fml::closure Release() { + fml::closure closure = closure_; + closure_ = nullptr; + return closure; + } + + private: + fml::closure closure_; + + FML_DISALLOW_COPY_AND_ASSIGN(ScopedCleanupClosure); +}; + } // namespace fml #endif // FLUTTER_FML_CLOSURE_H_ diff --git a/fml/mapping.cc b/fml/mapping.cc index bb69052e1945c..c349c5b30e025 100644 --- a/fml/mapping.cc +++ b/fml/mapping.cc @@ -79,6 +79,17 @@ const uint8_t* DataMapping::GetMapping() const { // NonOwnedMapping +NonOwnedMapping::NonOwnedMapping(const uint8_t* data, + size_t size, + ReleaseProc release_proc) + : data_(data), size_(size), release_proc_(release_proc) {} + +NonOwnedMapping::~NonOwnedMapping() { + if (release_proc_) { + release_proc_(data_, size_); + } +} + size_t NonOwnedMapping::GetSize() const { return size_; } diff --git a/fml/mapping.h b/fml/mapping.h index ceeb24662e1c3..cd077bd4483c9 100644 --- a/fml/mapping.h +++ b/fml/mapping.h @@ -102,8 +102,12 @@ class DataMapping final : public Mapping { class NonOwnedMapping final : public Mapping { public: - NonOwnedMapping(const uint8_t* data, size_t size) - : data_(data), size_(size) {} + using ReleaseProc = std::function; + NonOwnedMapping(const uint8_t* data, + size_t size, + ReleaseProc release_proc = nullptr); + + ~NonOwnedMapping() override; // |Mapping| size_t GetSize() const override; @@ -114,6 +118,7 @@ class NonOwnedMapping final : public Mapping { private: const uint8_t* const data_; const size_t size_; + const ReleaseProc release_proc_; FML_DISALLOW_COPY_AND_ASSIGN(NonOwnedMapping); }; diff --git a/lib/ui/painting/image_decoder_unittests.cc b/lib/ui/painting/image_decoder_unittests.cc index b107905576b2c..52a77760ffaf6 100644 --- a/lib/ui/painting/image_decoder_unittests.cc +++ b/lib/ui/painting/image_decoder_unittests.cc @@ -19,7 +19,7 @@ class TestIOManager final : public IOManager { public: TestIOManager(fml::RefPtr task_runner, bool has_gpu_context = true) - : gl_context_(has_gpu_context ? gl_surface_.CreateContext() : nullptr), + : gl_context_(has_gpu_context ? gl_surface_.CreateGrContext() : nullptr), weak_gl_context_factory_( has_gpu_context ? std::make_unique>( gl_context_.get()) diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 24a24d5a100f1..1421af3908503 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -223,17 +223,34 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { // for instrumentation. compositor_context_->ui_time().SetLapTime(layer_tree.build_time()); - auto* canvas = frame->SkiaCanvas(); - auto* external_view_embedder = surface_->GetExternalViewEmbedder(); + sk_sp embedder_root_surface; + if (external_view_embedder != nullptr) { - external_view_embedder->BeginFrame(layer_tree.frame_size()); + external_view_embedder->BeginFrame(layer_tree.frame_size(), + surface_->GetContext()); + embedder_root_surface = external_view_embedder->GetRootSurface(); } + // If the external view embedder has specified an optional root surface, the + // root surface transformation is set by the embedder instead of + // having to apply it here. + SkMatrix root_surface_transformation = + embedder_root_surface ? SkMatrix{} : surface_->GetRootTransformation(); + + auto root_surface_canvas = embedder_root_surface + ? embedder_root_surface->getCanvas() + : frame->SkiaCanvas(); + auto compositor_frame = compositor_context_->AcquireFrame( - surface_->GetContext(), canvas, external_view_embedder, - surface_->GetRootTransformation(), true, gpu_thread_merger_); + surface_->GetContext(), // skia GrContext + root_surface_canvas, // root surface canvas + external_view_embedder, // external view embedder + root_surface_transformation, // root surface transformation + true, // instrumentation enabled + gpu_thread_merger_ // thread merger + ); if (compositor_frame) { RasterStatus raster_status = compositor_frame->Raster(layer_tree, false); @@ -244,10 +261,12 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { if (external_view_embedder != nullptr) { external_view_embedder->SubmitFrame(surface_->GetContext()); } + FireNextFrameCallbackIfPresent(); - if (surface_->GetContext()) + if (surface_->GetContext()) { surface_->GetContext()->performDeferredCleanup(kSkiaCleanupExpiration); + } return raster_status; } diff --git a/shell/common/shell_test.cc b/shell/common/shell_test.cc index 414275a3dcb49..e796916cb6fee 100644 --- a/shell/common/shell_test.cc +++ b/shell/common/shell_test.cc @@ -217,7 +217,7 @@ ShellTestPlatformView::~ShellTestPlatformView() = default; // |PlatformView| std::unique_ptr ShellTestPlatformView::CreateRenderingSurface() { - return std::make_unique(this); + return std::make_unique(this, true); } // |GPUSurfaceGLDelegate| @@ -248,5 +248,10 @@ GPUSurfaceGLDelegate::GLProcResolver ShellTestPlatformView::GetGLProcResolver() }; } +// |GPUSurfaceGLDelegate| +ExternalViewEmbedder* ShellTestPlatformView::GetExternalViewEmbedder() { + return nullptr; +} + } // namespace testing } // namespace flutter diff --git a/shell/common/shell_test.h b/shell/common/shell_test.h index 662453c0d0c44..55436a82a29ed 100644 --- a/shell/common/shell_test.h +++ b/shell/common/shell_test.h @@ -99,6 +99,9 @@ class ShellTestPlatformView : public PlatformView, public GPUSurfaceGLDelegate { // |GPUSurfaceGLDelegate| GLProcResolver GetGLProcResolver() const override; + // |GPUSurfaceGLDelegate| + ExternalViewEmbedder* GetExternalViewEmbedder() override; + FML_DISALLOW_COPY_AND_ASSIGN(ShellTestPlatformView); }; diff --git a/shell/gpu/BUILD.gn b/shell/gpu/BUILD.gn index dc8d8c3f4dd7c..e7b3d679bb3b0 100644 --- a/shell/gpu/BUILD.gn +++ b/shell/gpu/BUILD.gn @@ -18,6 +18,8 @@ source_set("gpu_surface_software") { sources = [ "$gpu_dir/gpu_surface_software.cc", "$gpu_dir/gpu_surface_software.h", + "$gpu_dir/gpu_surface_software_delegate.cc", + "$gpu_dir/gpu_surface_software_delegate.h", ] deps = gpu_common_deps diff --git a/shell/gpu/gpu_surface_gl.cc b/shell/gpu/gpu_surface_gl.cc index 6ddbcde23b3c3..3d02bb59f19c0 100644 --- a/shell/gpu/gpu_surface_gl.cc +++ b/shell/gpu/gpu_surface_gl.cc @@ -33,8 +33,11 @@ static const int kGrCacheMaxCount = 8192; // system channel. static const size_t kGrCacheMaxByteSize = 24 * (1 << 20); -GPUSurfaceGL::GPUSurfaceGL(GPUSurfaceGLDelegate* delegate) - : delegate_(delegate), weak_factory_(this) { +GPUSurfaceGL::GPUSurfaceGL(GPUSurfaceGLDelegate* delegate, + bool render_to_surface) + : delegate_(delegate), + render_to_surface_(render_to_surface), + weak_factory_(this) { if (!delegate_->GLContextMakeCurrent()) { FML_LOG(ERROR) << "Could not make the context current to setup the gr context."; @@ -68,13 +71,18 @@ GPUSurfaceGL::GPUSurfaceGL(GPUSurfaceGLDelegate* delegate) delegate_->GLContextClearCurrent(); - valid_ = true; context_owner_ = true; + + valid_ = true; } GPUSurfaceGL::GPUSurfaceGL(sk_sp gr_context, - GPUSurfaceGLDelegate* delegate) - : delegate_(delegate), context_(gr_context), weak_factory_(this) { + GPUSurfaceGLDelegate* delegate, + bool render_to_surface) + : delegate_(delegate), + context_(gr_context), + render_to_surface_(render_to_surface), + weak_factory_(this) { if (!delegate_->GLContextMakeCurrent()) { FML_LOG(ERROR) << "Could not make the context current to setup the gr context."; @@ -236,6 +244,15 @@ std::unique_ptr GPUSurfaceGL::AcquireFrame(const SkISize& size) { return nullptr; } + // TODO(38466): Refactor GPU surface APIs take into account the fact that an + // external view embedder may want to render to the root surface. + if (!render_to_surface_) { + return std::make_unique( + nullptr, [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { + return true; + }); + } + const auto root_surface_transformation = GetRootTransformation(); sk_sp surface = diff --git a/shell/gpu/gpu_surface_gl.h b/shell/gpu/gpu_surface_gl.h index 9ad53f6228f73..97325569bfd16 100644 --- a/shell/gpu/gpu_surface_gl.h +++ b/shell/gpu/gpu_surface_gl.h @@ -19,10 +19,12 @@ namespace flutter { class GPUSurfaceGL : public Surface { public: - GPUSurfaceGL(GPUSurfaceGLDelegate* delegate); + GPUSurfaceGL(GPUSurfaceGLDelegate* delegate, bool render_to_surface); // Creates a new GL surface reusing an existing GrContext. - GPUSurfaceGL(sk_sp gr_context, GPUSurfaceGLDelegate* delegate); + GPUSurfaceGL(sk_sp gr_context, + GPUSurfaceGLDelegate* delegate, + bool render_to_surface); ~GPUSurfaceGL() override; @@ -49,9 +51,14 @@ class GPUSurfaceGL : public Surface { sk_sp context_; sk_sp onscreen_surface_; sk_sp offscreen_surface_; + bool context_owner_; + // TODO(38466): Refactor GPU surface APIs take into account the fact that an + // external view embedder may want to render to the root surface. This is a + // hack to make avoid allocating resources for the root surface when an + // external view embedder is present. + const bool render_to_surface_; bool valid_ = false; fml::WeakPtrFactory weak_factory_; - bool context_owner_; bool CreateOrUpdateSurfaces(const SkISize& size); diff --git a/shell/gpu/gpu_surface_gl_delegate.cc b/shell/gpu/gpu_surface_gl_delegate.cc index 4511141bbb833..1ef969fe8acc5 100644 --- a/shell/gpu/gpu_surface_gl_delegate.cc +++ b/shell/gpu/gpu_surface_gl_delegate.cc @@ -22,10 +22,6 @@ SkMatrix GPUSurfaceGLDelegate::GLContextSurfaceTransformation() const { return matrix; } -flutter::ExternalViewEmbedder* GPUSurfaceGLDelegate::GetExternalViewEmbedder() { - return nullptr; -} - GPUSurfaceGLDelegate::GLProcResolver GPUSurfaceGLDelegate::GetGLProcResolver() const { return nullptr; diff --git a/shell/gpu/gpu_surface_gl_delegate.h b/shell/gpu/gpu_surface_gl_delegate.h index 0a6b33d22487d..44dd872a73ba5 100644 --- a/shell/gpu/gpu_surface_gl_delegate.h +++ b/shell/gpu/gpu_surface_gl_delegate.h @@ -44,7 +44,7 @@ class GPUSurfaceGLDelegate { // Get a reference to the external views embedder. This happens on the same // thread that the renderer is operating on. - virtual flutter::ExternalViewEmbedder* GetExternalViewEmbedder(); + virtual ExternalViewEmbedder* GetExternalViewEmbedder() = 0; sk_sp GetGLInterface() const; diff --git a/shell/gpu/gpu_surface_software.cc b/shell/gpu/gpu_surface_software.cc index 399a9a46c529c..443e7b704ad59 100644 --- a/shell/gpu/gpu_surface_software.cc +++ b/shell/gpu/gpu_surface_software.cc @@ -9,11 +9,6 @@ namespace flutter { -flutter::ExternalViewEmbedder* -GPUSurfaceSoftwareDelegate::GetExternalViewEmbedder() { - return nullptr; -} - GPUSurfaceSoftware::GPUSurfaceSoftware(GPUSurfaceSoftwareDelegate* delegate) : delegate_(delegate), weak_factory_(this) {} diff --git a/shell/gpu/gpu_surface_software.h b/shell/gpu/gpu_surface_software.h index ccf2ef11b5c13..84b38411eaf59 100644 --- a/shell/gpu/gpu_surface_software.h +++ b/shell/gpu/gpu_surface_software.h @@ -5,23 +5,13 @@ #ifndef FLUTTER_SHELL_GPU_GPU_SURFACE_SOFTWARE_H_ #define FLUTTER_SHELL_GPU_GPU_SURFACE_SOFTWARE_H_ -#include "flutter/flow/embedded_views.h" #include "flutter/fml/macros.h" #include "flutter/fml/memory/weak_ptr.h" #include "flutter/shell/common/surface.h" -#include "third_party/skia/include/core/SkSurface.h" +#include "flutter/shell/gpu/gpu_surface_software_delegate.h" namespace flutter { -class GPUSurfaceSoftwareDelegate { - public: - virtual sk_sp AcquireBackingStore(const SkISize& size) = 0; - - virtual bool PresentBackingStore(sk_sp backing_store) = 0; - - virtual flutter::ExternalViewEmbedder* GetExternalViewEmbedder(); -}; - class GPUSurfaceSoftware : public Surface { public: GPUSurfaceSoftware(GPUSurfaceSoftwareDelegate* delegate); diff --git a/shell/gpu/gpu_surface_software_delegate.cc b/shell/gpu/gpu_surface_software_delegate.cc new file mode 100644 index 0000000000000..1566fe92df103 --- /dev/null +++ b/shell/gpu/gpu_surface_software_delegate.cc @@ -0,0 +1,13 @@ +// 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 "gpu_surface_software_delegate.h" + +namespace flutter { + +ExternalViewEmbedder* GPUSurfaceSoftwareDelegate::GetExternalViewEmbedder() { + return nullptr; +} + +} // namespace flutter diff --git a/shell/gpu/gpu_surface_software_delegate.h b/shell/gpu/gpu_surface_software_delegate.h new file mode 100644 index 0000000000000..fa62d26ce4cd3 --- /dev/null +++ b/shell/gpu/gpu_surface_software_delegate.h @@ -0,0 +1,65 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_GPU_GPU_SURFACE_SOFTWARE_DELEGATE_H_ +#define FLUTTER_SHELL_GPU_GPU_SURFACE_SOFTWARE_DELEGATE_H_ + +#include "flutter/flow/embedded_views.h" +#include "flutter/fml/macros.h" +#include "third_party/skia/include/core/SkSurface.h" + +namespace flutter { + +//------------------------------------------------------------------------------ +/// @brief Interface implemented by all platform surfaces that can present +/// a software backing store to the "screen". The GPU surface +/// abstraction (which abstracts the client rendering API) uses this +/// delegation pattern to tell the platform surface (which abstracts +/// how backing stores fulfilled by the selected client rendering +/// API end up on the "screen" on a particular platform) when the +/// rasterizer needs to allocate and present the software backing +/// store. +/// +/// @see |IOSurfaceSoftware|, |AndroidSurfaceSoftware|, +/// |EmbedderSurfaceSoftware|. +/// +class GPUSurfaceSoftwareDelegate { + public: + //---------------------------------------------------------------------------- + /// @brief Called when the GPU surface needs a new buffer to render a new + /// frame into. + /// + /// @param[in] size The size of the frame. + /// + /// @return A raster surface returned by the platform. + /// + virtual sk_sp AcquireBackingStore(const SkISize& size) = 0; + + //---------------------------------------------------------------------------- + /// @brief Called by the platform when a frame has been rendered into the + /// backing store and the platform must display it on-screen. + /// + /// @param[in] backing_store The software backing store to present. + /// + /// @return Returns if the platform could present the backing store onto + /// the screen. + /// + virtual bool PresentBackingStore(sk_sp backing_store) = 0; + + //---------------------------------------------------------------------------- + /// @brief Gets the view embedder that controls how the Flutter layer + /// hierarchy split into multiple chunks should be composited back + /// on-screen. This field is optional and the Flutter rasterizer + /// will render into a single on-screen surface if this call + /// returns a null external view embedder. + /// + /// @return The external view embedder, or, null if Flutter is rendering + /// into a single on-screen surface. + /// + virtual ExternalViewEmbedder* GetExternalViewEmbedder() = 0; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_GPU_GPU_SURFACE_SOFTWARE_DELEGATE_H_ diff --git a/shell/platform/android/android_surface_gl.cc b/shell/platform/android/android_surface_gl.cc index ff0e45fcef73c..737d9f293a518 100644 --- a/shell/platform/android/android_surface_gl.cc +++ b/shell/platform/android/android_surface_gl.cc @@ -56,7 +56,7 @@ bool AndroidSurfaceGL::IsValid() const { } std::unique_ptr AndroidSurfaceGL::CreateGPUSurface() { - auto surface = std::make_unique(this); + auto surface = std::make_unique(this, true); return surface->IsValid() ? std::move(surface) : nullptr; } @@ -125,4 +125,9 @@ intptr_t AndroidSurfaceGL::GLContextFBO() const { return 0; } +// |GPUSurfaceGLDelegate| +ExternalViewEmbedder* AndroidSurfaceGL::GetExternalViewEmbedder() { + return nullptr; +} + } // namespace flutter diff --git a/shell/platform/android/android_surface_gl.h b/shell/platform/android/android_surface_gl.h index 7592ad5f8df3e..d59302ad66509 100644 --- a/shell/platform/android/android_surface_gl.h +++ b/shell/platform/android/android_surface_gl.h @@ -58,6 +58,9 @@ class AndroidSurfaceGL final : public GPUSurfaceGLDelegate, // |GPUSurfaceGLDelegate| intptr_t GLContextFBO() const override; + // |GPUSurfaceGLDelegate| + ExternalViewEmbedder* GetExternalViewEmbedder() override; + private: fml::RefPtr onscreen_context_; fml::RefPtr offscreen_context_; diff --git a/shell/platform/android/android_surface_software.cc b/shell/platform/android/android_surface_software.cc index 633b6091f0c15..5262fa5b9ad78 100644 --- a/shell/platform/android/android_surface_software.cc +++ b/shell/platform/android/android_surface_software.cc @@ -134,6 +134,11 @@ bool AndroidSurfaceSoftware::PresentBackingStore( return true; } +// |GPUSurfaceSoftwareDelegate| +ExternalViewEmbedder* AndroidSurfaceSoftware::GetExternalViewEmbedder() { + return nullptr; +} + void AndroidSurfaceSoftware::TeardownOnScreenContext() {} bool AndroidSurfaceSoftware::OnScreenSurfaceResize(const SkISize& size) const { diff --git a/shell/platform/android/android_surface_software.h b/shell/platform/android/android_surface_software.h index cf7fcb3e20f8e..30888091fe0d5 100644 --- a/shell/platform/android/android_surface_software.h +++ b/shell/platform/android/android_surface_software.h @@ -47,6 +47,9 @@ class AndroidSurfaceSoftware final : public AndroidSurface, // |GPUSurfaceSoftwareDelegate| bool PresentBackingStore(sk_sp backing_store) override; + // |GPUSurfaceSoftwareDelegate| + ExternalViewEmbedder* GetExternalViewEmbedder() override; + private: sk_sp sk_surface_; fml::RefPtr native_window_; diff --git a/shell/platform/darwin/ios/ios_surface_gl.h b/shell/platform/darwin/ios/ios_surface_gl.h index cb19c393b3d61..b9a0238dd8856 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.h +++ b/shell/platform/darwin/ios/ios_surface_gl.h @@ -18,7 +18,7 @@ namespace flutter { class IOSSurfaceGL final : public IOSSurface, public GPUSurfaceGLDelegate, - public flutter::ExternalViewEmbedder { + public ExternalViewEmbedder { public: IOSSurfaceGL(std::shared_ptr context, fml::scoped_nsobject layer, @@ -49,28 +49,31 @@ class IOSSurfaceGL final : public IOSSurface, bool UseOffscreenSurface() const override; // |GPUSurfaceGLDelegate| - flutter::ExternalViewEmbedder* GetExternalViewEmbedder() override; + ExternalViewEmbedder* GetExternalViewEmbedder() override; - // |flutter::ExternalViewEmbedder| + // |ExternalViewEmbedder| + sk_sp GetRootSurface() override; + + // |ExternalViewEmbedder| void CancelFrame() override; - // |flutter::ExternalViewEmbedder| - void BeginFrame(SkISize frame_size) override; + // |ExternalViewEmbedder| + void BeginFrame(SkISize frame_size, GrContext* context) override; - // |flutter::ExternalViewEmbedder| + // |ExternalViewEmbedder| void PrerollCompositeEmbeddedView(int view_id, std::unique_ptr params) override; - // |flutter::ExternalViewEmbedder| + // |ExternalViewEmbedder| PostPrerollResult PostPrerollAction(fml::RefPtr gpu_thread_merger) override; - // |flutter::ExternalViewEmbedder| + // |ExternalViewEmbedder| std::vector GetCurrentCanvases() override; - // |flutter::ExternalViewEmbedder| + // |ExternalViewEmbedder| SkCanvas* CompositeEmbeddedView(int view_id) override; - // |flutter::ExternalViewEmbedder| + // |ExternalViewEmbedder| bool SubmitFrame(GrContext* context) override; private: diff --git a/shell/platform/darwin/ios/ios_surface_gl.mm b/shell/platform/darwin/ios/ios_surface_gl.mm index 9b945c80300c8..f087be2719ae2 100644 --- a/shell/platform/darwin/ios/ios_surface_gl.mm +++ b/shell/platform/darwin/ios/ios_surface_gl.mm @@ -39,11 +39,11 @@ } std::unique_ptr IOSSurfaceGL::CreateGPUSurface() { - return std::make_unique(this); + return std::make_unique(this, true); } std::unique_ptr IOSSurfaceGL::CreateSecondaryGPUSurface(GrContext* gr_context) { - return std::make_unique(sk_ref_sp(gr_context), this); + return std::make_unique(sk_ref_sp(gr_context), this, true); } intptr_t IOSSurfaceGL::GLContextFBO() const { @@ -74,6 +74,14 @@ return IsValid() && render_target_->PresentRenderBuffer(); } +// |ExternalViewEmbedder| +sk_sp IOSSurfaceGL::GetRootSurface() { + // On iOS, the root surface is created from the on-screen render target. Only the surfaces for the + // various overlays are controlled by this class. + return nullptr; +} + +// |ExternalViewEmbedder| flutter::ExternalViewEmbedder* IOSSurfaceGL::GetExternalViewEmbedder() { if (IsIosEmbeddedViewsPreviewEnabled()) { return this; @@ -82,6 +90,7 @@ } } +// |ExternalViewEmbedder| void IOSSurfaceGL::CancelFrame() { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); FML_CHECK(platform_views_controller != nullptr); @@ -91,13 +100,15 @@ [CATransaction commit]; } -void IOSSurfaceGL::BeginFrame(SkISize frame_size) { +// |ExternalViewEmbedder| +void IOSSurfaceGL::BeginFrame(SkISize frame_size, GrContext* context) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); FML_CHECK(platform_views_controller != nullptr); platform_views_controller->SetFrameSize(frame_size); [CATransaction begin]; } +// |ExternalViewEmbedder| void IOSSurfaceGL::PrerollCompositeEmbeddedView( int view_id, std::unique_ptr params) { @@ -106,6 +117,7 @@ platform_views_controller->PrerollCompositeEmbeddedView(view_id, std::move(params)); } +// |ExternalViewEmbedder| PostPrerollResult IOSSurfaceGL::PostPrerollAction( fml::RefPtr gpu_thread_merger) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); @@ -113,18 +125,21 @@ return platform_views_controller->PostPrerollAction(gpu_thread_merger); } +// |ExternalViewEmbedder| std::vector IOSSurfaceGL::GetCurrentCanvases() { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); FML_CHECK(platform_views_controller != nullptr); return platform_views_controller->GetCurrentCanvases(); } +// |ExternalViewEmbedder| SkCanvas* IOSSurfaceGL::CompositeEmbeddedView(int view_id) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); FML_CHECK(platform_views_controller != nullptr); return platform_views_controller->CompositeEmbeddedView(view_id); } +// |ExternalViewEmbedder| bool IOSSurfaceGL::SubmitFrame(GrContext* context) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); if (platform_views_controller == nullptr) { diff --git a/shell/platform/darwin/ios/ios_surface_software.h b/shell/platform/darwin/ios/ios_surface_software.h index 38545647c58d2..2b96e9bd3c930 100644 --- a/shell/platform/darwin/ios/ios_surface_software.h +++ b/shell/platform/darwin/ios/ios_surface_software.h @@ -17,7 +17,7 @@ namespace flutter { class IOSSurfaceSoftware final : public IOSSurface, public GPUSurfaceSoftwareDelegate, - public flutter::ExternalViewEmbedder { + public ExternalViewEmbedder { public: IOSSurfaceSoftware(fml::scoped_nsobject layer, FlutterPlatformViewsController* platform_views_controller); @@ -43,25 +43,28 @@ class IOSSurfaceSoftware final : public IOSSurface, bool PresentBackingStore(sk_sp backing_store) override; // |GPUSurfaceSoftwareDelegate| - flutter::ExternalViewEmbedder* GetExternalViewEmbedder() override; + ExternalViewEmbedder* GetExternalViewEmbedder() override; - // |flutter::ExternalViewEmbedder| + // |ExternalViewEmbedder| + sk_sp GetRootSurface() override; + + // |ExternalViewEmbedder| void CancelFrame() override; - // |flutter::ExternalViewEmbedder| - void BeginFrame(SkISize frame_size) override; + // |ExternalViewEmbedder| + void BeginFrame(SkISize frame_size, GrContext* context) override; - // |flutter::ExternalViewEmbedder| + // |ExternalViewEmbedder| void PrerollCompositeEmbeddedView(int view_id, std::unique_ptr params) override; - // |flutter::ExternalViewEmbedder| + // |ExternalViewEmbedder| std::vector GetCurrentCanvases() override; - // |flutter::ExternalViewEmbedder| + // |ExternalViewEmbedder| SkCanvas* CompositeEmbeddedView(int view_id) override; - // |flutter::ExternalViewEmbedder| + // |ExternalViewEmbedder| bool SubmitFrame(GrContext* context) override; private: diff --git a/shell/platform/darwin/ios/ios_surface_software.mm b/shell/platform/darwin/ios/ios_surface_software.mm index a55be763718c1..4470fc25dfdd5 100644 --- a/shell/platform/darwin/ios/ios_surface_software.mm +++ b/shell/platform/darwin/ios/ios_surface_software.mm @@ -127,7 +127,7 @@ return true; } -flutter::ExternalViewEmbedder* IOSSurfaceSoftware::GetExternalViewEmbedder() { +ExternalViewEmbedder* IOSSurfaceSoftware::GetExternalViewEmbedder() { if (IsIosEmbeddedViewsPreviewEnabled()) { return this; } else { @@ -135,38 +135,50 @@ } } +// |ExternalViewEmbedder| +sk_sp IOSSurfaceSoftware::GetRootSurface() { + // On iOS, the root surface is created using a managed allocation that is submitted to the + // platform. Only the surfaces for the various overlays are controlled by this class. + return nullptr; +} + +// |ExternalViewEmbedder| void IOSSurfaceSoftware::CancelFrame() { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); FML_CHECK(platform_views_controller != nullptr); platform_views_controller->CancelFrame(); } -void IOSSurfaceSoftware::BeginFrame(SkISize frame_size) { +// |ExternalViewEmbedder| +void IOSSurfaceSoftware::BeginFrame(SkISize frame_size, GrContext* context) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); FML_CHECK(platform_views_controller != nullptr); platform_views_controller->SetFrameSize(frame_size); } -void IOSSurfaceSoftware::PrerollCompositeEmbeddedView( - int view_id, - std::unique_ptr params) { +// |ExternalViewEmbedder| +void IOSSurfaceSoftware::PrerollCompositeEmbeddedView(int view_id, + std::unique_ptr params) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); FML_CHECK(platform_views_controller != nullptr); platform_views_controller->PrerollCompositeEmbeddedView(view_id, std::move(params)); } +// |ExternalViewEmbedder| std::vector IOSSurfaceSoftware::GetCurrentCanvases() { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); FML_CHECK(platform_views_controller != nullptr); return platform_views_controller->GetCurrentCanvases(); } +// |ExternalViewEmbedder| SkCanvas* IOSSurfaceSoftware::CompositeEmbeddedView(int view_id) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); FML_CHECK(platform_views_controller != nullptr); return platform_views_controller->CompositeEmbeddedView(view_id); } +// |ExternalViewEmbedder| bool IOSSurfaceSoftware::SubmitFrame(GrContext* context) { FlutterPlatformViewsController* platform_views_controller = GetPlatformViewsController(); if (platform_views_controller == nullptr) { diff --git a/shell/platform/embedder/BUILD.gn b/shell/platform/embedder/BUILD.gn index 31499efd82f88..1efe84661bd95 100644 --- a/shell/platform/embedder/BUILD.gn +++ b/shell/platform/embedder/BUILD.gn @@ -27,9 +27,13 @@ template("embedder_source_set") { "embedder_engine.h", "embedder_external_texture_gl.cc", "embedder_external_texture_gl.h", + "embedder_external_view_embedder.cc", + "embedder_external_view_embedder.h", "embedder_include.c", "embedder_platform_message_response.cc", "embedder_platform_message_response.h", + "embedder_render_target.cc", + "embedder_render_target.h", "embedder_safe_access.h", "embedder_surface.cc", "embedder_surface.h", @@ -79,6 +83,7 @@ config("embedder_prefix_config") { test_fixtures("fixtures") { dart_main = "fixtures/main.dart" + fixtures = [ "fixtures/compositor.png" ] } if (current_toolchain == host_toolchain) { @@ -93,10 +98,12 @@ if (current_toolchain == host_toolchain) { "tests/embedder_a11y_unittests.cc", "tests/embedder_config_builder.cc", "tests/embedder_config_builder.h", - "tests/embedder_context.cc", - "tests/embedder_context.h", "tests/embedder_test.cc", "tests/embedder_test.h", + "tests/embedder_test_compositor.cc", + "tests/embedder_test_compositor.h", + "tests/embedder_test_context.cc", + "tests/embedder_test_context.h", "tests/embedder_unittests.cc", ] diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 4275aefaba388..bc412d903bdc6 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -5,6 +5,7 @@ #define FML_USED_ON_EMBEDDER #include "flutter/fml/build_config.h" +#include "flutter/fml/make_copyable.h" #include "flutter/fml/native_library.h" #if OS_WIN @@ -35,6 +36,7 @@ extern const intptr_t kPlatformStrongDillSize; #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/embedder/embedder_engine.h" #include "flutter/shell/platform/embedder/embedder_platform_message_response.h" +#include "flutter/shell/platform/embedder/embedder_render_target.h" #include "flutter/shell/platform/embedder/embedder_safe_access.h" #include "flutter/shell/platform/embedder/embedder_task_runner.h" #include "flutter/shell/platform/embedder/embedder_thread_host.h" @@ -124,7 +126,9 @@ InferOpenGLPlatformViewCreationCallback( const FlutterRendererConfig* config, void* user_data, flutter::PlatformViewEmbedder::PlatformDispatchTable - platform_dispatch_table) { + platform_dispatch_table, + std::unique_ptr + external_view_embedder) { if (config->type != kOpenGL) { return nullptr; } @@ -194,16 +198,19 @@ InferOpenGLPlatformViewCreationCallback( gl_proc_resolver, // gl_proc_resolver }; - return [gl_dispatch_table, fbo_reset_after_present, - platform_dispatch_table](flutter::Shell& shell) { - return std::make_unique( - shell, // delegate - shell.GetTaskRunners(), // task runners - gl_dispatch_table, // embedder GL dispatch table - fbo_reset_after_present, // fbo reset after present - platform_dispatch_table // embedder platform dispatch table - ); - }; + return fml::MakeCopyable( + [gl_dispatch_table, fbo_reset_after_present, platform_dispatch_table, + external_view_embedder = + std::move(external_view_embedder)](flutter::Shell& shell) mutable { + return std::make_unique( + shell, // delegate + shell.GetTaskRunners(), // task runners + gl_dispatch_table, // embedder GL dispatch table + fbo_reset_after_present, // fbo reset after present + platform_dispatch_table, // embedder platform dispatch table + std::move(external_view_embedder) // external view embedder + ); + }); } static flutter::Shell::CreateCallback @@ -211,7 +218,9 @@ InferSoftwarePlatformViewCreationCallback( const FlutterRendererConfig* config, void* user_data, flutter::PlatformViewEmbedder::PlatformDispatchTable - platform_dispatch_table) { + platform_dispatch_table, + std::unique_ptr + external_view_embedder) { if (config->type != kSoftware) { return nullptr; } @@ -227,15 +236,18 @@ InferSoftwarePlatformViewCreationCallback( software_present_backing_store, // required }; - return [software_dispatch_table, - platform_dispatch_table](flutter::Shell& shell) { - return std::make_unique( - shell, // delegate - shell.GetTaskRunners(), // task runners - software_dispatch_table, // software dispatch table - platform_dispatch_table // platform dispatch table - ); - }; + return fml::MakeCopyable( + [software_dispatch_table, platform_dispatch_table, + external_view_embedder = + std::move(external_view_embedder)](flutter::Shell& shell) mutable { + return std::make_unique( + shell, // delegate + shell.GetTaskRunners(), // task runners + software_dispatch_table, // software dispatch table + platform_dispatch_table, // platform dispatch table + std::move(external_view_embedder) // external view embedder + ); + }); } static flutter::Shell::CreateCallback @@ -243,24 +255,250 @@ InferPlatformViewCreationCallback( const FlutterRendererConfig* config, void* user_data, flutter::PlatformViewEmbedder::PlatformDispatchTable - platform_dispatch_table) { + platform_dispatch_table, + std::unique_ptr + external_view_embedder) { if (config == nullptr) { return nullptr; } switch (config->type) { case kOpenGL: - return InferOpenGLPlatformViewCreationCallback(config, user_data, - platform_dispatch_table); + return InferOpenGLPlatformViewCreationCallback( + config, user_data, platform_dispatch_table, + std::move(external_view_embedder)); case kSoftware: - return InferSoftwarePlatformViewCreationCallback(config, user_data, - platform_dispatch_table); + return InferSoftwarePlatformViewCreationCallback( + config, user_data, platform_dispatch_table, + std::move(external_view_embedder)); default: return nullptr; } return nullptr; } +static sk_sp MakeSkSurfaceFromBackingStore( + GrContext* context, + const FlutterBackingStoreConfig& config, + const FlutterOpenGLTexture* texture) { + GrGLTextureInfo texture_info; + texture_info.fTarget = texture->target; + texture_info.fID = texture->name; + texture_info.fFormat = texture->format; + + GrBackendTexture backend_texture(config.size.width, // + config.size.height, // + GrMipMapped::kNo, // + texture_info // + ); + + SkSurfaceProps surface_properties( + SkSurfaceProps::InitType::kLegacyFontHost_InitType); + + auto surface = SkSurface::MakeFromBackendTexture( + context, // context + backend_texture, // back-end texture + kTopLeft_GrSurfaceOrigin, // surface origin + 1, // sample count + kN32_SkColorType, // color type + SkColorSpace::MakeSRGB(), // color space + &surface_properties, // surface properties + static_cast( + texture->destruction_callback), // release proc + texture->user_data // release context + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not wrap embedder supplied render texture."; + texture->destruction_callback(texture->user_data); + return nullptr; + } + + return surface; +} + +static sk_sp MakeSkSurfaceFromBackingStore( + GrContext* context, + const FlutterBackingStoreConfig& config, + const FlutterOpenGLFramebuffer* framebuffer) { + GrGLFramebufferInfo framebuffer_info = {}; + framebuffer_info.fFormat = framebuffer->target; + framebuffer_info.fFBOID = framebuffer->name; + + GrBackendRenderTarget backend_render_target( + config.size.width, // width + config.size.height, // height + 1, // sample count + 0, // stencil bits + framebuffer_info // framebuffer info + ); + + SkSurfaceProps surface_properties( + SkSurfaceProps::InitType::kLegacyFontHost_InitType); + + auto surface = SkSurface::MakeFromBackendRenderTarget( + context, // context + backend_render_target, // backend render target + kTopLeft_GrSurfaceOrigin, // surface origin + kN32_SkColorType, // color type + SkColorSpace::MakeSRGB(), // color space + &surface_properties, // surface properties + static_cast( + framebuffer->destruction_callback), // release proc + framebuffer->user_data // release context + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not wrap embedder supplied frame-buffer."; + framebuffer->destruction_callback(framebuffer->user_data); + return nullptr; + } + return surface; +} + +static sk_sp MakeSkSurfaceFromBackingStore( + GrContext* context, + const FlutterBackingStoreConfig& config, + const FlutterSoftwareBackingStore* software) { + const auto image_info = + SkImageInfo::MakeN32Premul(config.size.width, config.size.height); + + struct Captures { + VoidCallback destruction_callback; + void* user_data; + }; + auto captures = std::make_unique(); + captures->destruction_callback = software->destruction_callback; + captures->user_data = software->user_data; + auto release_proc = [](void* pixels, void* context) { + auto captures = reinterpret_cast(context); + captures->destruction_callback(captures->user_data); + }; + + auto surface = SkSurface::MakeRasterDirectReleaseProc( + image_info, // image info + const_cast(software->allocation), // pixels + software->row_bytes, // row bytes + release_proc, // release proc + captures.release() // release context + ); + + if (!surface) { + FML_LOG(ERROR) + << "Could not wrap embedder supplied software render buffer."; + software->destruction_callback(software->user_data); + return nullptr; + } + return surface; +} + +static std::unique_ptr +CreateEmbedderRenderTarget(const FlutterCompositor* compositor, + const FlutterBackingStoreConfig& config, + GrContext* context) { + FlutterBackingStore backing_store = {}; + backing_store.struct_size = sizeof(backing_store); + + // Safe access checks on the compositor struct have been performed in + // InferExternalViewEmbedderFromArgs and are not necessary here. + auto c_create_callback = compositor->create_backing_store_callback; + auto c_collect_callback = compositor->collect_backing_store_callback; + + if (!c_create_callback(&config, &backing_store, compositor->user_data)) { + FML_LOG(ERROR) << "Could not create the embedder backing store."; + return nullptr; + } + + if (backing_store.struct_size != sizeof(backing_store)) { + FML_LOG(ERROR) << "Embedder modified the backing store struct size."; + return nullptr; + } + + // In case we return early without creating an embedder render target, the + // embedder has still given us ownership of its baton which we must return + // back to it. If this method is successful, the closure is released when the + // render target is eventually released. + fml::ScopedCleanupClosure collect_callback( + [c_collect_callback, backing_store, user_data = compositor->user_data]() { + c_collect_callback(&backing_store, user_data); + }); + + // No safe access checks on the renderer are necessary since we allocated + // the struct. + + sk_sp render_surface; + + switch (backing_store.type) { + case kFlutterBackingStoreTypeOpenGL: + switch (backing_store.open_gl.type) { + case kFlutterOpenGLTargetTypeTexture: + render_surface = MakeSkSurfaceFromBackingStore( + context, config, &backing_store.open_gl.texture); + break; + case kFlutterOpenGLTargetTypeFramebuffer: + render_surface = MakeSkSurfaceFromBackingStore( + context, config, &backing_store.open_gl.framebuffer); + break; + } + break; + case kFlutterBackingStoreTypeSoftware: + render_surface = MakeSkSurfaceFromBackingStore(context, config, + &backing_store.software); + break; + }; + + if (!render_surface) { + FML_LOG(ERROR) << "Could not create a surface from an embedder provided " + "render target."; + return nullptr; + } + + return std::make_unique( + backing_store, std::move(render_surface), collect_callback.Release()); +} + +static std::pair, + bool /* halt engine launch if true */> +InferExternalViewEmbedderFromArgs(const FlutterCompositor* compositor) { + if (compositor == nullptr) { + return {nullptr, false}; + } + + auto c_create_callback = + SAFE_ACCESS(compositor, create_backing_store_callback, nullptr); + auto c_collect_callback = + SAFE_ACCESS(compositor, collect_backing_store_callback, nullptr); + auto c_present_callback = + SAFE_ACCESS(compositor, present_layers_callback, nullptr); + + // Make sure the required callbacks are present + if (!c_create_callback || !c_collect_callback || !c_present_callback) { + FML_LOG(ERROR) << "Required compositor callbacks absent."; + return {nullptr, true}; + } + + FlutterCompositor captured_compositor = *compositor; + + flutter::EmbedderExternalViewEmbedder::CreateRenderTargetCallback + create_render_target_callback = + [captured_compositor](GrContext* context, const auto& config) { + return CreateEmbedderRenderTarget(&captured_compositor, config, + context); + }; + + flutter::EmbedderExternalViewEmbedder::PresentCallback present_callback = + [c_present_callback, + user_data = compositor->user_data](const auto& layers) { + return c_present_callback( + const_cast(layers.data()), layers.size(), + user_data); + }; + + return {std::make_unique( + create_render_target_callback, present_callback), + false}; +} + struct _FlutterPlatformMessageResponseHandle { fml::RefPtr message; }; @@ -512,6 +750,12 @@ FlutterEngineResult FlutterEngineRun(size_t version, }; } + auto external_view_embedder_result = + InferExternalViewEmbedderFromArgs(SAFE_ACCESS(args, compositor, nullptr)); + if (external_view_embedder_result.second) { + return LOG_EMBEDDER_ERROR(kInvalidArguments); + } + flutter::PlatformViewEmbedder::PlatformDispatchTable platform_dispatch_table = { update_semantics_nodes_callback, // @@ -521,7 +765,8 @@ FlutterEngineResult FlutterEngineRun(size_t version, }; auto on_create_platform_view = InferPlatformViewCreationCallback( - config, user_data, platform_dispatch_table); + config, user_data, platform_dispatch_table, + std::move(external_view_embedder_result.first)); if (!on_create_platform_view) { return LOG_EMBEDDER_ERROR(kInvalidArguments); diff --git a/shell/platform/embedder/embedder.h b/shell/platform/embedder/embedder.h index 2545b354dc536..934d619ae3e13 100644 --- a/shell/platform/embedder/embedder.h +++ b/shell/platform/embedder/embedder.h @@ -181,42 +181,68 @@ typedef enum { typedef struct _FlutterEngine* FLUTTER_API_SYMBOL(FlutterEngine); typedef struct { - // horizontal scale factor + // horizontal scale factor double scaleX; - // horizontal skew factor + // horizontal skew factor double skewX; - // horizontal translation + // horizontal translation double transX; - // vertical skew factor + // vertical skew factor double skewY; - // vertical scale factor + // vertical scale factor double scaleY; - // vertical translation + // vertical translation double transY; - // input x-axis perspective factor + // input x-axis perspective factor double pers0; - // input y-axis perspective factor + // input y-axis perspective factor double pers1; - // perspective scale factor + // perspective scale factor double pers2; } FlutterTransformation; typedef void (*VoidCallback)(void* /* user data */); +typedef enum { + // Specifies an OpenGL texture target type. Textures are specified using + // the FlutterOpenGLTexture struct. + kFlutterOpenGLTargetTypeTexture, + // Specifies an OpenGL frame-buffer target type. Framebuffers are specified + // using the FlutterOpenGLFramebuffer struct. + kFlutterOpenGLTargetTypeFramebuffer, +} FlutterOpenGLTargetType; + typedef struct { - // Target texture of the active texture unit (example GL_TEXTURE_2D). + // Target texture of the active texture unit (example GL_TEXTURE_2D). uint32_t target; - // The name of the texture. + // The name of the texture. uint32_t name; - // The texture format (example GL_RGBA8). + // The texture format (example GL_RGBA8). uint32_t format; - // User data to be returned on the invocation of the destruction callback. + // User data to be returned on the invocation of the destruction callback. void* user_data; - // Callback invoked (on an engine managed thread) that asks the embedder to - // collect the texture. + // Callback invoked (on an engine managed thread) that asks the embedder to + // collect the texture. VoidCallback destruction_callback; } FlutterOpenGLTexture; +typedef struct { + // The target of the color attachment of the frame-buffer. For example, + // GL_TEXTURE_2D or GL_RENDERBUFFER. In case of ambiguity when dealing with + // Window bound frame-buffers, 0 may be used. + uint32_t target; + + // The name of the framebuffer. + uint32_t name; + + // User data to be returned on the invocation of the destruction callback. + void* user_data; + + // Callback invoked (on an engine managed thread) that asks the embedder to + // collect the framebuffer. + VoidCallback destruction_callback; +} FlutterOpenGLFramebuffer; + typedef bool (*BoolCallback)(void* /* user data */); typedef FlutterTransformation (*TransformationCallback)(void* /* user data */); typedef uint32_t (*UIntCallback)(void* /* user data */); @@ -359,7 +385,7 @@ typedef struct { double x; double y; // An optional device identifier. If this is not specified, it is assumed that - // the embedder has no multitouch capability. + // the embedder has no multi-touch capability. int32_t device; FlutterPointerSignalKind signal_kind; double scroll_delta_x; @@ -562,6 +588,160 @@ typedef struct { const FlutterTaskRunnerDescription* platform_task_runner; } FlutterCustomTaskRunners; +typedef struct { + // The type of the OpenGL backing store. Currently, it can either be a texture + // or a framebuffer. + FlutterOpenGLTargetType type; + union { + // A texture for Flutter to render into. + FlutterOpenGLTexture texture; + // A framebuffer for Flutter to render into. The embedder must ensure that + // the framebuffer is complete. + FlutterOpenGLFramebuffer framebuffer; + }; +} FlutterOpenGLBackingStore; + +typedef struct { + // A pointer to the raw bytes of the allocation described by this software + // backing store. + const void* allocation; + // The number of bytes in a single row of the allocation. + size_t row_bytes; + // The number of rows in the allocation. + size_t height; + // A baton that is not interpreted by the engine in any way. It will be given + // back to the embedder in the destruction callback below. Embedder resources + // may be associated with this baton. + void* user_data; + // The callback invoked by the engine when it no longer needs this backing + // store. + VoidCallback destruction_callback; +} FlutterSoftwareBackingStore; + +// The identifier of the platform view. This identifier is specified by the +// application when a platform view is added to the scene via the +// `SceneBuilder.addPlatformView` call. +typedef int64_t FlutterPlatformViewIdentifier; + +typedef struct { + // The size of this struct. Must be sizeof(FlutterPlatformView). + size_t struct_size; + // The identifier of this platform view. This identifier is specified by the + // application when a platform view is added to the scene via the + // `SceneBuilder.addPlatformView` call. + FlutterPlatformViewIdentifier identifier; +} FlutterPlatformView; + +typedef enum { + // Specifies an OpenGL backing store. Can either be an OpenGL texture or + // framebuffer. + kFlutterBackingStoreTypeOpenGL, + // Specified an software allocation for Flutter to render into using the CPU. + kFlutterBackingStoreTypeSoftware, +} FlutterBackingStoreType; + +typedef struct { + // The size of this struct. Must be sizeof(FlutterBackingStore). + size_t struct_size; + // A baton that is not interpreted by the engine in any way. The embedder may + // use this to associate resources that are tied to the lifecycle of the + // |FlutterBackingStore|. + void* user_data; + // Specifies the type of backing store. + FlutterBackingStoreType type; + // Indicates if this backing store was updated since the last time it was + // associated with a presented layer. + bool did_update; + union { + // The description of the OpenGL backing store. + FlutterOpenGLBackingStore open_gl; + // The description of the software backing store. + FlutterSoftwareBackingStore software; + }; +} FlutterBackingStore; + +typedef struct { + double x; + double y; +} FlutterPoint; + +typedef struct { + double width; + double height; +} FlutterSize; + +typedef struct { + // The size of this struct. Must be sizeof(FlutterBackingStoreConfig). + size_t struct_size; + // The size of the render target the engine expects to render into. + FlutterSize size; +} FlutterBackingStoreConfig; + +typedef enum { + // Indicates that the contents of this layer are rendered by Flutter into a + // backing store. + kFlutterLayerContentTypeBackingStore, + // Indicates that the contents of this layer are determined by the embedder. + kFlutterLayerContentTypePlatformView, +} FlutterLayerContentType; + +typedef struct { + // This size of this struct. Must be sizeof(FlutterLayer). + size_t struct_size; + // Each layer displays contents in one way or another. The type indicates + // whether those contents are specified by Flutter or the embedder. + FlutterLayerContentType type; + union { + // Indicates that the contents of this layer are rendered by Flutter into a + // backing store. + const FlutterBackingStore* backing_store; + // Indicates that the contents of this layer are determined by the embedder. + const FlutterPlatformView* platform_view; + }; + // The offset of this layer (in physical pixels) relative to the top left of + // the root surface used by the engine. + FlutterPoint offset; + // The size of the layer (in physical pixels). + FlutterSize size; +} FlutterLayer; + +typedef bool (*FlutterBackingStoreCreateCallback)( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out, + void* user_data); + +typedef bool (*FlutterBackingStoreCollectCallback)( + const FlutterBackingStore* renderer, + void* user_data); + +typedef bool (*FlutterLayersPresentCallback)(const FlutterLayer** layers, + size_t layers_count, + void* user_data); + +typedef struct { + // This size of this struct. Must be sizeof(FlutterCompositor). + size_t struct_size; + // A baton that in not interpreted by the engine in any way. If it passed back + // to the embedder in |FlutterCompositor.create_backing_store_callback|, + // |FlutterCompositor.collect_backing_store_callback| and + // |FlutterCompositor.present_layers_callback| + void* user_data; + // A callback invoked by the engine to obtain a backing store for a specific + // |FlutterLayer|. + // + // On ABI stability: Callers must take care to restrict access within + // |FlutterBackingStore::struct_size| when specifying a new backing store to + // the engine. This only matters if the embedder expects to be used with + // engines older than the version whose headers it used during compilation. + FlutterBackingStoreCreateCallback create_backing_store_callback; + // A callback invoked by the engine to release the backing store. The embedder + // may collect any resources associated with the backing store. + FlutterBackingStoreCollectCallback collect_backing_store_callback; + // Callback invoked by the engine to composite the contents of each layer onto + // the screen. + FlutterLayersPresentCallback present_layers_callback; +} FlutterCompositor; + typedef struct { // The size of this struct. Must be sizeof(FlutterProjectArgs). size_t struct_size; @@ -718,6 +898,22 @@ typedef struct { // Dart VM when the last engine is terminated in the process should opt into // this behavior by setting this flag to true. bool shutdown_dart_vm_when_done; + + // Typically, Flutter renders the layer hierarchy into a single root surface. + // However, when embedders need to interleave their own contents within the + // Flutter layer hierarchy, their applications can push platform views within + // the Flutter scene. This is done using the `SceneBuilder.addPlatformView` + // call. When this happens, the Flutter rasterizer divides the effective view + // hierarchy into multiple layers. Each layer gets its own backing store and + // Flutter renders into the same. Once the layers contents have been + // fulfilled, the embedder is asked to composite these layers on-screen. At + // this point, it can interleave its own contents within the effective + // hierarchy. The interface for the specification of these layer backing + // stores and the hooks to listen for the composition of layers on-screen can + // be controlled using this field. This field is completely optional. In its + // absence, platforms views in the scene are ignored and Flutter renders to + // the root surface as normal. + const FlutterCompositor* compositor; } FlutterProjectArgs; FLUTTER_EXPORT diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc new file mode 100644 index 0000000000000..d1f38171dc8e9 --- /dev/null +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -0,0 +1,267 @@ +// 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_external_view_embedder.h" + +#include + +#include "flutter/shell/platform/embedder/embedder_render_target.h" + +namespace flutter { + +EmbedderExternalViewEmbedder::EmbedderExternalViewEmbedder( + CreateRenderTargetCallback create_render_target_callback, + PresentCallback present_callback) + : create_render_target_callback_(create_render_target_callback), + present_callback_(present_callback) { + FML_DCHECK(create_render_target_callback_); + FML_DCHECK(present_callback_); +} + +EmbedderExternalViewEmbedder::~EmbedderExternalViewEmbedder() = default; + +void EmbedderExternalViewEmbedder::Reset() { + pending_recorders_.clear(); + pending_params_.clear(); + composition_order_.clear(); +} + +// |ExternalViewEmbedder| +void EmbedderExternalViewEmbedder::CancelFrame() { + Reset(); +} + +static FlutterBackingStoreConfig MakeBackingStoreConfig(const SkISize& size) { + FlutterBackingStoreConfig config = {}; + + config.struct_size = sizeof(config); + + config.size.width = size.width(); + config.size.height = size.height(); + + return config; +} + +// |ExternalViewEmbedder| +void EmbedderExternalViewEmbedder::BeginFrame(SkISize frame_size, + GrContext* context) { + Reset(); + pending_frame_size_ = frame_size; + + // Decide if we want to discard the previous root render target. + if (root_render_target_) { + auto surface = root_render_target_->GetRenderSurface(); + // This is unlikely to happen but the embedder could have given the + // rasterizer a render target the previous frame that Skia could not + // materialize into a renderable surface. Discard the target and try again. + if (!surface) { + root_render_target_ = nullptr; + } else { + auto last_surface_size = + SkISize::Make(surface->width(), surface->height()); + if (pending_frame_size_ != last_surface_size) { + root_render_target_ = nullptr; + } + } + } + + // If there is no root render target, create one now. This will be accessed by + // the rasterizer before the submit call layer to access the surface surface + // canvas. + if (!root_render_target_) { + root_render_target_ = create_render_target_callback_( + context, MakeBackingStoreConfig(pending_frame_size_)); + } +} + +// |ExternalViewEmbedder| +void EmbedderExternalViewEmbedder::PrerollCompositeEmbeddedView( + int view_id, + std::unique_ptr params) { + FML_DCHECK(pending_recorders_.count(view_id) == 0); + FML_DCHECK(pending_params_.count(view_id) == 0); + FML_DCHECK(std::find(composition_order_.begin(), composition_order_.end(), + view_id) == composition_order_.end()); + + pending_recorders_[view_id] = std::make_unique(); + pending_params_[view_id] = *params; + composition_order_.push_back(view_id); +} + +// |ExternalViewEmbedder| +std::vector EmbedderExternalViewEmbedder::GetCurrentCanvases() { + std::vector canvases; + for (const auto& recorder : pending_recorders_) { + canvases.push_back(recorder.second->beginRecording( + pending_frame_size_.width(), pending_frame_size_.height())); + } + return canvases; +} + +// |ExternalViewEmbedder| +SkCanvas* EmbedderExternalViewEmbedder::CompositeEmbeddedView(int view_id) { + auto found = pending_recorders_.find(view_id); + if (found == pending_recorders_.end()) { + FML_DCHECK(false) << "Attempted to composite a view that was not " + "pre-rolled."; + return nullptr; + } + return found->second->getRecordingCanvas(); +} + +static FlutterLayer MakeLayer(const SkISize& frame_size, + const FlutterBackingStore* store) { + FlutterLayer layer = {}; + + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = store; + + layer.offset.x = 0.0; + layer.offset.y = 0.0; + + layer.size.width = frame_size.width(); + layer.size.height = frame_size.height(); + + return layer; +} + +static FlutterPlatformView MakePlatformView( + FlutterPlatformViewIdentifier identifier) { + FlutterPlatformView view = {}; + + view.struct_size = sizeof(view); + + view.identifier = identifier; + + return view; +} + +static FlutterLayer MakeLayer(const EmbeddedViewParams& params, + const FlutterPlatformView& platform_view) { + FlutterLayer layer = {}; + + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypePlatformView; + layer.platform_view = &platform_view; + + layer.offset.x = params.offsetPixels.x(); + layer.offset.y = params.offsetPixels.y(); + + layer.size.width = params.sizePoints.width(); + layer.size.height = params.sizePoints.height(); + + return layer; +} + +// |ExternalViewEmbedder| +bool EmbedderExternalViewEmbedder::SubmitFrame(GrContext* context) { + std::map + presented_platform_views; + // Layers may contain pointers to platform views in the collection above. + std::vector presented_layers; + Registry render_targets_used; + + if (!root_render_target_) { + FML_LOG(ERROR) + << "Could not acquire the root render target from the embedder."; + return false; + } + + { + // The root surface is expressed as a layer. + EmbeddedViewParams params; + params.offsetPixels = SkPoint::Make(0, 0); + params.sizePoints = pending_frame_size_; + presented_layers.push_back( + MakeLayer(pending_frame_size_, root_render_target_->GetBackingStore())); + } + + for (const auto& view_id : composition_order_) { + FML_DCHECK(pending_recorders_.count(view_id) == 1); + FML_DCHECK(pending_params_.count(view_id) == 1); + + const auto& params = pending_params_.at(view_id); + auto& recorder = pending_recorders_.at(view_id); + + auto picture = recorder->finishRecordingAsPicture(); + if (!picture) { + FML_LOG(ERROR) << "Could not finish recording into the picture before " + "on-screen composition."; + return false; + } + + const auto backing_store_config = + MakeBackingStoreConfig(pending_frame_size_); + + RegistryKey registry_key(view_id, backing_store_config); + + auto found_render_target = registry_.find(registry_key); + + // Find a cached render target in the registry. If none exists, ask the + // embedder for a new one. + std::shared_ptr render_target; + if (found_render_target == registry_.end()) { + render_target = + create_render_target_callback_(context, backing_store_config); + } else { + render_target = found_render_target->second; + } + + if (!render_target) { + FML_LOG(ERROR) << "Could not acquire external render target for " + "on-screen composition."; + return false; + } + + render_targets_used[registry_key] = render_target; + + auto render_surface = render_target->GetRenderSurface(); + auto render_canvas = render_surface ? render_surface->getCanvas() : nullptr; + + if (!render_canvas) { + FML_LOG(ERROR) + << "Could not acquire render canvas for on-screen rendering."; + return false; + } + + render_canvas->clear(SK_ColorTRANSPARENT); + render_canvas->drawPicture(picture); + render_canvas->flush(); + + // Indicate a layer for the platform view. Add to `presented_platform_views` + // in order to keep at allocated just for the scope of the current method. + // The layers presented to the embedder will contain a back pointer to this + // struct. It is safe to deallocate when the embedder callback is done. + presented_platform_views[view_id] = MakePlatformView(view_id); + presented_layers.push_back( + MakeLayer(params, presented_platform_views.at(view_id))); + + // Indicate a layer for the backing store containing contents rendered by + // Flutter. + presented_layers.push_back( + MakeLayer(pending_frame_size_, render_target->GetBackingStore())); + } + + { + std::vector presented_layers_pointers; + presented_layers_pointers.reserve(presented_layers.size()); + for (const auto& layer : presented_layers) { + presented_layers_pointers.push_back(&layer); + } + present_callback_(std::move(presented_layers_pointers)); + } + + registry_ = std::move(render_targets_used); + + return true; +} + +// |ExternalViewEmbedder| +sk_sp EmbedderExternalViewEmbedder::GetRootSurface() { + return root_render_target_ ? root_render_target_->GetRenderSurface() + : nullptr; +} + +} // namespace flutter diff --git a/shell/platform/embedder/embedder_external_view_embedder.h b/shell/platform/embedder/embedder_external_view_embedder.h new file mode 100644 index 0000000000000..d8329ba520776 --- /dev/null +++ b/shell/platform/embedder/embedder_external_view_embedder.h @@ -0,0 +1,130 @@ +// 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_EXTERNAL_VIEW_EMBEDDER_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_EXTERNAL_VIEW_EMBEDDER_H_ + +#include +#include + +#include "flutter/flow/embedded_views.h" +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/embedder/embedder_render_target.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" + +namespace flutter { + +//------------------------------------------------------------------------------ +/// @brief The external view embedder used by the generic embedder API. +/// This class acts a proxy between the rasterizer and the embedder +/// when the rasterizer is rendering into multiple layers. It asks +/// the embedder for the render targets for the various layers the +/// rasterizer is rendering into, recycles the render targets as +/// necessary and converts rasterizer specific metadata into an +/// embedder friendly format so that it can present the layers +/// on-screen. +/// +class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder { + public: + using CreateRenderTargetCallback = + std::function( + GrContext* context, + const FlutterBackingStoreConfig& config)>; + using PresentCallback = + std::function& layers)>; + + //---------------------------------------------------------------------------- + /// @brief Creates an external view embedder used by the generic embedder + /// API. + /// + /// @param[in] create_render_target_callback + /// The render target callback used to + /// request the render target for a layer. + /// @param[in] present_callback The callback used to forward a + /// collection of layers (backed by + /// fulfilled render targets) to the + /// embedder for presentation. + /// + EmbedderExternalViewEmbedder( + CreateRenderTargetCallback create_render_target_callback, + PresentCallback present_callback); + + //---------------------------------------------------------------------------- + /// @brief Collects the external view embedder. + /// + ~EmbedderExternalViewEmbedder() override; + + private: + // |ExternalViewEmbedder| + void CancelFrame() override; + + // |ExternalViewEmbedder| + void BeginFrame(SkISize frame_size, GrContext* context) override; + + // |ExternalViewEmbedder| + void PrerollCompositeEmbeddedView( + int view_id, + std::unique_ptr params) override; + + // |ExternalViewEmbedder| + std::vector GetCurrentCanvases() override; + + // |ExternalViewEmbedder| + SkCanvas* CompositeEmbeddedView(int view_id) override; + + // |ExternalViewEmbedder| + bool SubmitFrame(GrContext* context) override; + + // |ExternalViewEmbedder| + sk_sp GetRootSurface() override; + + private: + using ViewIdentifier = int64_t; + struct RegistryKey { + ViewIdentifier view_identifier = 0; + SkISize size = SkISize::Make(0, 0); + + RegistryKey(ViewIdentifier view_identifier, + const FlutterBackingStoreConfig& config) + : view_identifier(view_identifier), + size(SkISize::Make(config.size.width, config.size.height)) {} + + struct Hash { + constexpr std::size_t operator()(RegistryKey const& key) const { + return key.view_identifier; + }; + }; + + struct Equal { + constexpr bool operator()(const RegistryKey& lhs, + const RegistryKey& rhs) const { + return lhs.view_identifier == rhs.view_identifier && + lhs.size == rhs.size; + } + }; + }; + + const CreateRenderTargetCallback create_render_target_callback_; + const PresentCallback present_callback_; + using Registry = std::unordered_map, + RegistryKey::Hash, + RegistryKey::Equal>; + + SkISize pending_frame_size_ = SkISize::Make(0, 0); + std::map> + pending_recorders_; + std::map pending_params_; + std::vector composition_order_; + std::shared_ptr root_render_target_; + Registry registry_; + + void Reset(); + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderExternalViewEmbedder); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_EXTERNAL_VIEW_EMBEDDER_H_ diff --git a/shell/platform/embedder/embedder_render_target.cc b/shell/platform/embedder/embedder_render_target.cc new file mode 100644 index 0000000000000..1485cdd388fcd --- /dev/null +++ b/shell/platform/embedder/embedder_render_target.cc @@ -0,0 +1,37 @@ +// 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_render_target.h" + +#include "flutter/fml/logging.h" + +namespace flutter { + +EmbedderRenderTarget::EmbedderRenderTarget(FlutterBackingStore backing_store, + sk_sp render_surface, + fml::closure on_release) + : backing_store_(backing_store), + render_surface_(std::move(render_surface)), + on_release_(on_release) { + // TODO(38468): The optimization to elide backing store updates between frames + // has not been implemented yet. + backing_store_.did_update = true; + FML_DCHECK(render_surface_); +} + +EmbedderRenderTarget::~EmbedderRenderTarget() { + if (on_release_) { + on_release_(); + } +} + +const FlutterBackingStore* EmbedderRenderTarget::GetBackingStore() const { + return &backing_store_; +} + +sk_sp EmbedderRenderTarget::GetRenderSurface() { + return render_surface_; +} + +} // namespace flutter diff --git a/shell/platform/embedder/embedder_render_target.h b/shell/platform/embedder/embedder_render_target.h new file mode 100644 index 0000000000000..a63a068334b9e --- /dev/null +++ b/shell/platform/embedder/embedder_render_target.h @@ -0,0 +1,78 @@ +// 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_RENDER_TARGET_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_RENDER_TARGET_H_ + +#include "flutter/fml/closure.h" +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkSurface.h" + +namespace flutter { + +//------------------------------------------------------------------------------ +/// @brief Describes a surface whose backing store is managed by the +/// embedder. The type of surface depends on the client rendering +/// API used. The embedder is notified of the collection of this +/// render target via a callback. +/// +class EmbedderRenderTarget { + public: + //---------------------------------------------------------------------------- + /// @brief Creates a render target whose backing store is managed by the + /// embedder. The way this render target is exposed to the engine + /// is via an SkSurface and a callback that is invoked by this + /// object in its destructor. + /// + /// @param[in] backing_store The backing store describing this render + /// target. + /// @param[in] render_surface The surface for this target. + /// @param[in] on_release The callback to invoke (eventually forwarded + /// to the embedder) when the backing store is no + /// longer required by the engine. + /// + EmbedderRenderTarget(FlutterBackingStore backing_store, + sk_sp render_surface, + fml::closure on_release); + + //---------------------------------------------------------------------------- + /// @brief Destroys this instance of the render target and invokes the + /// callback for the embedder to release its resource associated + /// with the particular backing store. + /// + ~EmbedderRenderTarget(); + + //---------------------------------------------------------------------------- + /// @brief A render surface the rasterizer can use to draw into the + /// backing store of this render target. + /// + /// @return The render surface. + /// + sk_sp GetRenderSurface(); + + //---------------------------------------------------------------------------- + /// @brief The embedder backing store descriptor. This is the descriptor + /// that was given to the engine by the embedder. This descriptor + /// may contain context the embedder can use to associate it + /// resources with the compositor layers when they are given back + /// to it in present callback. The engine does not use this in any + /// way. + /// + /// @return The backing store. + /// + const FlutterBackingStore* GetBackingStore() const; + + private: + FlutterBackingStore backing_store_; + sk_sp render_surface_; + fml::closure on_release_; + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderRenderTarget); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_RENDER_TARGET_H_ diff --git a/shell/platform/embedder/embedder_surface.h b/shell/platform/embedder/embedder_surface.h index a6d11e3e29c90..cda35e4b2f1f2 100644 --- a/shell/platform/embedder/embedder_surface.h +++ b/shell/platform/embedder/embedder_surface.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_H_ #define FLUTTER_SHELL_PLATFORM_EMBEDDER_EMBEDDER_SURFACE_H_ +#include "flutter/flow/embedded_views.h" #include "flutter/fml/macros.h" #include "flutter/shell/common/surface.h" diff --git a/shell/platform/embedder/embedder_surface_gl.cc b/shell/platform/embedder/embedder_surface_gl.cc index f006ca0fdad7d..b5c320867ae14 100644 --- a/shell/platform/embedder/embedder_surface_gl.cc +++ b/shell/platform/embedder/embedder_surface_gl.cc @@ -8,10 +8,13 @@ namespace flutter { -EmbedderSurfaceGL::EmbedderSurfaceGL(GLDispatchTable gl_dispatch_table, - bool fbo_reset_after_present) +EmbedderSurfaceGL::EmbedderSurfaceGL( + GLDispatchTable gl_dispatch_table, + bool fbo_reset_after_present, + std::unique_ptr external_view_embedder) : gl_dispatch_table_(gl_dispatch_table), - fbo_reset_after_present_(fbo_reset_after_present) { + fbo_reset_after_present_(fbo_reset_after_present), + external_view_embedder_(std::move(external_view_embedder)) { // Make sure all required members of the dispatch table are checked. if (!gl_dispatch_table_.gl_make_current_callback || !gl_dispatch_table_.gl_clear_current_callback || @@ -66,6 +69,11 @@ SkMatrix EmbedderSurfaceGL::GLContextSurfaceTransformation() const { return callback(); } +// |GPUSurfaceGLDelegate| +ExternalViewEmbedder* EmbedderSurfaceGL::GetExternalViewEmbedder() { + return external_view_embedder_.get(); +} + // |GPUSurfaceGLDelegate| EmbedderSurfaceGL::GLProcResolver EmbedderSurfaceGL::GetGLProcResolver() const { return gl_dispatch_table_.gl_proc_resolver; @@ -73,7 +81,11 @@ EmbedderSurfaceGL::GLProcResolver EmbedderSurfaceGL::GetGLProcResolver() const { // |EmbedderSurface| std::unique_ptr EmbedderSurfaceGL::CreateGPUSurface() { - return std::make_unique(this); + bool render_to_surface = !external_view_embedder_; + return std::make_unique(this, // GPU surface GL delegate + render_to_surface // render to surface + + ); } // |EmbedderSurface| diff --git a/shell/platform/embedder/embedder_surface_gl.h b/shell/platform/embedder/embedder_surface_gl.h index faf3dfba171fd..a01fa05d4e62c 100644 --- a/shell/platform/embedder/embedder_surface_gl.h +++ b/shell/platform/embedder/embedder_surface_gl.h @@ -7,6 +7,7 @@ #include "flutter/fml/macros.h" #include "flutter/shell/gpu/gpu_surface_gl.h" +#include "flutter/shell/platform/embedder/embedder_external_view_embedder.h" #include "flutter/shell/platform/embedder/embedder_surface.h" namespace flutter { @@ -25,8 +26,10 @@ class EmbedderSurfaceGL final : public EmbedderSurface, std::function gl_proc_resolver; // optional }; - EmbedderSurfaceGL(GLDispatchTable gl_dispatch_table, - bool fbo_reset_after_present); + EmbedderSurfaceGL( + GLDispatchTable gl_dispatch_table, + bool fbo_reset_after_present, + std::unique_ptr external_view_embedder); ~EmbedderSurfaceGL() override; @@ -35,6 +38,8 @@ class EmbedderSurfaceGL final : public EmbedderSurface, GLDispatchTable gl_dispatch_table_; bool fbo_reset_after_present_; + std::unique_ptr external_view_embedder_; + // |EmbedderSurface| bool IsValid() const override; @@ -62,6 +67,9 @@ class EmbedderSurfaceGL final : public EmbedderSurface, // |GPUSurfaceGLDelegate| SkMatrix GLContextSurfaceTransformation() const override; + // |GPUSurfaceGLDelegate| + ExternalViewEmbedder* GetExternalViewEmbedder() override; + // |GPUSurfaceGLDelegate| GLProcResolver GetGLProcResolver() const override; diff --git a/shell/platform/embedder/embedder_surface_software.cc b/shell/platform/embedder/embedder_surface_software.cc index 0d24933cf5e3d..84a83ddb3a9d0 100644 --- a/shell/platform/embedder/embedder_surface_software.cc +++ b/shell/platform/embedder/embedder_surface_software.cc @@ -10,8 +10,10 @@ namespace flutter { EmbedderSurfaceSoftware::EmbedderSurfaceSoftware( - SoftwareDispatchTable software_dispatch_table) - : software_dispatch_table_(software_dispatch_table) { + SoftwareDispatchTable software_dispatch_table, + std::unique_ptr external_view_embedder) + : software_dispatch_table_(software_dispatch_table), + external_view_embedder_(std::move(external_view_embedder)) { if (!software_dispatch_table_.software_present_backing_store) { return; } @@ -104,4 +106,9 @@ bool EmbedderSurfaceSoftware::PresentBackingStore( ); } +// |GPUSurfaceSoftwareDelegate| +ExternalViewEmbedder* EmbedderSurfaceSoftware::GetExternalViewEmbedder() { + return external_view_embedder_.get(); +} + } // namespace flutter diff --git a/shell/platform/embedder/embedder_surface_software.h b/shell/platform/embedder/embedder_surface_software.h index 6e49f1dc003ac..7dcce095634b9 100644 --- a/shell/platform/embedder/embedder_surface_software.h +++ b/shell/platform/embedder/embedder_surface_software.h @@ -7,6 +7,7 @@ #include "flutter/fml/macros.h" #include "flutter/shell/gpu/gpu_surface_software.h" +#include "flutter/shell/platform/embedder/embedder_external_view_embedder.h" #include "flutter/shell/platform/embedder/embedder_surface.h" namespace flutter { @@ -19,7 +20,9 @@ class EmbedderSurfaceSoftware final : public EmbedderSurface, software_present_backing_store; // required }; - EmbedderSurfaceSoftware(SoftwareDispatchTable software_dispatch_table); + EmbedderSurfaceSoftware( + SoftwareDispatchTable software_dispatch_table, + std::unique_ptr external_view_embedder); ~EmbedderSurfaceSoftware() override; @@ -27,6 +30,7 @@ class EmbedderSurfaceSoftware final : public EmbedderSurface, bool valid_ = false; SoftwareDispatchTable software_dispatch_table_; sk_sp sk_surface_; + std::unique_ptr external_view_embedder_; // |EmbedderSurface| bool IsValid() const override; @@ -43,6 +47,9 @@ class EmbedderSurfaceSoftware final : public EmbedderSurface, // |GPUSurfaceSoftwareDelegate| bool PresentBackingStore(sk_sp backing_store) override; + // |GPUSurfaceSoftwareDelegate| + ExternalViewEmbedder* GetExternalViewEmbedder() override; + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderSurfaceSoftware); }; diff --git a/shell/platform/embedder/fixtures/compositor.png b/shell/platform/embedder/fixtures/compositor.png new file mode 100644 index 0000000000000000000000000000000000000000..a03c3cd4ff5a591da9354269979086dd04f2a340 GIT binary patch literal 1707 zcmeAS@N?(olHy`uVBq!ia0y~yVEh8Y9Be?5)7S2I0V$SZC(jTLAgJL;>0n@BOY(Ga z45^s&_Ku-nNT5X9Lu-c)R>dusCte7=d3!@=q3X5d1om|Y?%nKJs`lkeTT|NuvAV|( z9?X8e(*NAvyR$FXoMr+ViUtnc-ZV?DCUV;O^*@#WpUR5NzD z#}fkhLT6MCf9j!S_{{O)UpMV(SxJHMo`UWBX3A7s+3lY*r`pbHuSA_BLlH44Bw>$; z`8FxNQ@rX=!3nH-$1Zu@Q;ZH1iAfJ{>NWFnOd+ZAc3FD=Vqi+r_(V+VX4X4;c1>G% x(e2l7v;T83?1>~IKcQvnQSN96kP!khck(%uRBP9&&f^B@_H^}gS?83{1OQC46{r9J literal 0 HcmV?d00001 diff --git a/shell/platform/embedder/fixtures/main.dart b/shell/platform/embedder/fixtures/main.dart index 0590ae275b217..5c4d795995fe4 100644 --- a/shell/platform/embedder/fixtures/main.dart +++ b/shell/platform/embedder/fixtures/main.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'dart:ui'; +import 'dart:core'; import 'dart:convert'; void main() {} @@ -178,3 +179,78 @@ void null_platform_messages() { }; signalNativeTest(); } + +Picture CreateSimplePicture() { + Paint blackPaint = Paint(); + PictureRecorder baseRecorder = PictureRecorder(); + Canvas canvas = Canvas(baseRecorder); + canvas.drawRect(Rect.fromLTRB(0.0, 0.0, 1000.0, 1000.0), blackPaint); + return baseRecorder.endRecording(); +} + +@pragma('vm:entry-point') +void can_composite_platform_views() { + window.onBeginFrame = (Duration duration) { + SceneBuilder builder = SceneBuilder(); + builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); + builder.pushOffset(1.0, 2.0); + builder.addPlatformView(42, width: 123.0, height: 456.0); + builder.addPicture(Offset(1.0, 1.0), CreateSimplePicture()); + builder.pop(); // offset + signalNativeTest(); // Signal 2 + window.render(builder.build()); + }; + signalNativeTest(); // Signal 1 + window.scheduleFrame(); +} + +Picture CreateColoredBox(Color color, Size size) { + Paint paint = Paint(); + paint.color = color; + PictureRecorder baseRecorder = PictureRecorder(); + Canvas canvas = Canvas(baseRecorder); + canvas.drawRect(Rect.fromLTRB(0.0, 0.0, size.width, size.height), paint); + return baseRecorder.endRecording(); +} + +@pragma('vm:entry-point') +void can_composite_platform_views_with_known_scene() { + window.onBeginFrame = (Duration duration) { + Color red = Color.fromARGB(127, 255, 0, 0); + Color blue = Color.fromARGB(127, 0, 0, 255); + Color gray = Color.fromARGB(127, 127, 127, 127); + + Size size = Size(50.0, 150.0); + + SceneBuilder builder = SceneBuilder(); + builder.pushOffset(0.0, 0.0); + + // 10 (Index 0) + builder.addPicture(Offset(10.0, 10.0), CreateColoredBox(red, size)); // red - flutter + + builder.pushOffset(20.0, 20.0); + // 20 (Index 1) + builder.addPlatformView(1, width: size.width, height:size.height); // green - platform + builder.pop(); + + // 30 (Index 2) + builder.addPicture(Offset(30.0, 30.0), CreateColoredBox(blue, size)); // blue - flutter + + builder.pushOffset(40.0, 40.0); + // 40 (Index 3) + builder.addPlatformView(2, width: size.width, height:size.height); // magenta - platform + builder.pop(); + + // 50 (Index 4) + builder.addPicture(Offset(50.0, 50.0), CreateColoredBox(gray, size)); // gray - flutter + + builder.pop(); + + window.render(builder.build()); + + signalNativeTest(); // Signal 2 + }; + signalNativeTest(); // Signal 1 + window.scheduleFrame(); +} + diff --git a/shell/platform/embedder/platform_view_embedder.cc b/shell/platform/embedder/platform_view_embedder.cc index 823d7de651ce9..b6daf1946704a 100644 --- a/shell/platform/embedder/platform_view_embedder.cc +++ b/shell/platform/embedder/platform_view_embedder.cc @@ -11,21 +11,25 @@ PlatformViewEmbedder::PlatformViewEmbedder( flutter::TaskRunners task_runners, EmbedderSurfaceGL::GLDispatchTable gl_dispatch_table, bool fbo_reset_after_present, - PlatformDispatchTable platform_dispatch_table) + PlatformDispatchTable platform_dispatch_table, + std::unique_ptr external_view_embedder) : PlatformView(delegate, std::move(task_runners)), - embedder_surface_( - std::make_unique(gl_dispatch_table, - fbo_reset_after_present)), + embedder_surface_(std::make_unique( + gl_dispatch_table, + fbo_reset_after_present, + std::move(external_view_embedder))), platform_dispatch_table_(platform_dispatch_table) {} PlatformViewEmbedder::PlatformViewEmbedder( PlatformView::Delegate& delegate, flutter::TaskRunners task_runners, EmbedderSurfaceSoftware::SoftwareDispatchTable software_dispatch_table, - PlatformDispatchTable platform_dispatch_table) + PlatformDispatchTable platform_dispatch_table, + std::unique_ptr external_view_embedder) : PlatformView(delegate, std::move(task_runners)), - embedder_surface_( - std::make_unique(software_dispatch_table)), + embedder_surface_(std::make_unique( + software_dispatch_table, + std::move(external_view_embedder))), platform_dispatch_table_(platform_dispatch_table) {} PlatformViewEmbedder::~PlatformViewEmbedder() = default; diff --git a/shell/platform/embedder/platform_view_embedder.h b/shell/platform/embedder/platform_view_embedder.h index 6e40dc5808214..13d4c45684655 100644 --- a/shell/platform/embedder/platform_view_embedder.h +++ b/shell/platform/embedder/platform_view_embedder.h @@ -36,18 +36,21 @@ class PlatformViewEmbedder final : public PlatformView { }; // Creates a platform view that sets up an OpenGL rasterizer. - PlatformViewEmbedder(PlatformView::Delegate& delegate, - flutter::TaskRunners task_runners, - EmbedderSurfaceGL::GLDispatchTable gl_dispatch_table, - bool fbo_reset_after_present, - PlatformDispatchTable platform_dispatch_table); + PlatformViewEmbedder( + PlatformView::Delegate& delegate, + flutter::TaskRunners task_runners, + EmbedderSurfaceGL::GLDispatchTable gl_dispatch_table, + bool fbo_reset_after_present, + PlatformDispatchTable platform_dispatch_table, + std::unique_ptr external_view_embedder); // Create a platform view that sets up a software rasterizer. PlatformViewEmbedder( PlatformView::Delegate& delegate, flutter::TaskRunners task_runners, EmbedderSurfaceSoftware::SoftwareDispatchTable software_dispatch_table, - PlatformDispatchTable platform_dispatch_table); + PlatformDispatchTable platform_dispatch_table, + std::unique_ptr external_view_embedder); ~PlatformViewEmbedder() override; diff --git a/shell/platform/embedder/tests/embedder_assertions.h b/shell/platform/embedder/tests/embedder_assertions.h new file mode 100644 index 0000000000000..bdc2a597368e4 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_assertions.h @@ -0,0 +1,250 @@ +// 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_ASSERTIONS_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_ASSERTIONS_H_ + +#include + +#include "flutter/fml/logging.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/testing/assertions.h" +#include "gtest/gtest.h" +#include "third_party/skia/include/core/SkPoint.h" +#include "third_party/skia/include/core/SkSize.h" + +//------------------------------------------------------------------------------ +// Equality +//------------------------------------------------------------------------------ +inline bool operator==(const FlutterPoint& a, const FlutterPoint& b) { + return flutter::testing::NumberNear(a.x, b.x) && + flutter::testing::NumberNear(a.y, b.y); +} + +inline bool operator==(const FlutterSize& a, const FlutterSize& b) { + return flutter::testing::NumberNear(a.width, b.width) && + flutter::testing::NumberNear(a.height, b.height); +} + +inline bool operator==(const FlutterOpenGLTexture& a, + const FlutterOpenGLTexture& b) { + return a.target == b.target && a.name == b.name && a.format == b.format && + a.user_data == b.user_data && + a.destruction_callback == b.destruction_callback; +} + +inline bool operator==(const FlutterOpenGLFramebuffer& a, + const FlutterOpenGLFramebuffer& b) { + return a.target == b.target && a.name == b.name && + a.user_data == b.user_data && + a.destruction_callback == b.destruction_callback; +} + +inline bool operator==(const FlutterOpenGLBackingStore& a, + const FlutterOpenGLBackingStore& b) { + if (!(a.type == b.type)) { + return false; + } + + switch (a.type) { + case kFlutterOpenGLTargetTypeTexture: + return a.texture == b.texture; + case kFlutterOpenGLTargetTypeFramebuffer: + return a.framebuffer == b.framebuffer; + } + + return false; +} + +inline bool operator==(const FlutterSoftwareBackingStore& a, + const FlutterSoftwareBackingStore& b) { + return a.allocation == b.allocation && a.row_bytes == b.row_bytes && + a.height == b.height && a.user_data == b.user_data && + a.destruction_callback == b.destruction_callback; +} + +inline bool operator==(const FlutterBackingStore& a, + const FlutterBackingStore& b) { + if (!(a.struct_size == b.struct_size && a.user_data == b.user_data && + a.type == b.type && a.did_update == b.did_update)) { + return false; + } + + switch (a.type) { + case kFlutterBackingStoreTypeOpenGL: + return a.open_gl == b.open_gl; + case kFlutterBackingStoreTypeSoftware: + return a.software == b.software; + } + + return false; +} + +inline bool operator==(const FlutterPlatformView& a, + const FlutterPlatformView& b) { + return a.struct_size == b.struct_size && a.identifier == b.identifier; +} + +inline bool operator==(const FlutterLayer& a, const FlutterLayer& b) { + if (!(a.struct_size == b.struct_size && a.type == b.type && + a.offset == b.offset && a.size == b.size)) { + return false; + } + + switch (a.type) { + case kFlutterLayerContentTypeBackingStore: + return *a.backing_store == *b.backing_store; + case kFlutterLayerContentTypePlatformView: + return *a.platform_view == *b.platform_view; + } + + return false; +} + +//------------------------------------------------------------------------------ +// Printing +//------------------------------------------------------------------------------ + +inline std::ostream& operator<<(std::ostream& out, const FlutterPoint& point) { + return out << "(" << point.x << ", " << point.y << ")"; +} + +inline std::ostream& operator<<(std::ostream& out, const FlutterSize& size) { + return out << "(" << size.width << ", " << size.height << ")"; +} + +inline std::string FlutterLayerContentTypeToString( + FlutterLayerContentType type) { + switch (type) { + case kFlutterLayerContentTypeBackingStore: + return "kFlutterLayerContentTypeBackingStore"; + case kFlutterLayerContentTypePlatformView: + return "kFlutterLayerContentTypePlatformView"; + } + return "Unknown"; +} + +inline std::string FlutterBackingStoreTypeToString( + FlutterBackingStoreType type) { + switch (type) { + case kFlutterBackingStoreTypeOpenGL: + return "kFlutterBackingStoreTypeOpenGL"; + case kFlutterBackingStoreTypeSoftware: + return "kFlutterBackingStoreTypeSoftware"; + } + return "Unknown"; +} + +inline std::ostream& operator<<(std::ostream& out, + const FlutterOpenGLTexture& item) { + return out << "(FlutterOpenGLTexture) Target: 0x" << std::hex << item.target + << std::dec << " Name: " << item.name << " Format: " << item.format + << " User Data: " << item.user_data + << " Destruction Callback: " << item.destruction_callback; +} + +inline std::ostream& operator<<(std::ostream& out, + const FlutterOpenGLFramebuffer& item) { + return out << "(FlutterOpenGLFramebuffer) Target: 0x" << std::hex + << item.target << std::dec << " Name: " << item.name + << " User Data: " << item.user_data + << " Destruction Callback: " << item.destruction_callback; +} + +inline std::ostream& operator<<(std::ostream& out, + const FlutterPlatformView& platform_view) { + return out << "(FlutterPlatformView) Struct Size: " + << platform_view.struct_size + << " Identifier: " << platform_view.identifier; +} + +inline std::string FlutterOpenGLTargetTypeToString( + FlutterOpenGLTargetType type) { + switch (type) { + case kFlutterOpenGLTargetTypeTexture: + return "kFlutterOpenGLTargetTypeTexture"; + case kFlutterOpenGLTargetTypeFramebuffer: + return "kFlutterOpenGLTargetTypeFramebuffer"; + } + return "Unknown"; +} + +inline std::ostream& operator<<(std::ostream& out, + const FlutterOpenGLBackingStore& item) { + out << "(FlutterOpenGLBackingStore) Type: " + << FlutterOpenGLTargetTypeToString(item.type) << " "; + switch (item.type) { + case kFlutterOpenGLTargetTypeTexture: + out << item.texture; + break; + case kFlutterOpenGLTargetTypeFramebuffer: + out << item.framebuffer; + break; + } + return out; +} + +inline std::ostream& operator<<(std::ostream& out, + const FlutterSoftwareBackingStore& item) { + return out << "(FlutterSoftwareBackingStore) Allocation: " << item.allocation + << " Row Bytes: " << item.row_bytes << " Height: " << item.height + << " User Data: " << item.user_data + << " Destruction Callback: " << item.destruction_callback; +} + +inline std::ostream& operator<<(std::ostream& out, + const FlutterBackingStore& backing_store) { + out << "(FlutterBackingStore) Struct size: " << backing_store.struct_size + << " User Data: " << backing_store.user_data + << " Type: " << FlutterBackingStoreTypeToString(backing_store.type) + << " "; + + switch (backing_store.type) { + case kFlutterBackingStoreTypeOpenGL: + out << backing_store.open_gl; + break; + + case kFlutterBackingStoreTypeSoftware: + out << backing_store.software; + break; + } + + return out; +} + +inline std::ostream& operator<<(std::ostream& out, const FlutterLayer& layer) { + out << "(Flutter Layer) Struct size: " << layer.struct_size + << " Type: " << FlutterLayerContentTypeToString(layer.type); + + switch (layer.type) { + case kFlutterLayerContentTypeBackingStore: + out << *layer.backing_store; + break; + case kFlutterLayerContentTypePlatformView: + out << *layer.platform_view; + break; + } + + return out << " Offset: " << layer.offset << " Size: " << layer.size; +} + +//------------------------------------------------------------------------------ +// Factories and Casts +//------------------------------------------------------------------------------ + +inline FlutterPoint FlutterPointMake(double x, double y) { + FlutterPoint point = {}; + point.x = x; + point.y = y; + return point; +} + +inline FlutterSize FlutterSizeMake(double width, double height) { + FlutterSize size = {}; + size.width = width; + size.height = height; + return size; +} + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_ASSERTIONS_H_ diff --git a/shell/platform/embedder/tests/embedder_config_builder.cc b/shell/platform/embedder/tests/embedder_config_builder.cc index 24e05be0763bf..00831c65383ac 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.cc +++ b/shell/platform/embedder/tests/embedder_config_builder.cc @@ -4,42 +4,46 @@ #include "flutter/shell/platform/embedder/tests/embedder_config_builder.h" +#include "flutter/shell/platform/embedder/embedder.h" + namespace flutter { namespace testing { EmbedderConfigBuilder::EmbedderConfigBuilder( - EmbedderContext& context, + EmbedderTestContext& context, InitializationPreference preference) : context_(context) { project_args_.struct_size = sizeof(project_args_); project_args_.shutdown_dart_vm_when_done = true; project_args_.platform_message_callback = [](const FlutterPlatformMessage* message, void* context) { - reinterpret_cast(context)->PlatformMessageCallback( - message); + reinterpret_cast(context) + ->PlatformMessageCallback(message); }; custom_task_runners_.struct_size = sizeof(FlutterCustomTaskRunners); opengl_renderer_config_.struct_size = sizeof(FlutterOpenGLRendererConfig); opengl_renderer_config_.make_current = [](void* context) -> bool { - return reinterpret_cast(context)->GLMakeCurrent(); + return reinterpret_cast(context)->GLMakeCurrent(); }; opengl_renderer_config_.clear_current = [](void* context) -> bool { - return reinterpret_cast(context)->GLClearCurrent(); + return reinterpret_cast(context)->GLClearCurrent(); }; opengl_renderer_config_.present = [](void* context) -> bool { - return reinterpret_cast(context)->GLPresent(); + return reinterpret_cast(context)->GLPresent(); }; opengl_renderer_config_.fbo_callback = [](void* context) -> uint32_t { - return reinterpret_cast(context)->GLGetFramebuffer(); + return reinterpret_cast(context)->GLGetFramebuffer(); }; opengl_renderer_config_.make_resource_current = [](void* context) -> bool { - return reinterpret_cast(context)->GLMakeResourceCurrent(); + return reinterpret_cast(context) + ->GLMakeResourceCurrent(); }; opengl_renderer_config_.gl_proc_resolver = [](void* context, const char* name) -> void* { - return reinterpret_cast(context)->GLGetProcAddress(name); + return reinterpret_cast(context)->GLGetProcAddress( + name); }; software_renderer_config_.struct_size = sizeof(FlutterSoftwareRendererConfig); @@ -100,14 +104,14 @@ void EmbedderConfigBuilder::SetSnapshots() { void EmbedderConfigBuilder::SetIsolateCreateCallbackHook() { project_args_.root_isolate_create_callback = - EmbedderContext::GetIsolateCreateCallbackHook(); + EmbedderTestContext::GetIsolateCreateCallbackHook(); } void EmbedderConfigBuilder::SetSemanticsCallbackHooks() { project_args_.update_semantics_node_callback = - EmbedderContext::GetUpdateSemanticsNodeCallbackHook(); + EmbedderTestContext::GetUpdateSemanticsNodeCallbackHook(); project_args_.update_semantics_custom_action_callback = - EmbedderContext::GetUpdateSemanticsCustomActionCallbackHook(); + EmbedderTestContext::GetUpdateSemanticsCustomActionCallbackHook(); } void EmbedderConfigBuilder::SetDartEntrypoint(std::string entrypoint) { @@ -141,6 +145,43 @@ void EmbedderConfigBuilder::SetPlatformMessageCallback( context_.SetPlatformMessageCallback(callback); } +void EmbedderConfigBuilder::SetCompositor() { + context_.SetupCompositor(); + auto& compositor = context_.GetCompositor(); + compositor_.struct_size = sizeof(compositor_); + compositor_.user_data = &compositor; + compositor_.create_backing_store_callback = + [](const FlutterBackingStoreConfig* config, // + FlutterBackingStore* backing_store_out, // + void* user_data // + ) { + return reinterpret_cast(user_data) + ->CreateBackingStore(config, backing_store_out); + }; + compositor_.collect_backing_store_callback = + [](const FlutterBackingStore* backing_store, // + void* user_data // + ) { + return reinterpret_cast(user_data) + ->CollectBackingStore(backing_store); + }; + compositor_.present_layers_callback = [](const FlutterLayer** layers, // + size_t layers_count, // + void* user_data // + ) { + return reinterpret_cast(user_data)->Present( + layers, // + layers_count // + + ); + }; + project_args_.compositor = &compositor_; +} + +FlutterCompositor& EmbedderConfigBuilder::GetCompositor() { + return compositor_; +} + UniqueEngine EmbedderConfigBuilder::LaunchEngine() { FlutterEngine engine = nullptr; diff --git a/shell/platform/embedder/tests/embedder_config_builder.h b/shell/platform/embedder/tests/embedder_config_builder.h index 3d04f2498131f..917e3936702a7 100644 --- a/shell/platform/embedder/tests/embedder_config_builder.h +++ b/shell/platform/embedder/tests/embedder_config_builder.h @@ -8,8 +8,9 @@ #include "flutter/fml/macros.h" #include "flutter/fml/unique_object.h" #include "flutter/shell/platform/embedder/embedder.h" -#include "flutter/shell/platform/embedder/tests/embedder_context.h" #include "flutter/shell/platform/embedder/tests/embedder_test.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_compositor.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_context.h" namespace flutter { namespace testing { @@ -34,7 +35,7 @@ class EmbedderConfigBuilder { kNoInitialize, }; - EmbedderConfigBuilder(EmbedderContext& context, + EmbedderConfigBuilder(EmbedderTestContext& context, InitializationPreference preference = InitializationPreference::kInitialize); @@ -63,16 +64,21 @@ class EmbedderConfigBuilder { void SetPlatformMessageCallback( std::function callback); + void SetCompositor(); + + FlutterCompositor& GetCompositor(); + UniqueEngine LaunchEngine(); private: - EmbedderContext& context_; + EmbedderTestContext& context_; FlutterProjectArgs project_args_ = {}; FlutterRendererConfig renderer_config_ = {}; FlutterSoftwareRendererConfig software_renderer_config_ = {}; FlutterOpenGLRendererConfig opengl_renderer_config_ = {}; std::string dart_entrypoint_; FlutterCustomTaskRunners custom_task_runners_ = {}; + FlutterCompositor compositor_ = {}; std::vector command_line_arguments_; FML_DISALLOW_COPY_AND_ASSIGN(EmbedderConfigBuilder); diff --git a/shell/platform/embedder/tests/embedder_test.cc b/shell/platform/embedder/tests/embedder_test.cc index 62192fd253899..cc699c26e463a 100644 --- a/shell/platform/embedder/tests/embedder_test.cc +++ b/shell/platform/embedder/tests/embedder_test.cc @@ -15,12 +15,12 @@ std::string EmbedderTest::GetFixturesDirectory() const { return GetFixturesPath(); } -EmbedderContext& EmbedderTest::GetEmbedderContext() { +EmbedderTestContext& EmbedderTest::GetEmbedderContext() { // Setup the embedder context lazily instead of in the SetUp method because we // don't to do all the work if the test won't end up using context. if (!embedder_context_) { embedder_context_ = - std::make_unique(GetFixturesDirectory()); + std::make_unique(GetFixturesDirectory()); } return *embedder_context_; } diff --git a/shell/platform/embedder/tests/embedder_test.h b/shell/platform/embedder/tests/embedder_test.h index ab1717400cf15..f170a106553dc 100644 --- a/shell/platform/embedder/tests/embedder_test.h +++ b/shell/platform/embedder/tests/embedder_test.h @@ -8,7 +8,7 @@ #include #include "flutter/fml/macros.h" -#include "flutter/shell/platform/embedder/tests/embedder_context.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_context.h" #include "flutter/testing/testing.h" #include "flutter/testing/thread_test.h" @@ -23,10 +23,10 @@ class EmbedderTest : public ThreadTest { std::string GetFixturesDirectory() const; - EmbedderContext& GetEmbedderContext(); + EmbedderTestContext& GetEmbedderContext(); private: - std::unique_ptr embedder_context_; + std::unique_ptr embedder_context_; // |testing::Test| void SetUp() override; diff --git a/shell/platform/embedder/tests/embedder_test_compositor.cc b/shell/platform/embedder/tests/embedder_test_compositor.cc new file mode 100644 index 0000000000000..b651f4924a863 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_compositor.cc @@ -0,0 +1,305 @@ +// 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_compositor.h" + +#include "flutter/fml/logging.h" +#include "flutter/shell/platform/embedder/tests/embedder_assertions.h" +#include "third_party/skia/include/core/SkSurface.h" + +namespace flutter { +namespace testing { + +EmbedderTestCompositor::EmbedderTestCompositor(sk_sp context) + : context_(context) { + FML_CHECK(context_); +} + +EmbedderTestCompositor::~EmbedderTestCompositor() = default; + +void EmbedderTestCompositor::SetRenderTargetType(RenderTargetType type) { + type_ = type; +} + +bool EmbedderTestCompositor::CreateBackingStore( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + switch (type_) { + case RenderTargetType::kOpenGLFramebuffer: + return CreateFramebufferRenderSurface(config, backing_store_out); + case RenderTargetType::kOpenGLTexture: + return CreateTextureRenderSurface(config, backing_store_out); + case RenderTargetType::kSoftwareBuffer: + return CreateSoftwareRenderSurface(config, backing_store_out); + } + FML_CHECK(false); + return false; +} + +bool EmbedderTestCompositor::CollectBackingStore( + const FlutterBackingStore* backing_store) { + // We have already set the destruction callback for the various backing + // stores. Our user_data is just the canvas from that backing store and does + // not need to be explicitly collected. Embedders might have some other state + // they want to collect though. + return true; +} + +bool EmbedderTestCompositor::UpdateOffscrenComposition( + const FlutterLayer** layers, + size_t layers_count) { + last_composition_ = nullptr; + + auto surface_size = SkISize::Make(800, 600); + + auto surface = SkSurface::MakeRenderTarget( + context_.get(), // context + SkBudgeted::kNo, // budgeted + SkImageInfo::MakeN32Premul(surface_size), // image info + 1, // sample count + kTopLeft_GrSurfaceOrigin, // surface origin + nullptr, // surface properties + false // create mipmaps + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not update the off-screen composition."; + return false; + } + + auto canvas = surface->getCanvas(); + + // This has to be transparent because we are going to be compositing this + // sub-hierarchy onto the on-screen surface. + canvas->clear(SK_ColorTRANSPARENT); + + for (size_t i = 0; i < layers_count; ++i) { + const auto* layer = layers[i]; + + sk_sp platform_renderered_contents; + + sk_sp layer_image; + SkIPoint canvas_offset = SkIPoint::Make(0, 0); + + switch (layer->type) { + case kFlutterLayerContentTypeBackingStore: + layer_image = + reinterpret_cast(layer->backing_store->user_data) + ->makeImageSnapshot(); + + break; + case kFlutterLayerContentTypePlatformView: + layer_image = + platform_view_renderer_callback_ + ? platform_view_renderer_callback_(*layer, context_.get()) + : nullptr; + canvas_offset = SkIPoint::Make(layer->offset.x, layer->offset.y); + break; + }; + + if (!layer_image && layer->type != kFlutterLayerContentTypePlatformView) { + FML_LOG(ERROR) << "Could not snapshot layer in test compositor: " + << *layer; + return false; + } + + // The image rendered by Flutter already has the correct offset and + // transformation applied. The layers offset is meant for the platform. + canvas->drawImage(layer_image.get(), canvas_offset.x(), canvas_offset.y()); + } + + last_composition_ = surface->makeImageSnapshot(); + + if (!last_composition_) { + FML_LOG(ERROR) << "Could not update the contents of the sub-composition."; + return false; + } + + if (next_scene_callback_) { + auto last_composition_snapshot = last_composition_->makeRasterImage(); + FML_CHECK(last_composition_snapshot); + auto callback = next_scene_callback_; + next_scene_callback_ = nullptr; + callback(std::move(last_composition_snapshot)); + } + + return true; +} + +sk_sp EmbedderTestCompositor::GetLastComposition() { + return last_composition_; +} + +bool EmbedderTestCompositor::Present(const FlutterLayer** layers, + size_t layers_count) { + if (!UpdateOffscrenComposition(layers, layers_count)) { + FML_LOG(ERROR) + << "Could not update the off-screen composition in the test compositor"; + return false; + } + + // If the test has asked to access the layers and renderers being presented. + // Access the same and present it to the test for its test assertions. + if (next_present_callback_) { + auto callback = next_present_callback_; + next_present_callback_ = nullptr; + callback(layers, layers_count); + } + + return true; +} + +bool EmbedderTestCompositor::CreateFramebufferRenderSurface( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + const auto image_info = + SkImageInfo::MakeN32Premul(config->size.width, config->size.height); + + auto surface = + SkSurface::MakeRenderTarget(context_.get(), // context + SkBudgeted::kNo, // budgeted + image_info, // image info + 1, // sample count + kTopLeft_GrSurfaceOrigin, // surface origin + nullptr, // surface properties + false // mipmaps + + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not create render target for compositor layer."; + return false; + } + + GrBackendRenderTarget render_target = surface->getBackendRenderTarget( + SkSurface::BackendHandleAccess::kDiscardWrite_BackendHandleAccess); + + if (!render_target.isValid()) { + FML_LOG(ERROR) << "Backend render target was invalid."; + return false; + } + + GrGLFramebufferInfo framebuffer_info = {}; + if (!render_target.getGLFramebufferInfo(&framebuffer_info)) { + FML_LOG(ERROR) << "Could not access backend framebuffer info."; + return false; + } + + backing_store_out->type = kFlutterBackingStoreTypeOpenGL; + backing_store_out->user_data = surface.get(); + backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + backing_store_out->open_gl.framebuffer.target = framebuffer_info.fFormat; + backing_store_out->open_gl.framebuffer.name = framebuffer_info.fFBOID; + // The balancing unref is in the destruction callback. + surface->ref(); + backing_store_out->open_gl.framebuffer.user_data = surface.get(); + backing_store_out->open_gl.framebuffer.destruction_callback = + [](void* user_data) { reinterpret_cast(user_data)->unref(); }; + + return true; +} + +bool EmbedderTestCompositor::CreateTextureRenderSurface( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + const auto image_info = + SkImageInfo::MakeN32Premul(config->size.width, config->size.height); + + auto surface = + SkSurface::MakeRenderTarget(context_.get(), // context + SkBudgeted::kNo, // budgeted + image_info, // image info + 1, // sample count + kTopLeft_GrSurfaceOrigin, // surface origin + nullptr, // surface properties + false // mipmaps + + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not create render target for compositor layer."; + return false; + } + + GrBackendTexture render_texture = surface->getBackendTexture( + SkSurface::BackendHandleAccess::kDiscardWrite_BackendHandleAccess); + + if (!render_texture.isValid()) { + FML_LOG(ERROR) << "Backend render texture was invalid."; + return false; + } + + GrGLTextureInfo texture_info = {}; + if (!render_texture.getGLTextureInfo(&texture_info)) { + FML_LOG(ERROR) << "Could not access backend texture info."; + return false; + } + + backing_store_out->type = kFlutterBackingStoreTypeOpenGL; + backing_store_out->user_data = surface.get(); + backing_store_out->open_gl.type = kFlutterOpenGLTargetTypeTexture; + backing_store_out->open_gl.texture.target = texture_info.fTarget; + backing_store_out->open_gl.texture.name = texture_info.fID; + backing_store_out->open_gl.texture.format = texture_info.fFormat; + // The balancing unref is in the destruction callback. + surface->ref(); + backing_store_out->open_gl.texture.user_data = surface.get(); + backing_store_out->open_gl.texture.destruction_callback = + [](void* user_data) { reinterpret_cast(user_data)->unref(); }; + + return true; +} + +bool EmbedderTestCompositor::CreateSoftwareRenderSurface( + const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out) { + auto surface = SkSurface::MakeRaster( + SkImageInfo::MakeN32Premul(config->size.width, config->size.height)); + + if (!surface) { + FML_LOG(ERROR) + << "Could not create the render target for compositor layer."; + return false; + } + + SkPixmap pixmap; + if (!surface->peekPixels(&pixmap)) { + FML_LOG(ERROR) << "Could not peek pixels of pixmap."; + return false; + } + + backing_store_out->type = kFlutterBackingStoreTypeSoftware; + backing_store_out->user_data = surface.get(); + backing_store_out->software.allocation = pixmap.addr(); + backing_store_out->software.row_bytes = pixmap.rowBytes(); + backing_store_out->software.height = pixmap.height(); + // The balancing unref is in the destruction callback. + surface->ref(); + backing_store_out->software.user_data = surface.get(); + backing_store_out->software.destruction_callback = [](void* user_data) { + reinterpret_cast(user_data)->unref(); + }; + + return true; +} + +void EmbedderTestCompositor::SetNextPresentCallback( + PresentCallback next_present_callback) { + FML_CHECK(!next_present_callback_); + next_present_callback_ = next_present_callback; +} + +void EmbedderTestCompositor::SetNextSceneCallback( + NextSceneCallback next_scene_callback) { + FML_CHECK(!next_scene_callback_); + next_scene_callback_ = next_scene_callback; +} + +void EmbedderTestCompositor::SetPlatformViewRendererCallback( + PlatformViewRendererCallback callback) { + platform_view_renderer_callback_ = callback; +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_test_compositor.h b/shell/platform/embedder/tests/embedder_test_compositor.h new file mode 100644 index 0000000000000..8a427d1cd7e44 --- /dev/null +++ b/shell/platform/embedder/tests/embedder_test_compositor.h @@ -0,0 +1,85 @@ +// 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_TEST_COMPOSITOR_H_ +#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_COMPOSITOR_H_ + +#include + +#include "flutter/fml/macros.h" +#include "flutter/shell/platform/embedder/embedder.h" +#include "third_party/skia/include/gpu/GrContext.h" + +namespace flutter { +namespace testing { + +class EmbedderTestCompositor { + public: + enum class RenderTargetType { + kOpenGLFramebuffer, + kOpenGLTexture, + kSoftwareBuffer, + }; + + EmbedderTestCompositor(sk_sp context); + + ~EmbedderTestCompositor(); + + void SetRenderTargetType(RenderTargetType type); + + bool CreateBackingStore(const FlutterBackingStoreConfig* config, + FlutterBackingStore* backing_store_out); + + bool CollectBackingStore(const FlutterBackingStore* backing_store); + + bool Present(const FlutterLayer** layers, size_t layers_count); + + using PlatformViewRendererCallback = + std::function(const FlutterLayer& layer, + GrContext* context)>; + void SetPlatformViewRendererCallback(PlatformViewRendererCallback callback); + + using PresentCallback = + std::function; + //---------------------------------------------------------------------------- + /// @brief Allows tests to install a callback to notify them when the + /// entire render tree has been finalized so they can run their + /// assertions. + /// + /// @param[in] next_present_callback The next present callback + /// + void SetNextPresentCallback(PresentCallback next_present_callback); + + using NextSceneCallback = std::function image)>; + void SetNextSceneCallback(NextSceneCallback next_scene_callback); + + sk_sp GetLastComposition(); + + private: + sk_sp context_; + RenderTargetType type_ = RenderTargetType::kOpenGLFramebuffer; + PlatformViewRendererCallback platform_view_renderer_callback_; + PresentCallback next_present_callback_; + NextSceneCallback next_scene_callback_; + sk_sp last_composition_; + + bool UpdateOffscrenComposition(const FlutterLayer** layers, + size_t layers_count); + + bool CreateFramebufferRenderSurface(const FlutterBackingStoreConfig* config, + FlutterBackingStore* renderer_out); + + bool CreateTextureRenderSurface(const FlutterBackingStoreConfig* config, + FlutterBackingStore* renderer_out); + + bool CreateSoftwareRenderSurface(const FlutterBackingStoreConfig* config, + FlutterBackingStore* renderer_out); + + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestCompositor); +}; + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_TEST_COMPOSITOR_H_ diff --git a/shell/platform/embedder/tests/embedder_context.cc b/shell/platform/embedder/tests/embedder_test_context.cc similarity index 52% rename from shell/platform/embedder/tests/embedder_context.cc rename to shell/platform/embedder/tests/embedder_test_context.cc index 5b8297547c66b..212dc34ffc5e9 100644 --- a/shell/platform/embedder/tests/embedder_context.cc +++ b/shell/platform/embedder/tests/embedder_test_context.cc @@ -2,14 +2,15 @@ // 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_context.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_context.h" #include "flutter/runtime/dart_vm.h" +#include "third_party/skia/include/core/SkSurface.h" namespace flutter { namespace testing { -EmbedderContext::EmbedderContext(std::string assets_path) +EmbedderTestContext::EmbedderTestContext(std::string assets_path) : assets_path_(std::move(assets_path)), native_resolver_(std::make_shared()) { auto assets_dir = fml::OpenDirectory(assets_path_.c_str(), false, @@ -35,68 +36,70 @@ EmbedderContext::EmbedderContext(std::string assets_path) }); } -EmbedderContext::~EmbedderContext() = default; +EmbedderTestContext::~EmbedderTestContext() = default; -const std::string& EmbedderContext::GetAssetsPath() const { +const std::string& EmbedderTestContext::GetAssetsPath() const { return assets_path_; } -const fml::Mapping* EmbedderContext::GetVMSnapshotData() const { +const fml::Mapping* EmbedderTestContext::GetVMSnapshotData() const { return vm_snapshot_data_.get(); } -const fml::Mapping* EmbedderContext::GetVMSnapshotInstructions() const { +const fml::Mapping* EmbedderTestContext::GetVMSnapshotInstructions() const { return vm_snapshot_instructions_.get(); } -const fml::Mapping* EmbedderContext::GetIsolateSnapshotData() const { +const fml::Mapping* EmbedderTestContext::GetIsolateSnapshotData() const { return isolate_snapshot_data_.get(); } -const fml::Mapping* EmbedderContext::GetIsolateSnapshotInstructions() const { +const fml::Mapping* EmbedderTestContext::GetIsolateSnapshotInstructions() + const { return isolate_snapshot_instructions_.get(); } -void EmbedderContext::AddIsolateCreateCallback(fml::closure closure) { +void EmbedderTestContext::AddIsolateCreateCallback(fml::closure closure) { if (closure) { isolate_create_callbacks_.push_back(closure); } } -VoidCallback EmbedderContext::GetIsolateCreateCallbackHook() { +VoidCallback EmbedderTestContext::GetIsolateCreateCallbackHook() { return [](void* user_data) { - reinterpret_cast(user_data)->FireIsolateCreateCallbacks(); + reinterpret_cast(user_data) + ->FireIsolateCreateCallbacks(); }; } -void EmbedderContext::FireIsolateCreateCallbacks() { +void EmbedderTestContext::FireIsolateCreateCallbacks() { for (auto closure : isolate_create_callbacks_) { closure(); } } -void EmbedderContext::AddNativeCallback(const char* name, - Dart_NativeFunction function) { +void EmbedderTestContext::AddNativeCallback(const char* name, + Dart_NativeFunction function) { native_resolver_->AddNativeCallback({name}, function); } -void EmbedderContext::SetSemanticsNodeCallback( +void EmbedderTestContext::SetSemanticsNodeCallback( SemanticsNodeCallback update_semantics_node_callback) { update_semantics_node_callback_ = update_semantics_node_callback; } -void EmbedderContext::SetSemanticsCustomActionCallback( +void EmbedderTestContext::SetSemanticsCustomActionCallback( SemanticsActionCallback update_semantics_custom_action_callback) { update_semantics_custom_action_callback_ = update_semantics_custom_action_callback; } -void EmbedderContext::SetPlatformMessageCallback( +void EmbedderTestContext::SetPlatformMessageCallback( std::function callback) { platform_message_callback_ = callback; } -void EmbedderContext::PlatformMessageCallback( +void EmbedderTestContext::PlatformMessageCallback( const FlutterPlatformMessage* message) { if (platform_message_callback_) { platform_message_callback_(message); @@ -104,9 +107,9 @@ void EmbedderContext::PlatformMessageCallback( } FlutterUpdateSemanticsNodeCallback -EmbedderContext::GetUpdateSemanticsNodeCallbackHook() { +EmbedderTestContext::GetUpdateSemanticsNodeCallbackHook() { return [](const FlutterSemanticsNode* semantics_node, void* user_data) { - auto context = reinterpret_cast(user_data); + auto context = reinterpret_cast(user_data); if (auto callback = context->update_semantics_node_callback_) { callback(semantics_node); } @@ -114,48 +117,88 @@ EmbedderContext::GetUpdateSemanticsNodeCallbackHook() { } FlutterUpdateSemanticsCustomActionCallback -EmbedderContext::GetUpdateSemanticsCustomActionCallbackHook() { +EmbedderTestContext::GetUpdateSemanticsCustomActionCallbackHook() { return [](const FlutterSemanticsCustomAction* action, void* user_data) { - auto context = reinterpret_cast(user_data); + auto context = reinterpret_cast(user_data); if (auto callback = context->update_semantics_custom_action_callback_) { callback(action); } }; } -void EmbedderContext::SetupOpenGLSurface() { - gl_surface_ = std::make_unique(); +void EmbedderTestContext::SetupOpenGLSurface() { + if (!gl_surface_) { + gl_surface_ = std::make_unique(); + } } -bool EmbedderContext::GLMakeCurrent() { +bool EmbedderTestContext::GLMakeCurrent() { FML_CHECK(gl_surface_) << "GL surface must be initialized."; return gl_surface_->MakeCurrent(); } -bool EmbedderContext::GLClearCurrent() { +bool EmbedderTestContext::GLClearCurrent() { FML_CHECK(gl_surface_) << "GL surface must be initialized."; return gl_surface_->ClearCurrent(); } -bool EmbedderContext::GLPresent() { +bool EmbedderTestContext::GLPresent() { FML_CHECK(gl_surface_) << "GL surface must be initialized."; - return gl_surface_->Present(); + + if (next_scene_callback_) { + auto raster_snapshot = gl_surface_->GetRasterSurfaceSnapshot(); + FML_CHECK(raster_snapshot); + auto callback = next_scene_callback_; + next_scene_callback_ = nullptr; + callback(std::move(raster_snapshot)); + } + + if (!gl_surface_->Present()) { + return false; + } + + return true; } -uint32_t EmbedderContext::GLGetFramebuffer() { +uint32_t EmbedderTestContext::GLGetFramebuffer() { FML_CHECK(gl_surface_) << "GL surface must be initialized."; return gl_surface_->GetFramebuffer(); } -bool EmbedderContext::GLMakeResourceCurrent() { +bool EmbedderTestContext::GLMakeResourceCurrent() { FML_CHECK(gl_surface_) << "GL surface must be initialized."; return gl_surface_->MakeResourceCurrent(); } -void* EmbedderContext::GLGetProcAddress(const char* name) { +void* EmbedderTestContext::GLGetProcAddress(const char* name) { FML_CHECK(gl_surface_) << "GL surface must be initialized."; return gl_surface_->GetProcAddress(name); } +void EmbedderTestContext::SetupCompositor() { + if (compositor_) { + return; + } + SetupOpenGLSurface(); + compositor_ = + std::make_unique(gl_surface_->GetGrContext()); +} + +EmbedderTestCompositor& EmbedderTestContext::GetCompositor() { + FML_CHECK(compositor_) + << "Accessed the compositor on a context where one was not setup. Used " + "the config builder to setup a context with a custom compositor."; + return *compositor_; +} + +void EmbedderTestContext::SetNextSceneCallback( + NextSceneCallback next_scene_callback) { + if (compositor_) { + compositor_->SetNextSceneCallback(next_scene_callback); + return; + } + next_scene_callback_ = next_scene_callback; +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/embedder/tests/embedder_context.h b/shell/platform/embedder/tests/embedder_test_context.h similarity index 83% rename from shell/platform/embedder/tests/embedder_context.h rename to shell/platform/embedder/tests/embedder_test_context.h index 02f617411a220..9d78a03c03c7f 100644 --- a/shell/platform/embedder/tests/embedder_context.h +++ b/shell/platform/embedder/tests/embedder_test_context.h @@ -14,8 +14,10 @@ #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" #include "flutter/shell/platform/embedder/embedder.h" +#include "flutter/shell/platform/embedder/tests/embedder_test_compositor.h" #include "flutter/testing/test_dart_native_resolver.h" #include "flutter/testing/test_gl_surface.h" +#include "third_party/skia/include/core/SkImage.h" namespace flutter { namespace testing { @@ -24,11 +26,11 @@ using SemanticsNodeCallback = std::function; using SemanticsActionCallback = std::function; -class EmbedderContext { +class EmbedderTestContext { public: - EmbedderContext(std::string assets_path = ""); + EmbedderTestContext(std::string assets_path = ""); - ~EmbedderContext(); + ~EmbedderTestContext(); const std::string& GetAssetsPath() const; @@ -52,6 +54,13 @@ class EmbedderContext { void SetPlatformMessageCallback( std::function callback); + void SetupCompositor(); + + EmbedderTestCompositor& GetCompositor(); + + using NextSceneCallback = std::function image)>; + void SetNextSceneCallback(NextSceneCallback next_scene_callback); + private: // This allows the builder to access the hooks. friend class EmbedderConfigBuilder; @@ -67,6 +76,8 @@ class EmbedderContext { SemanticsActionCallback update_semantics_custom_action_callback_; std::function platform_message_callback_; std::unique_ptr gl_surface_; + std::unique_ptr compositor_; + NextSceneCallback next_scene_callback_; static VoidCallback GetIsolateCreateCallbackHook(); @@ -96,7 +107,7 @@ class EmbedderContext { void PlatformMessageCallback(const FlutterPlatformMessage* message); - FML_DISALLOW_COPY_AND_ASSIGN(EmbedderContext); + FML_DISALLOW_COPY_AND_ASSIGN(EmbedderTestContext); }; } // namespace testing diff --git a/shell/platform/embedder/tests/embedder_unittests.cc b/shell/platform/embedder/tests/embedder_unittests.cc index 5ef8ed40240b6..6fdf7b978ab37 100644 --- a/shell/platform/embedder/tests/embedder_unittests.cc +++ b/shell/platform/embedder/tests/embedder_unittests.cc @@ -11,12 +11,15 @@ #include "flutter/fml/make_copyable.h" #include "flutter/fml/mapping.h" #include "flutter/fml/message_loop.h" +#include "flutter/fml/synchronization/count_down_latch.h" #include "flutter/fml/synchronization/waitable_event.h" #include "flutter/fml/thread.h" #include "flutter/runtime/dart_vm.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/testing/testing.h" +#include "third_party/skia/include/core/SkSurface.h" #include "third_party/tonic/converter/dart_converter.h" namespace flutter { @@ -25,7 +28,7 @@ namespace testing { using EmbedderTest = testing::EmbedderTest; TEST(EmbedderTestNoFixture, MustNotRunWithInvalidArgs) { - EmbedderContext context; + EmbedderTestContext context; EmbedderConfigBuilder builder( context, EmbedderConfigBuilder::InitializationPreference::kNoInitialize); auto engine = builder.LaunchEngine(); @@ -510,5 +513,561 @@ TEST_F(EmbedderTest, VMAndIsolateSnapshotSizesAreRedundantInAOTMode) { ASSERT_TRUE(engine.is_valid()); } +//------------------------------------------------------------------------------ +/// If an incorrectly configured compositor is set on the engine, the engine +/// must fail to launch instead of failing to render a frame at a later point in +/// time. +/// +TEST_F(EmbedderTest, + MustPreventEngineLaunchWhenRequiredCompositorArgsAreAbsent) { + auto& context = GetEmbedderContext(); + EmbedderConfigBuilder builder(context); + builder.SetCompositor(); + builder.GetCompositor().create_backing_store_callback = nullptr; + builder.GetCompositor().collect_backing_store_callback = nullptr; + builder.GetCompositor().present_layers_callback = nullptr; + auto engine = builder.LaunchEngine(); + ASSERT_FALSE(engine.is_valid()); +} + +//------------------------------------------------------------------------------ +/// Must be able to render to a custom compositor whose render targets are fully +/// complete OpenGL textures. +/// +TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLFramebuffer) { + auto& context = GetEmbedderContext(); + + context.SetupCompositor(); + + context.GetCompositor().SetRenderTargetType( + EmbedderTestCompositor::RenderTargetType::kOpenGLFramebuffer); + + fml::CountDownLatch latch(3); + context.GetCompositor().SetNextPresentCallback( + [&](const FlutterLayer** layers, size_t layers_count) { + ASSERT_EQ(layers_count, 3u); + + { + FlutterBackingStore backing_store = *layers[0]->backing_store; + backing_store.struct_size = sizeof(backing_store); + backing_store.type = kFlutterBackingStoreTypeOpenGL; + backing_store.did_update = true; + backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + layer.size = FlutterSizeMake(800.0, 600.0); + layer.offset = FlutterPointMake(0, 0); + + ASSERT_EQ(*layers[0], layer); + } + + { + FlutterPlatformView platform_view = {}; + platform_view.struct_size = sizeof(platform_view); + platform_view.identifier = 42; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypePlatformView; + layer.platform_view = &platform_view; + layer.size = FlutterSizeMake(123.0, 456.0); + layer.offset = FlutterPointMake(1.0, 2.0); + + ASSERT_EQ(*layers[1], layer); + } + + { + FlutterBackingStore backing_store = *layers[2]->backing_store; + backing_store.struct_size = sizeof(backing_store); + backing_store.type = kFlutterBackingStoreTypeOpenGL; + backing_store.did_update = true; + backing_store.open_gl.type = kFlutterOpenGLTargetTypeFramebuffer; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + layer.size = FlutterSizeMake(800.0, 600.0); + layer.offset = FlutterPointMake(0.0, 0.0); + + ASSERT_EQ(*layers[2], layer); + } + + latch.CountDown(); + }); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(); + builder.SetCompositor(); + builder.SetDartEntrypoint("can_composite_platform_views"); + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY( + [&latch](Dart_NativeArguments args) { latch.CountDown(); })); + + auto engine = builder.LaunchEngine(); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + ASSERT_TRUE(engine.is_valid()); + + latch.Wait(); +} + +//------------------------------------------------------------------------------ +/// Must be able to render using a custom compositor whose render targets for +/// the individual layers are OpenGL textures. +/// +TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToOpenGLTexture) { + auto& context = GetEmbedderContext(); + + context.SetupCompositor(); + + context.GetCompositor().SetRenderTargetType( + EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); + + fml::CountDownLatch latch(3); + context.GetCompositor().SetNextPresentCallback( + [&](const FlutterLayer** layers, size_t layers_count) { + ASSERT_EQ(layers_count, 3u); + + { + FlutterBackingStore backing_store = *layers[0]->backing_store; + backing_store.struct_size = sizeof(backing_store); + backing_store.type = kFlutterBackingStoreTypeOpenGL; + backing_store.did_update = true; + backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + layer.size = FlutterSizeMake(800.0, 600.0); + layer.offset = FlutterPointMake(0, 0); + + ASSERT_EQ(*layers[0], layer); + } + + { + FlutterPlatformView platform_view = {}; + platform_view.struct_size = sizeof(platform_view); + platform_view.identifier = 42; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypePlatformView; + layer.platform_view = &platform_view; + layer.size = FlutterSizeMake(123.0, 456.0); + layer.offset = FlutterPointMake(1.0, 2.0); + + ASSERT_EQ(*layers[1], layer); + } + + { + FlutterBackingStore backing_store = *layers[2]->backing_store; + backing_store.struct_size = sizeof(backing_store); + backing_store.type = kFlutterBackingStoreTypeOpenGL; + backing_store.did_update = true; + backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + layer.size = FlutterSizeMake(800.0, 600.0); + layer.offset = FlutterPointMake(0.0, 0.0); + + ASSERT_EQ(*layers[2], layer); + } + + latch.CountDown(); + }); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(); + builder.SetCompositor(); + builder.SetDartEntrypoint("can_composite_platform_views"); + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY( + [&latch](Dart_NativeArguments args) { latch.CountDown(); })); + + auto engine = builder.LaunchEngine(); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + ASSERT_TRUE(engine.is_valid()); + + latch.Wait(); +} + +//------------------------------------------------------------------------------ +/// Must be able to render using a custom compositor whose render target for the +/// individual layers are software buffers. +/// +TEST_F(EmbedderTest, CompositorMustBeAbleToRenderToSoftwareBuffer) { + auto& context = GetEmbedderContext(); + + context.SetupCompositor(); + + context.GetCompositor().SetRenderTargetType( + EmbedderTestCompositor::RenderTargetType::kSoftwareBuffer); + + fml::CountDownLatch latch(3); + context.GetCompositor().SetNextPresentCallback( + [&](const FlutterLayer** layers, size_t layers_count) { + ASSERT_EQ(layers_count, 3u); + + { + FlutterBackingStore backing_store = *layers[0]->backing_store; + backing_store.struct_size = sizeof(backing_store); + backing_store.type = kFlutterBackingStoreTypeSoftware; + backing_store.did_update = true; + ASSERT_FLOAT_EQ( + backing_store.software.row_bytes * backing_store.software.height, + 800 * 4 * 600.0); + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + layer.size = FlutterSizeMake(800.0, 600.0); + layer.offset = FlutterPointMake(0, 0); + + ASSERT_EQ(*layers[0], layer); + } + + { + FlutterPlatformView platform_view = {}; + platform_view.struct_size = sizeof(platform_view); + platform_view.identifier = 42; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypePlatformView; + layer.platform_view = &platform_view; + layer.size = FlutterSizeMake(123.0, 456.0); + layer.offset = FlutterPointMake(1.0, 2.0); + + ASSERT_EQ(*layers[1], layer); + } + + { + FlutterBackingStore backing_store = *layers[2]->backing_store; + backing_store.struct_size = sizeof(backing_store); + backing_store.type = kFlutterBackingStoreTypeSoftware; + backing_store.did_update = true; + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + layer.size = FlutterSizeMake(800.0, 600.0); + layer.offset = FlutterPointMake(0.0, 0.0); + + ASSERT_EQ(*layers[2], layer); + } + + latch.CountDown(); + }); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(); + builder.SetCompositor(); + builder.SetDartEntrypoint("can_composite_platform_views"); + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY( + [&latch](Dart_NativeArguments args) { latch.CountDown(); })); + + auto engine = builder.LaunchEngine(); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + ASSERT_TRUE(engine.is_valid()); + + latch.Wait(); +} + +static sk_sp CreateRenderSurface(const FlutterLayer& layer, + GrContext* context) { + const auto image_info = + SkImageInfo::MakeN32Premul(layer.size.width, layer.size.height); + auto surface = + SkSurface::MakeRenderTarget(context, // context + SkBudgeted::kNo, // budgeted + image_info, // image info + 1, // sample count + kTopLeft_GrSurfaceOrigin, // surface origin + nullptr, // surface properties + false // mipmaps + + ); + FML_CHECK(surface != nullptr); + return surface; +} + +static bool RasterImagesAreSame(sk_sp a, sk_sp b) { + FML_CHECK(!a->isTextureBacked()); + FML_CHECK(!b->isTextureBacked()); + + if (!a || !b) { + return false; + } + + SkPixmap pixmapA; + SkPixmap pixmapB; + + if (!a->peekPixels(&pixmapA)) { + FML_LOG(ERROR) << "Could not peek pixels of image A."; + return false; + } + + if (!b->peekPixels(&pixmapB)) { + FML_LOG(ERROR) << "Could not peek pixels of image B."; + + return false; + } + + const auto sizeA = pixmapA.rowBytes() * pixmapA.height(); + const auto sizeB = pixmapB.rowBytes() * pixmapB.height(); + + if (sizeA != sizeB) { + FML_LOG(ERROR) << "Pixmap sizes were inconsistent."; + return false; + } + + return ::memcmp(pixmapA.addr(), pixmapB.addr(), sizeA) == 0; +} + +bool WriteImageToDisk(const fml::UniqueFD& directory, + const std::string& name, + sk_sp image) { + if (!image) { + return false; + } + + auto data = image->encodeToData(SkEncodedImageFormat::kPNG, 100); + + if (!data) { + return false; + } + + fml::NonOwnedMapping mapping(static_cast(data->data()), + data->size()); + return WriteAtomically(directory, name.c_str(), mapping); +} + +//------------------------------------------------------------------------------ +/// Test the layer structure and pixels rendered when using a custom compositor. +/// +TEST_F(EmbedderTest, CompositorMustBeAbleToRenderKnownScene) { + auto& context = GetEmbedderContext(); + + context.SetupCompositor(); + + context.GetCompositor().SetRenderTargetType( + EmbedderTestCompositor::RenderTargetType::kOpenGLTexture); + + fml::CountDownLatch latch(6); + + sk_sp scene_image; + context.SetNextSceneCallback([&](sk_sp scene) { + scene_image = std::move(scene); + latch.CountDown(); + }); + + context.GetCompositor().SetNextPresentCallback( + [&](const FlutterLayer** layers, size_t layers_count) { + ASSERT_EQ(layers_count, 5u); + + // Layer Root + { + FlutterBackingStore backing_store = *layers[0]->backing_store; + backing_store.type = kFlutterBackingStoreTypeOpenGL; + backing_store.did_update = true; + backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + layer.size = FlutterSizeMake(800.0, 600.0); + layer.offset = FlutterPointMake(0.0, 0.0); + + ASSERT_EQ(*layers[0], layer); + } + + // Layer 1 + { + FlutterPlatformView platform_view = {}; + platform_view.struct_size = sizeof(platform_view); + platform_view.identifier = 1; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypePlatformView; + layer.platform_view = &platform_view; + layer.size = FlutterSizeMake(50.0, 150.0); + layer.offset = FlutterPointMake(20.0, 20.0); + + ASSERT_EQ(*layers[1], layer); + } + + // Layer 2 + { + FlutterBackingStore backing_store = *layers[2]->backing_store; + backing_store.type = kFlutterBackingStoreTypeOpenGL; + backing_store.did_update = true; + backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + layer.size = FlutterSizeMake(800.0, 600.0); + layer.offset = FlutterPointMake(0.0, 0.0); + + ASSERT_EQ(*layers[2], layer); + } + + // Layer 3 + { + FlutterPlatformView platform_view = {}; + platform_view.struct_size = sizeof(platform_view); + platform_view.identifier = 2; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypePlatformView; + layer.platform_view = &platform_view; + layer.size = FlutterSizeMake(50.0, 150.0); + layer.offset = FlutterPointMake(40.0, 40.0); + + ASSERT_EQ(*layers[3], layer); + } + + // Layer 4 + { + FlutterBackingStore backing_store = *layers[4]->backing_store; + backing_store.type = kFlutterBackingStoreTypeOpenGL; + backing_store.did_update = true; + backing_store.open_gl.type = kFlutterOpenGLTargetTypeTexture; + + FlutterLayer layer = {}; + layer.struct_size = sizeof(layer); + layer.type = kFlutterLayerContentTypeBackingStore; + layer.backing_store = &backing_store; + layer.size = FlutterSizeMake(800.0, 600.0); + layer.offset = FlutterPointMake(0.0, 0.0); + + ASSERT_EQ(*layers[4], layer); + } + + latch.CountDown(); + }); + + context.GetCompositor().SetPlatformViewRendererCallback( + [&](const FlutterLayer& layer, GrContext* context) -> sk_sp { + auto surface = CreateRenderSurface(layer, context); + auto canvas = surface->getCanvas(); + FML_CHECK(canvas != nullptr); + + switch (layer.platform_view->identifier) { + case 1: { + SkPaint paint; + // See dart test for total order. + paint.setColor(SK_ColorGREEN); + paint.setAlpha(127); + const auto& rect = + SkRect::MakeWH(layer.size.width, layer.size.height); + canvas->drawRect(rect, paint); + latch.CountDown(); + } break; + case 2: { + SkPaint paint; + // See dart test for total order. + paint.setColor(SK_ColorMAGENTA); + paint.setAlpha(127); + const auto& rect = + SkRect::MakeWH(layer.size.width, layer.size.height); + canvas->drawRect(rect, paint); + latch.CountDown(); + } break; + default: + // Asked to render an unknown platform view. + FML_CHECK(false) + << "Test was asked to composite an unknown platform view."; + } + + return surface->makeImageSnapshot(); + }); + + EmbedderConfigBuilder builder(context); + builder.SetOpenGLRendererConfig(); + builder.SetCompositor(); + builder.SetDartEntrypoint("can_composite_platform_views_with_known_scene"); + context.AddNativeCallback( + "SignalNativeTest", + CREATE_NATIVE_ENTRY( + [&latch](Dart_NativeArguments args) { latch.CountDown(); })); + + auto engine = builder.LaunchEngine(); + + // Send a window metrics events so frames may be scheduled. + FlutterWindowMetricsEvent event = {}; + event.struct_size = sizeof(event); + event.width = 800; + event.height = 600; + ASSERT_EQ(FlutterEngineSendWindowMetricsEvent(engine.get(), &event), + kSuccess); + ASSERT_TRUE(engine.is_valid()); + + latch.Wait(); + + // Render the scene and assert that it matches expectation. + ASSERT_TRUE(scene_image); + fml::FileMapping fixture_image_mapping(OpenFixture("compositor.png")); + ASSERT_GE(fixture_image_mapping.GetSize(), 0u); + auto encoded_image = SkData::MakeWithoutCopy( + fixture_image_mapping.GetMapping(), fixture_image_mapping.GetSize()); + auto fixture_image = + SkImage::MakeFromEncoded(std::move(encoded_image))->makeRasterImage(); + ASSERT_TRUE(fixture_image); + auto scene_image_subset = scene_image->makeSubset( + SkIRect::MakeWH(fixture_image->width(), fixture_image->height())); + ASSERT_TRUE(scene_image_subset); + const auto images_are_same = + RasterImagesAreSame(scene_image_subset, fixture_image); + if (!images_are_same) { + auto fixtures_fd = OpenFixturesDirectory(); + ASSERT_TRUE( + WriteImageToDisk(fixtures_fd, "actual.png", scene_image_subset)); + ASSERT_TRUE( + WriteImageToDisk(fixtures_fd, "expectation.png", fixture_image)); + FML_LOG(ERROR) << "Test compositor did not generated expected images. Got: " + "'actual.png' Expected: 'expectation.png' in Directory: " + << GetFixturesPath(); + } + ASSERT_TRUE(images_are_same); +} + } // namespace testing } // namespace flutter diff --git a/testing/BUILD.gn b/testing/BUILD.gn index 82107d7f4e075..b613afee0348c 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -6,6 +6,7 @@ source_set("testing_lib") { testonly = true sources = [ + "$flutter_root/testing/assertions.h", "$flutter_root/testing/testing.cc", "$flutter_root/testing/testing.h", ] @@ -58,12 +59,9 @@ if (current_toolchain == host_toolchain) { ] deps = [ - "//$flutter_root/fml", - "//third_party/swiftshader_flutter:swiftshader", - ] - - public_deps = [ + "$flutter_root/fml", "//third_party/skia", + "//third_party/swiftshader_flutter:swiftshader", ] } } diff --git a/testing/assertions.h b/testing/assertions.h new file mode 100644 index 0000000000000..a6e7b22fbc24e --- /dev/null +++ b/testing/assertions.h @@ -0,0 +1,21 @@ +// 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_ASSERTIONS_H_ +#define FLUTTER_TESTING_ASSERTIONS_H_ + +#include + +namespace flutter { +namespace testing { + +inline bool NumberNear(double a, double b) { + static const double epsilon = 1e-3; + return (a > (b - epsilon)) && (a < (b + epsilon)); +} + +} // namespace testing +} // namespace flutter + +#endif // FLUTTER_TESTING_ASSERTIONS_H_ diff --git a/testing/test_gl_surface.cc b/testing/test_gl_surface.cc index 44a92badbc51a..e856ba452e429 100644 --- a/testing/test_gl_surface.cc +++ b/testing/test_gl_surface.cc @@ -11,7 +11,9 @@ #include #include "flutter/fml/logging.h" +#include "third_party/skia/include/core/SkSurface.h" #include "third_party/skia/include/gpu/gl/GrGLAssembleInterface.h" +#include "third_party/skia/src/gpu/gl/GrGLDefines.h" namespace flutter { namespace testing { @@ -77,6 +79,9 @@ static std::string GetEGLError() { return stream.str(); } +constexpr size_t kTestGLSurfaceWidth = 800; +constexpr size_t kTestGLSurfaceHeight = 600; + TestGLSurface::TestGLSurface() { display_ = ::eglGetDisplay(EGL_DEFAULT_DISPLAY); FML_CHECK(display_ != EGL_NO_DISPLAY); @@ -109,8 +114,8 @@ TestGLSurface::TestGLSurface() { { const EGLint surface_attributes[] = { - EGL_HEIGHT, 1, // - EGL_WIDTH, 1, // + EGL_WIDTH, kTestGLSurfaceWidth, // + EGL_HEIGHT, kTestGLSurfaceHeight, // EGL_NONE, }; @@ -155,6 +160,8 @@ TestGLSurface::TestGLSurface() { } TestGLSurface::~TestGLSurface() { + context_ = nullptr; + auto result = ::eglDestroyContext(display_, onscreen_context_); FML_CHECK(result == EGL_TRUE) << GetEGLError(); @@ -171,6 +178,10 @@ TestGLSurface::~TestGLSurface() { FML_CHECK(result == EGL_TRUE); } +SkISize TestGLSurface::GetSize() const { + return SkISize::Make(kTestGLSurfaceWidth, kTestGLSurfaceHeight); +} + bool TestGLSurface::MakeCurrent() { auto result = ::eglMakeCurrent(display_, onscreen_surface_, onscreen_surface_, onscreen_context_); @@ -231,7 +242,17 @@ void* TestGLSurface::GetProcAddress(const char* name) const { return reinterpret_cast(symbol); } -sk_sp TestGLSurface::CreateContext() { +sk_sp TestGLSurface::GetGrContext() { + if (context_) { + return context_; + } + + context_ = CreateGrContext(); + + return context_; +} + +sk_sp TestGLSurface::CreateGrContext() { if (!MakeCurrent()) { return nullptr; } @@ -263,7 +284,74 @@ sk_sp TestGLSurface::CreateContext() { return nullptr; } - return GrContext::MakeGL(interface); + context_ = GrContext::MakeGL(interface); + return context_; +} + +sk_sp TestGLSurface::GetOnscreenSurface() { + GrGLFramebufferInfo framebuffer_info = {}; + framebuffer_info.fFBOID = GetFramebuffer(); + framebuffer_info.fFormat = GR_GL_RGBA8; + + const auto size = GetSize(); + + GrBackendRenderTarget backend_render_target( + size.width(), // width + size.height(), // height + 1, // sample count + 8, // stencil bits + framebuffer_info // framebuffer info + ); + + SkSurfaceProps surface_properties( + SkSurfaceProps::InitType::kLegacyFontHost_InitType); + + auto surface = SkSurface::MakeFromBackendRenderTarget( + GetGrContext().get(), // context + backend_render_target, // backend render target + kBottomLeft_GrSurfaceOrigin, // surface origin + kN32_SkColorType, // color type + SkColorSpace::MakeSRGB(), // color space + &surface_properties, // surface properties + nullptr, // release proc + nullptr // release context + ); + + if (!surface) { + FML_LOG(ERROR) << "Could not wrap the surface while attempting to " + "snapshot the GL surface."; + return nullptr; + } + + return surface; +} + +sk_sp TestGLSurface::GetRasterSurfaceSnapshot() { + auto surface = GetOnscreenSurface(); + + if (!surface) { + FML_LOG(ERROR) << "Aborting snapshot because of on-screen surface " + "acquisition failure."; + return nullptr; + } + + auto device_snapshot = surface->makeImageSnapshot(); + + if (!device_snapshot) { + FML_LOG(ERROR) << "Could not create the device snapshot while attempting " + "to snapshot the GL 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 GL surface."; + return nullptr; + } + + return host_snapshot; } } // namespace testing diff --git a/testing/test_gl_surface.h b/testing/test_gl_surface.h index 0351552660af3..6ba27194cf2a3 100644 --- a/testing/test_gl_surface.h +++ b/testing/test_gl_surface.h @@ -19,6 +19,8 @@ class TestGLSurface { ~TestGLSurface(); + SkISize GetSize() const; + bool MakeCurrent(); bool ClearCurrent(); @@ -31,7 +33,13 @@ class TestGLSurface { void* GetProcAddress(const char* name) const; - sk_sp CreateContext(); + sk_sp GetOnscreenSurface(); + + sk_sp GetGrContext(); + + sk_sp CreateGrContext(); + + sk_sp GetRasterSurfaceSnapshot(); private: // Importing the EGL.h pulls in platform headers which are problematic @@ -47,6 +55,7 @@ class TestGLSurface { EGLContext offscreen_context_; EGLSurface onscreen_surface_; EGLSurface offscreen_surface_; + sk_sp context_; FML_DISALLOW_COPY_AND_ASSIGN(TestGLSurface); }; diff --git a/testing/testing.cc b/testing/testing.cc index 270d6f00b7e93..ff50552859df2 100644 --- a/testing/testing.cc +++ b/testing/testing.cc @@ -13,12 +13,7 @@ std::string GetCurrentTestName() { return ::testing::UnitTest::GetInstance()->current_test_info()->name(); } -fml::UniqueFD OpenFixture(std::string fixture_name) { - if (fixture_name.size() == 0) { - FML_LOG(ERROR) << "Invalid fixture name."; - return {}; - } - +fml::UniqueFD OpenFixturesDirectory() { auto fixtures_directory = OpenDirectory(GetFixturesPath(), // path false, // create @@ -29,6 +24,16 @@ fml::UniqueFD OpenFixture(std::string fixture_name) { FML_LOG(ERROR) << "Could not open fixtures directory."; return {}; } + return fixtures_directory; +} + +fml::UniqueFD OpenFixture(std::string fixture_name) { + if (fixture_name.size() == 0) { + FML_LOG(ERROR) << "Invalid fixture name."; + return {}; + } + + auto fixtures_directory = OpenFixturesDirectory(); auto fixture_fd = fml::OpenFile(fixtures_directory, // base directory fixture_name.c_str(), // path diff --git a/testing/testing.h b/testing/testing.h index fc4fe6a4d8e66..9f0fba0349247 100644 --- a/testing/testing.h +++ b/testing/testing.h @@ -8,6 +8,7 @@ #include #include "flutter/fml/file.h" +#include "flutter/testing/assertions.h" #include "gtest/gtest.h" namespace flutter { @@ -18,6 +19,8 @@ namespace testing { // error. const char* GetFixturesPath(); +fml::UniqueFD OpenFixturesDirectory(); + fml::UniqueFD OpenFixture(std::string fixture_name); std::string GetCurrentTestName();