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 0000000000000..a03c3cd4ff5a5 Binary files /dev/null and b/shell/platform/embedder/fixtures/compositor.png differ 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();