From b990ad11bde5e0844e478bc9a2b94c275cbbce58 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Sat, 20 Jun 2020 12:51:55 -0700 Subject: [PATCH] Implement external view embedder on Android (#19033) The external view embedder allows to embed Android views in a Flutter app. --- BUILD.gn | 3 +- ci/licenses_golden/licenses_flutter | 19 +- flow/embedded_views.h | 8 +- fml/raster_thread_merger.cc | 11 +- fml/raster_thread_merger.h | 5 +- fml/raster_thread_merger_unittests.cc | 6 + shell/common/rasterizer.cc | 6 +- .../shell_test_external_view_embedder.cc | 8 +- .../shell_test_external_view_embedder.h | 8 +- shell/platform/android/BUILD.gn | 9 +- shell/platform/android/android_context.cc | 37 -- shell/platform/android/android_context_gl.h | 4 +- .../platform/android/android_shell_holder.cc | 1 + shell/platform/android/android_surface.cc | 41 --- shell/platform/android/android_surface_gl.cc | 15 +- shell/platform/android/android_surface_gl.h | 10 +- .../android/android_surface_software.cc | 10 +- .../android/android_surface_software.h | 9 +- .../android/android_surface_vulkan.cc | 13 +- .../platform/android/android_surface_vulkan.h | 10 +- shell/platform/android/context/BUILD.gn | 18 + .../android/context/android_context.cc | 22 ++ .../android/{ => context}/android_context.h | 8 +- .../android/external_view_embedder/BUILD.gn | 13 +- .../external_view_embedder.cc | 182 +++++++++- .../external_view_embedder.h | 70 +++- .../external_view_embedder_unittests.cc | 333 ++++++++++++++++-- .../external_view_embedder/surface_pool.cc | 81 +++++ .../external_view_embedder/surface_pool.h | 86 +++++ .../surface_pool_unittests.cc | 168 +++++++++ shell/platform/android/jni/BUILD.gn | 36 ++ shell/platform/android/jni/jni_mock.h | 91 +++++ .../platform/android/jni/jni_mock_unittest.cc | 25 ++ .../android/jni/platform_view_android_jni.h | 31 +- .../platform/android/platform_view_android.cc | 47 ++- .../platform/android/platform_view_android.h | 4 +- .../android/platform_view_android_jni_impl.cc | 8 +- .../android/platform_view_android_jni_impl.h | 3 +- shell/platform/android/surface/BUILD.gn | 54 +++ .../{ => surface}/android_native_window.cc | 8 +- .../{ => surface}/android_native_window.h | 10 +- .../android/surface/android_surface.cc | 11 + .../android/{ => surface}/android_surface.h | 13 +- .../android/surface/android_surface_mock.cc | 29 ++ .../android/surface/android_surface_mock.h | 59 ++++ shell/platform/darwin/ios/ios_surface.h | 6 +- shell/platform/darwin/ios/ios_surface.mm | 5 +- .../embedder_external_view_embedder.cc | 8 +- .../embedder_external_view_embedder.h | 8 +- testing/BUILD.gn | 1 + testing/run_tests.py | 7 +- 51 files changed, 1443 insertions(+), 235 deletions(-) delete mode 100644 shell/platform/android/android_context.cc delete mode 100644 shell/platform/android/android_surface.cc create mode 100644 shell/platform/android/context/BUILD.gn create mode 100644 shell/platform/android/context/android_context.cc rename shell/platform/android/{ => context}/android_context.h (80%) create mode 100644 shell/platform/android/external_view_embedder/surface_pool.cc create mode 100644 shell/platform/android/external_view_embedder/surface_pool.h create mode 100644 shell/platform/android/external_view_embedder/surface_pool_unittests.cc create mode 100644 shell/platform/android/jni/jni_mock.h create mode 100644 shell/platform/android/jni/jni_mock_unittest.cc create mode 100644 shell/platform/android/surface/BUILD.gn rename shell/platform/android/{ => surface}/android_native_window.cc (80%) rename shell/platform/android/{ => surface}/android_native_window.h (87%) create mode 100644 shell/platform/android/surface/android_surface.cc rename shell/platform/android/{ => surface}/android_surface.h (73%) create mode 100644 shell/platform/android/surface/android_surface_mock.cc create mode 100644 shell/platform/android/surface/android_surface_mock.h diff --git a/BUILD.gn b/BUILD.gn index 19dce340cd183..fe269de24cfae 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -74,7 +74,6 @@ group("flutter") { "//flutter/lib/ui:ui_unittests", "//flutter/runtime:runtime_unittests", "//flutter/shell/common:shell_unittests", - "//flutter/shell/platform/android/external_view_embedder:android_external_view_embedder_unittests", "//flutter/shell/platform/embedder:embedder_unittests", "//flutter/testing:testing_unittests", "//flutter/third_party/txt:txt_unittests", @@ -85,6 +84,8 @@ group("flutter") { "//flutter/fml:fml_benchmarks", "//flutter/lib/ui:ui_benchmarks", "//flutter/shell/common:shell_benchmarks", + "//flutter/shell/platform/android/external_view_embedder:android_external_view_embedder_unittests", + "//flutter/shell/platform/android/jni:jni_unittests", "//flutter/third_party/txt:txt_benchmarks", ] } diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index eb2713c557b90..242783e947b27 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -645,8 +645,6 @@ FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan.h FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan_delegate.cc FILE: ../../../flutter/shell/gpu/gpu_surface_vulkan_delegate.h FILE: ../../../flutter/shell/platform/android/AndroidManifest.xml -FILE: ../../../flutter/shell/platform/android/android_context.cc -FILE: ../../../flutter/shell/platform/android/android_context.h FILE: ../../../flutter/shell/platform/android/android_context_gl.cc FILE: ../../../flutter/shell/platform/android/android_context_gl.h FILE: ../../../flutter/shell/platform/android/android_environment_gl.cc @@ -654,12 +652,8 @@ FILE: ../../../flutter/shell/platform/android/android_environment_gl.h FILE: ../../../flutter/shell/platform/android/android_exports.lst FILE: ../../../flutter/shell/platform/android/android_external_texture_gl.cc FILE: ../../../flutter/shell/platform/android/android_external_texture_gl.h -FILE: ../../../flutter/shell/platform/android/android_native_window.cc -FILE: ../../../flutter/shell/platform/android/android_native_window.h FILE: ../../../flutter/shell/platform/android/android_shell_holder.cc FILE: ../../../flutter/shell/platform/android/android_shell_holder.h -FILE: ../../../flutter/shell/platform/android/android_surface.cc -FILE: ../../../flutter/shell/platform/android/android_surface.h FILE: ../../../flutter/shell/platform/android/android_surface_gl.cc FILE: ../../../flutter/shell/platform/android/android_surface_gl.h FILE: ../../../flutter/shell/platform/android/android_surface_software.cc @@ -668,9 +662,14 @@ FILE: ../../../flutter/shell/platform/android/android_surface_vulkan.cc FILE: ../../../flutter/shell/platform/android/android_surface_vulkan.h FILE: ../../../flutter/shell/platform/android/apk_asset_provider.cc FILE: ../../../flutter/shell/platform/android/apk_asset_provider.h +FILE: ../../../flutter/shell/platform/android/context/android_context.cc +FILE: ../../../flutter/shell/platform/android/context/android_context.h FILE: ../../../flutter/shell/platform/android/external_view_embedder/external_view_embedder.cc FILE: ../../../flutter/shell/platform/android/external_view_embedder/external_view_embedder.h FILE: ../../../flutter/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc +FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_pool.cc +FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_pool.h +FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_pool_unittests.cc FILE: ../../../flutter/shell/platform/android/flutter_main.cc FILE: ../../../flutter/shell/platform/android/flutter_main.h FILE: ../../../flutter/shell/platform/android/io/flutter/Log.java @@ -786,6 +785,8 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterRunArgument FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterView.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/TextureRegistry.java FILE: ../../../flutter/shell/platform/android/io/flutter/view/VsyncWaiter.java +FILE: ../../../flutter/shell/platform/android/jni/jni_mock.h +FILE: ../../../flutter/shell/platform/android/jni/jni_mock_unittest.cc FILE: ../../../flutter/shell/platform/android/jni/platform_view_android_jni.cc FILE: ../../../flutter/shell/platform/android/jni/platform_view_android_jni.h FILE: ../../../flutter/shell/platform/android/library_loader.cc @@ -796,6 +797,12 @@ FILE: ../../../flutter/shell/platform/android/platform_view_android.h FILE: ../../../flutter/shell/platform/android/platform_view_android_jni_impl.cc FILE: ../../../flutter/shell/platform/android/platform_view_android_jni_impl.h FILE: ../../../flutter/shell/platform/android/robolectric.properties +FILE: ../../../flutter/shell/platform/android/surface/android_native_window.cc +FILE: ../../../flutter/shell/platform/android/surface/android_native_window.h +FILE: ../../../flutter/shell/platform/android/surface/android_surface.cc +FILE: ../../../flutter/shell/platform/android/surface/android_surface.h +FILE: ../../../flutter/shell/platform/android/surface/android_surface_mock.cc +FILE: ../../../flutter/shell/platform/android/surface/android_surface_mock.h FILE: ../../../flutter/shell/platform/android/vsync_waiter_android.cc FILE: ../../../flutter/shell/platform/android/vsync_waiter_android.h FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/basic_message_channel_unittests.cc diff --git a/flow/embedded_views.h b/flow/embedded_views.h index 8bbef5a0a3f4d..9cc90860101a8 100644 --- a/flow/embedded_views.h +++ b/flow/embedded_views.h @@ -227,9 +227,11 @@ class ExternalViewEmbedder { // sets the stage for the next pre-roll. virtual void CancelFrame() = 0; - virtual void BeginFrame(SkISize frame_size, - GrContext* context, - double device_pixel_ratio) = 0; + virtual void BeginFrame( + SkISize frame_size, + GrContext* context, + double device_pixel_ratio, + fml::RefPtr raster_thread_merger) = 0; virtual void PrerollCompositeEmbeddedView( int view_id, diff --git a/fml/raster_thread_merger.cc b/fml/raster_thread_merger.cc index 718b41ce327d3..b424c04b2754e 100644 --- a/fml/raster_thread_merger.cc +++ b/fml/raster_thread_merger.cc @@ -28,12 +28,15 @@ void RasterThreadMerger::MergeWithLease(size_t lease_term) { } } -bool RasterThreadMerger::IsOnRasterizingThread() { - const auto current_queue_id = MessageLoop::GetCurrentTaskQueueId(); +bool RasterThreadMerger::IsOnPlatformThread() const { + return MessageLoop::GetCurrentTaskQueueId() == platform_queue_id_; +} + +bool RasterThreadMerger::IsOnRasterizingThread() const { if (is_merged_) { - return current_queue_id == platform_queue_id_; + return IsOnPlatformThread(); } else { - return current_queue_id == gpu_queue_id_; + return !IsOnPlatformThread(); } } diff --git a/fml/raster_thread_merger.h b/fml/raster_thread_merger.h index 1e12e3c41d9b8..7c0318ff77d26 100644 --- a/fml/raster_thread_merger.h +++ b/fml/raster_thread_merger.h @@ -44,7 +44,10 @@ class RasterThreadMerger // Returns true if the current thread owns rasterizing. // When the threads are merged, platform thread owns rasterizing. // When un-merged, raster thread owns rasterizing. - bool IsOnRasterizingThread(); + bool IsOnRasterizingThread() const; + + // Returns true if the current thread is the platform thread. + bool IsOnPlatformThread() const; private: static const int kLeaseNotSet; diff --git a/fml/raster_thread_merger_unittests.cc b/fml/raster_thread_merger_unittests.cc index 40ae9ba5351e2..a182723a79191 100644 --- a/fml/raster_thread_merger_unittests.cc +++ b/fml/raster_thread_merger_unittests.cc @@ -92,12 +92,14 @@ TEST(RasterThreadMerger, IsNotOnRasterizingThread) { loop1->GetTaskRunner()->PostTask([&]() { ASSERT_FALSE(raster_thread_merger_->IsOnRasterizingThread()); + ASSERT_TRUE(raster_thread_merger_->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1); pre_merge.CountDown(); }); loop2->GetTaskRunner()->PostTask([&]() { ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); + ASSERT_FALSE(raster_thread_merger_->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid2); pre_merge.CountDown(); }); @@ -108,6 +110,7 @@ TEST(RasterThreadMerger, IsNotOnRasterizingThread) { loop1->GetTaskRunner()->PostTask([&]() { ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); + ASSERT_TRUE(raster_thread_merger_->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1); post_merge.CountDown(); }); @@ -116,6 +119,7 @@ TEST(RasterThreadMerger, IsNotOnRasterizingThread) { // this will be false since this is going to be run // on loop1 really. ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); + ASSERT_TRUE(raster_thread_merger_->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1); post_merge.CountDown(); }); @@ -126,12 +130,14 @@ TEST(RasterThreadMerger, IsNotOnRasterizingThread) { loop1->GetTaskRunner()->PostTask([&]() { ASSERT_FALSE(raster_thread_merger_->IsOnRasterizingThread()); + ASSERT_TRUE(raster_thread_merger_->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid1); post_unmerge.CountDown(); }); loop2->GetTaskRunner()->PostTask([&]() { ASSERT_TRUE(raster_thread_merger_->IsOnRasterizingThread()); + ASSERT_FALSE(raster_thread_merger_->IsOnPlatformThread()); ASSERT_EQ(fml::MessageLoop::GetCurrentTaskQueueId(), qid2); post_unmerge.CountDown(); }); diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 076c42937076f..afcce2960a6a6 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -391,9 +391,9 @@ RasterStatus Rasterizer::DrawToSurface(flutter::LayerTree& layer_tree) { SkCanvas* embedder_root_canvas = nullptr; if (external_view_embedder != nullptr) { - external_view_embedder->BeginFrame(layer_tree.frame_size(), - surface_->GetContext(), - layer_tree.device_pixel_ratio()); + external_view_embedder->BeginFrame( + layer_tree.frame_size(), surface_->GetContext(), + layer_tree.device_pixel_ratio(), raster_thread_merger_); embedder_root_canvas = external_view_embedder->GetRootCanvas(); } diff --git a/shell/common/shell_test_external_view_embedder.cc b/shell/common/shell_test_external_view_embedder.cc index 3930ab7b61717..306c1b0016b44 100644 --- a/shell/common/shell_test_external_view_embedder.cc +++ b/shell/common/shell_test_external_view_embedder.cc @@ -6,9 +6,11 @@ namespace flutter { void ShellTestExternalViewEmbedder::CancelFrame() {} // |ExternalViewEmbedder| -void ShellTestExternalViewEmbedder::BeginFrame(SkISize frame_size, - GrContext* context, - double device_pixel_ratio) {} +void ShellTestExternalViewEmbedder::BeginFrame( + SkISize frame_size, + GrContext* context, + double device_pixel_ratio, + fml::RefPtr raster_thread_merger) {} // |ExternalViewEmbedder| void ShellTestExternalViewEmbedder::PrerollCompositeEmbeddedView( diff --git a/shell/common/shell_test_external_view_embedder.h b/shell/common/shell_test_external_view_embedder.h index 765ae5c8334e7..da96503a9bb46 100644 --- a/shell/common/shell_test_external_view_embedder.h +++ b/shell/common/shell_test_external_view_embedder.h @@ -29,9 +29,11 @@ class ShellTestExternalViewEmbedder final : public ExternalViewEmbedder { void CancelFrame() override; // |ExternalViewEmbedder| - void BeginFrame(SkISize frame_size, - GrContext* context, - double device_pixel_ratio) override; + void BeginFrame( + SkISize frame_size, + GrContext* context, + double device_pixel_ratio, + fml::RefPtr raster_thread_merger) override; // |ExternalViewEmbedder| void PrerollCompositeEmbeddedView( diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index fc33b1ce4b474..9680f061afdbc 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -23,20 +23,14 @@ shared_library("flutter_shell_native") { sources = [ "$root_build_dir/flutter_icu/icudtl.o", - "android_context.cc", - "android_context.h", "android_context_gl.cc", "android_context_gl.h", "android_environment_gl.cc", "android_environment_gl.h", "android_external_texture_gl.cc", "android_external_texture_gl.h", - "android_native_window.cc", - "android_native_window.h", "android_shell_holder.cc", "android_shell_holder.h", - "android_surface.cc", - "android_surface.h", "android_surface_gl.cc", "android_surface_gl.h", "android_surface_software.cc", @@ -67,8 +61,11 @@ shared_library("flutter_shell_native") { "//flutter/runtime", "//flutter/runtime:libdart", "//flutter/shell/common", + "//flutter/shell/platform/android/context", "//flutter/shell/platform/android/external_view_embedder", "//flutter/shell/platform/android/jni", + "//flutter/shell/platform/android/surface", + "//flutter/shell/platform/android/surface:native_window", "//third_party/skia", ] diff --git a/shell/platform/android/android_context.cc b/shell/platform/android/android_context.cc deleted file mode 100644 index c59f56186d900..0000000000000 --- a/shell/platform/android/android_context.cc +++ /dev/null @@ -1,37 +0,0 @@ -// 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/android/android_context.h" - -#include "flutter/shell/platform/android/android_context_gl.h" -#include "flutter/shell/platform/android/android_environment_gl.h" - -namespace flutter { - -std::shared_ptr CreateContextGL() { - auto context_gl = std::make_shared( - AndroidRenderingAPI::kOpenGLES, - fml::MakeRefCounted()); - FML_CHECK(context_gl->IsValid()) << "Could not create an Android context GL."; - return context_gl; -} - -AndroidContext::AndroidContext(AndroidRenderingAPI rendering_api) - : rendering_api_(rendering_api) {} - -AndroidContext::~AndroidContext() = default; - -AndroidRenderingAPI AndroidContext::RenderingApi() const { - return rendering_api_; -} - -std::shared_ptr AndroidContext::Create( - AndroidRenderingAPI rendering_api) { - if (rendering_api == AndroidRenderingAPI::kOpenGLES) { - return CreateContextGL(); - } - return std::make_shared(rendering_api); -} - -} // namespace flutter diff --git a/shell/platform/android/android_context_gl.h b/shell/platform/android/android_context_gl.h index 6b68d295a0835..1b7b5f411a8d3 100644 --- a/shell/platform/android/android_context_gl.h +++ b/shell/platform/android/android_context_gl.h @@ -9,9 +9,9 @@ #include "flutter/fml/memory/ref_counted.h" #include "flutter/fml/memory/ref_ptr.h" #include "flutter/shell/common/platform_view.h" -#include "flutter/shell/platform/android/android_context.h" #include "flutter/shell/platform/android/android_environment_gl.h" -#include "flutter/shell/platform/android/android_native_window.h" +#include "flutter/shell/platform/android/context/android_context.h" +#include "flutter/shell/platform/android/surface/android_native_window.h" #include "third_party/skia/include/core/SkSize.h" namespace flutter { diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index e99c76e961ed1..62f8acf6fc8fd 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -16,6 +16,7 @@ #include "flutter/fml/make_copyable.h" #include "flutter/fml/message_loop.h" +#include "flutter/fml/platform/android/jni_util.h" #include "flutter/shell/common/rasterizer.h" #include "flutter/shell/platform/android/platform_view_android.h" diff --git a/shell/platform/android/android_surface.cc b/shell/platform/android/android_surface.cc deleted file mode 100644 index e50f79180b78b..0000000000000 --- a/shell/platform/android/android_surface.cc +++ /dev/null @@ -1,41 +0,0 @@ -// 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/android/android_surface.h" - -#include - -#include "flutter/shell/platform/android/android_surface_gl.h" -#include "flutter/shell/platform/android/android_surface_software.h" -#if SHELL_ENABLE_VULKAN -#include "flutter/shell/platform/android/android_surface_vulkan.h" -#endif // SHELL_ENABLE_VULKAN - -namespace flutter { - -std::unique_ptr AndroidSurface::Create( - std::shared_ptr android_context, - std::shared_ptr jni_facade) { - std::unique_ptr surface; - switch (android_context->RenderingApi()) { - case AndroidRenderingAPI::kSoftware: - surface = std::make_unique(jni_facade); - break; - case AndroidRenderingAPI::kOpenGLES: - surface = std::make_unique(android_context, jni_facade); - break; - case AndroidRenderingAPI::kVulkan: -#if SHELL_ENABLE_VULKAN - surface = std::make_unique(jni_facade); -#endif // SHELL_ENABLE_VULKAN - break; - } - FML_CHECK(surface); - return surface->IsValid() ? std::move(surface) : nullptr; - ; -} - -AndroidSurface::~AndroidSurface() = default; - -} // namespace flutter diff --git a/shell/platform/android/android_surface_gl.cc b/shell/platform/android/android_surface_gl.cc index 8b91867dd3f0e..f4bd9e6747907 100644 --- a/shell/platform/android/android_surface_gl.cc +++ b/shell/platform/android/android_surface_gl.cc @@ -13,19 +13,22 @@ namespace flutter { AndroidSurfaceGL::AndroidSurfaceGL( std::shared_ptr android_context, - std::shared_ptr jni_facade) - : native_window_(nullptr), + std::shared_ptr jni_facade, + const AndroidSurface::Factory& surface_factory) + : external_view_embedder_( + std::make_unique(android_context, + jni_facade, + surface_factory)), + android_context_( + std::static_pointer_cast(android_context)), + native_window_(nullptr), onscreen_surface_(nullptr), offscreen_surface_(nullptr) { - android_context_ = - std::static_pointer_cast(android_context); // Acquire the offscreen surface. offscreen_surface_ = android_context_->CreateOffscreenSurface(); if (!offscreen_surface_->IsValid()) { offscreen_surface_ = nullptr; } - external_view_embedder_ = - std::make_unique(jni_facade); } AndroidSurfaceGL::~AndroidSurfaceGL() = default; diff --git a/shell/platform/android/android_surface_gl.h b/shell/platform/android/android_surface_gl.h index faf30e0b8afc8..7c682171d12c4 100644 --- a/shell/platform/android/android_surface_gl.h +++ b/shell/platform/android/android_surface_gl.h @@ -12,9 +12,9 @@ #include "flutter/shell/gpu/gpu_surface_gl.h" #include "flutter/shell/platform/android/android_context_gl.h" #include "flutter/shell/platform/android/android_environment_gl.h" -#include "flutter/shell/platform/android/android_surface.h" #include "flutter/shell/platform/android/external_view_embedder/external_view_embedder.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" +#include "flutter/shell/platform/android/surface/android_surface.h" namespace flutter { @@ -22,7 +22,8 @@ class AndroidSurfaceGL final : public GPUSurfaceGLDelegate, public AndroidSurface { public: AndroidSurfaceGL(std::shared_ptr android_context, - std::shared_ptr jni_facade); + std::shared_ptr jni_facade, + const AndroidSurface::Factory& surface_factory); ~AndroidSurfaceGL() override; @@ -63,9 +64,10 @@ class AndroidSurfaceGL final : public GPUSurfaceGLDelegate, ExternalViewEmbedder* GetExternalViewEmbedder() override; private: + const std::unique_ptr external_view_embedder_; + const std::shared_ptr android_context_; + fml::RefPtr native_window_; - std::unique_ptr external_view_embedder_; - std::shared_ptr android_context_; std::unique_ptr onscreen_surface_; std::unique_ptr offscreen_surface_; diff --git a/shell/platform/android/android_surface_software.cc b/shell/platform/android/android_surface_software.cc index 56d9e3bc542fd..7face67038d0a 100644 --- a/shell/platform/android/android_surface_software.cc +++ b/shell/platform/android/android_surface_software.cc @@ -37,11 +37,15 @@ bool GetSkColorType(int32_t buffer_format, } // anonymous namespace AndroidSurfaceSoftware::AndroidSurfaceSoftware( - std::shared_ptr jni_facade) { + std::shared_ptr android_context, + std::shared_ptr jni_facade, + AndroidSurface::Factory surface_factory) + : external_view_embedder_( + std::make_unique(android_context, + jni_facade, + surface_factory)) { GetSkColorType(WINDOW_FORMAT_RGBA_8888, &target_color_type_, &target_alpha_type_); - external_view_embedder_ = - std::make_unique(jni_facade); } AndroidSurfaceSoftware::~AndroidSurfaceSoftware() = default; diff --git a/shell/platform/android/android_surface_software.h b/shell/platform/android/android_surface_software.h index 00a4b245af7d2..1f5fb885dbf88 100644 --- a/shell/platform/android/android_surface_software.h +++ b/shell/platform/android/android_surface_software.h @@ -9,16 +9,18 @@ #include "flutter/fml/platform/android/jni_weak_ref.h" #include "flutter/fml/platform/android/scoped_java_ref.h" #include "flutter/shell/gpu/gpu_surface_software.h" -#include "flutter/shell/platform/android/android_surface.h" #include "flutter/shell/platform/android/external_view_embedder/external_view_embedder.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" +#include "flutter/shell/platform/android/surface/android_surface.h" namespace flutter { class AndroidSurfaceSoftware final : public AndroidSurface, public GPUSurfaceSoftwareDelegate { public: - AndroidSurfaceSoftware(std::shared_ptr jni_facade); + AndroidSurfaceSoftware(std::shared_ptr android_context, + std::shared_ptr jni_facade, + AndroidSurface::Factory surface_factory); ~AndroidSurfaceSoftware() override; @@ -53,11 +55,12 @@ class AndroidSurfaceSoftware final : public AndroidSurface, ExternalViewEmbedder* GetExternalViewEmbedder() override; private: + const std::unique_ptr external_view_embedder_; + sk_sp sk_surface_; fml::RefPtr native_window_; SkColorType target_color_type_; SkAlphaType target_alpha_type_; - std::unique_ptr external_view_embedder_; FML_DISALLOW_COPY_AND_ASSIGN(AndroidSurfaceSoftware); }; diff --git a/shell/platform/android/android_surface_vulkan.cc b/shell/platform/android/android_surface_vulkan.cc index 3d802a1f1ba42..4c428e931637c 100644 --- a/shell/platform/android/android_surface_vulkan.cc +++ b/shell/platform/android/android_surface_vulkan.cc @@ -13,11 +13,14 @@ namespace flutter { AndroidSurfaceVulkan::AndroidSurfaceVulkan( - std::shared_ptr jni_facade) - : proc_table_(fml::MakeRefCounted()) { - external_view_embedder_ = - std::make_unique(jni_facade); -} + std::shared_ptr android_context, + std::shared_ptr jni_facade, + AndroidSurface::Factory surface_factory) + : external_view_embedder_( + std::make_unique(android_context, + jni_facade, + surface_factory)), + proc_table_(fml::MakeRefCounted()) {} AndroidSurfaceVulkan::~AndroidSurfaceVulkan() = default; diff --git a/shell/platform/android/android_surface_vulkan.h b/shell/platform/android/android_surface_vulkan.h index 2778a7ec10003..3491d55acbcb8 100644 --- a/shell/platform/android/android_surface_vulkan.h +++ b/shell/platform/android/android_surface_vulkan.h @@ -9,10 +9,9 @@ #include #include "flutter/fml/macros.h" #include "flutter/shell/gpu/gpu_surface_vulkan_delegate.h" -#include "flutter/shell/platform/android/android_native_window.h" -#include "flutter/shell/platform/android/android_surface.h" #include "flutter/shell/platform/android/external_view_embedder/external_view_embedder.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" +#include "flutter/shell/platform/android/surface/android_surface.h" #include "flutter/vulkan/vulkan_window.h" namespace flutter { @@ -20,7 +19,9 @@ namespace flutter { class AndroidSurfaceVulkan : public AndroidSurface, public GPUSurfaceVulkanDelegate { public: - AndroidSurfaceVulkan(std::shared_ptr jni_facade); + AndroidSurfaceVulkan(std::shared_ptr android_context, + std::shared_ptr jni_facade, + AndroidSurface::Factory surface_factory); ~AndroidSurfaceVulkan() override; @@ -52,9 +53,10 @@ class AndroidSurfaceVulkan : public AndroidSurface, fml::RefPtr vk() override; private: + const std::unique_ptr external_view_embedder_; + fml::RefPtr proc_table_; fml::RefPtr native_window_; - std::unique_ptr external_view_embedder_; FML_DISALLOW_COPY_AND_ASSIGN(AndroidSurfaceVulkan); }; diff --git a/shell/platform/android/context/BUILD.gn b/shell/platform/android/context/BUILD.gn new file mode 100644 index 0000000000000..f510fbdd649df --- /dev/null +++ b/shell/platform/android/context/BUILD.gn @@ -0,0 +1,18 @@ +# 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. + +import("//flutter/common/config.gni") + +source_set("context") { + sources = [ + "android_context.cc", + "android_context.h", + ] + + public_configs = [ "//flutter:config" ] + + deps = [ + "//flutter/fml", + ] +} diff --git a/shell/platform/android/context/android_context.cc b/shell/platform/android/context/android_context.cc new file mode 100644 index 0000000000000..4ff6550c15abb --- /dev/null +++ b/shell/platform/android/context/android_context.cc @@ -0,0 +1,22 @@ +// 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/android/context/android_context.h" + +namespace flutter { + +AndroidContext::AndroidContext(AndroidRenderingAPI rendering_api) + : rendering_api_(rendering_api) {} + +AndroidContext::~AndroidContext() = default; + +AndroidRenderingAPI AndroidContext::RenderingApi() const { + return rendering_api_; +} + +bool AndroidContext::IsValid() const { + return true; +} + +} // namespace flutter diff --git a/shell/platform/android/android_context.h b/shell/platform/android/context/android_context.h similarity index 80% rename from shell/platform/android/android_context.h rename to shell/platform/android/context/android_context.h index 313d5377c40fa..627cbd8150855 100644 --- a/shell/platform/android/android_context.h +++ b/shell/platform/android/context/android_context.h @@ -6,9 +6,6 @@ #define FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_CONTEXT_H_ #include "flutter/fml/macros.h" -#include "flutter/fml/memory/ref_counted.h" -#include "flutter/fml/memory/ref_ptr.h" -#include "flutter/shell/common/platform_view.h" namespace flutter { @@ -27,11 +24,10 @@ class AndroidContext { ~AndroidContext(); - static std::shared_ptr Create( - AndroidRenderingAPI rendering_api); - AndroidRenderingAPI RenderingApi() const; + bool IsValid() const; + private: const AndroidRenderingAPI rendering_api_; diff --git a/shell/platform/android/external_view_embedder/BUILD.gn b/shell/platform/android/external_view_embedder/BUILD.gn index 5145e00fa8972..06765a97a13d0 100644 --- a/shell/platform/android/external_view_embedder/BUILD.gn +++ b/shell/platform/android/external_view_embedder/BUILD.gn @@ -2,18 +2,15 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. -import("//build/config/android/config.gni") import("//flutter/common/config.gni") -import("//flutter/shell/config.gni") -import("//flutter/shell/gpu/gpu.gni") -import("//flutter/shell/version/version.gni") - import("//flutter/testing/testing.gni") source_set("external_view_embedder") { sources = [ "external_view_embedder.cc", "external_view_embedder.h", + "surface_pool.cc", + "surface_pool.h", ] public_configs = [ "//flutter:config" ] @@ -22,7 +19,9 @@ source_set("external_view_embedder") { "//flutter/common", "//flutter/flow", "//flutter/fml", + "//flutter/shell/platform/android/context", "//flutter/shell/platform/android/jni", + "//flutter/shell/platform/android/surface", "//third_party/skia", ] } @@ -36,11 +35,15 @@ executable("android_external_view_embedder_unittests") { sources = [ "external_view_embedder_unittests.cc", + "surface_pool_unittests.cc", ] deps = [ ":external_view_embedder", ":external_view_embedder_fixtures", + "//flutter/shell/gpu:gpu_surface_gl", + "//flutter/shell/platform/android/jni:jni_mock", + "//flutter/shell/platform/android/surface:surface_mock", "//flutter/testing", "//flutter/testing:dart", "//flutter/testing:skia", diff --git a/shell/platform/android/external_view_embedder/external_view_embedder.cc b/shell/platform/android/external_view_embedder/external_view_embedder.cc index 4e42ac662f77f..9485eedff5491 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder.cc @@ -9,57 +9,191 @@ namespace flutter { AndroidExternalViewEmbedder::AndroidExternalViewEmbedder( - std::shared_ptr jni_facade) - : ExternalViewEmbedder(), jni_facade_(jni_facade) {} + std::shared_ptr android_context, + std::shared_ptr jni_facade, + const AndroidSurface::Factory& surface_factory) + : ExternalViewEmbedder(), + android_context_(android_context), + jni_facade_(jni_facade), + surface_factory_(surface_factory), + surface_pool_(std::make_unique()) {} // |ExternalViewEmbedder| void AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView( int view_id, std::unique_ptr params) { - // TODO(egarciad): Implement hybrid composition. - // https://github.com/flutter/flutter/issues/55270 TRACE_EVENT0("flutter", "AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView"); - picture_recorders_[view_id] = std::make_unique(); - picture_recorders_[view_id]->beginRecording(SkRect::Make(frame_size_)); + auto rtree_factory = RTreeFactory(); + view_rtrees_.insert({view_id, rtree_factory.getInstance()}); + + auto picture_recorder = std::make_unique(); + picture_recorder->beginRecording(SkRect::Make(frame_size_), &rtree_factory); + + picture_recorders_.insert({view_id, std::move(picture_recorder)}); composition_order_.push_back(view_id); + // Update params only if they changed. + if (view_params_.count(view_id) == 1 && + view_params_.at(view_id) == *params.get()) { + return; + } + view_params_.insert_or_assign(view_id, EmbeddedViewParams(*params.get())); } // |ExternalViewEmbedder| SkCanvas* AndroidExternalViewEmbedder::CompositeEmbeddedView(int view_id) { - return picture_recorders_[view_id]->getRecordingCanvas(); + if (picture_recorders_.count(view_id) == 1) { + return picture_recorders_.at(view_id)->getRecordingCanvas(); + } + return nullptr; } // |ExternalViewEmbedder| std::vector AndroidExternalViewEmbedder::GetCurrentCanvases() { - // TODO(egarciad): Implement hybrid composition. - // https://github.com/flutter/flutter/issues/55270 std::vector canvases; for (size_t i = 0; i < composition_order_.size(); i++) { int64_t view_id = composition_order_[i]; - canvases.push_back(picture_recorders_[view_id]->getRecordingCanvas()); + canvases.push_back(picture_recorders_.at(view_id)->getRecordingCanvas()); } return canvases; } +SkRect AndroidExternalViewEmbedder::GetViewRect(int view_id) const { + const EmbeddedViewParams& params = view_params_.at(view_id); + // TODO(egarciad): The rect should be computed from the mutator stack. + // https://github.com/flutter/flutter/issues/59821 + return SkRect::MakeXYWH(params.offsetPixels.x(), // + params.offsetPixels.y(), // + params.sizePoints.width() * device_pixel_ratio_, // + params.sizePoints.height() * device_pixel_ratio_ // + ); +} + // |ExternalViewEmbedder| bool AndroidExternalViewEmbedder::SubmitFrame( GrContext* context, std::unique_ptr frame) { - // TODO(egarciad): Implement hybrid composition. - // https://github.com/flutter/flutter/issues/55270 TRACE_EVENT0("flutter", "AndroidExternalViewEmbedder::SubmitFrame"); + if (should_run_rasterizer_on_platform_thread_) { // Don't submit the current frame if the frame will be resubmitted. return true; } + + std::unordered_map> overlay_layers; + std::unordered_map> pictures; + SkCanvas* background_canvas = frame->SkiaCanvas(); + + // Restore the clip context after exiting this method since it's changed + // below. + SkAutoCanvasRestore save(background_canvas, /*doSave=*/true); + for (size_t i = 0; i < composition_order_.size(); i++) { int64_t view_id = composition_order_[i]; - frame->SkiaCanvas()->drawPicture( - picture_recorders_[view_id]->finishRecordingAsPicture()); + SkRect view_rect = GetViewRect(view_id); + + // Display the platform view. If it's already displayed, then it's + // just positioned and sized. + jni_facade_->FlutterViewOnDisplayPlatformView(view_id, // + view_rect.x(), // + view_rect.y(), // + view_rect.width(), // + view_rect.height() // + ); + + sk_sp picture = + picture_recorders_.at(view_id)->finishRecordingAsPicture(); + FML_CHECK(picture); + pictures.insert({view_id, picture}); + sk_sp rtree = view_rtrees_.at(view_id); + // Determinate if Flutter UI intersects with any of the previous + // platform views stacked by z position. + // + // This is done by querying the r-tree that holds the records for the + // picture recorder corresponding to the flow layers added after a platform + // view layer. + for (ssize_t j = i; j >= 0; j--) { + int64_t current_view_id = composition_order_[j]; + SkRect current_view_rect = GetViewRect(current_view_id); + // Each rect corresponds to a native view that renders Flutter UI. + std::list intersection_rects = + rtree->searchNonOverlappingDrawnRects(current_view_rect); + auto allocation_size = intersection_rects.size(); + + // Limit the number of native views, so it doesn't grow forever. + // + // In this case, the rects are merged into a single one that is the union + // of all the rects. + if (allocation_size > kMaxLayerAllocations) { + SkRect joined_rect; + for (const SkRect& rect : intersection_rects) { + joined_rect.join(rect); + } + intersection_rects.clear(); + intersection_rects.push_back(joined_rect); + } + for (SkRect& intersection_rect : intersection_rects) { + // Get the intersection rect between the current rect + // and the platform view rect. + // joined_rect.intersect(platform_view_rect); + // Subpixels in the platform may not align with the canvas subpixels. + // + // To workaround it, round the floating point bounds and make the rect + // slighly larger. For example, {0.3, 0.5, 3.1, 4.7} becomes {0, 0, 4, + // 5}. + intersection_rect.set(intersection_rect.roundOut()); + // Clip the background canvas, so it doesn't contain any of the pixels + // drawn on the overlay layer. + background_canvas->clipRect(intersection_rect, SkClipOp::kDifference); + } + overlay_layers.insert({current_view_id, intersection_rects}); + } + background_canvas->drawPicture(pictures.at(view_id)); } - return frame->Submit(); + // Submit the background canvas frame before switching the GL context to + // the surfaces above. + frame->Submit(); + + for (int64_t view_id : composition_order_) { + for (const SkRect& overlay_rect : overlay_layers.at(view_id)) { + CreateSurfaceIfNeeded(context, // + view_id, // + pictures.at(view_id), // + overlay_rect // + ) + ->Submit(); + } + } + return true; +} + +// |ExternalViewEmbedder| +std::unique_ptr +AndroidExternalViewEmbedder::CreateSurfaceIfNeeded(GrContext* context, + int64_t view_id, + sk_sp picture, + const SkRect& rect) { + std::shared_ptr layer = surface_pool_->GetLayer( + context, android_context_, jni_facade_, surface_factory_); + + std::unique_ptr frame = + layer->surface->AcquireFrame(frame_size_); + // Display the overlay surface. If it's already displayed, then it's + // just positioned and sized. + jni_facade_->FlutterViewDisplayOverlaySurface(layer->id, // + rect.x(), // + rect.y(), // + rect.width(), // + rect.height() // + ); + SkCanvas* overlay_canvas = frame->SkiaCanvas(); + overlay_canvas->clear(SK_ColorTRANSPARENT); + // Offset the picture since its absolute position on the scene is determined + // by the position of the overlay view. + overlay_canvas->translate(-rect.x(), -rect.y()); + overlay_canvas->drawPicture(picture); + return frame; } // |ExternalViewEmbedder| @@ -100,11 +234,18 @@ void AndroidExternalViewEmbedder::Reset() { } // |ExternalViewEmbedder| -void AndroidExternalViewEmbedder::BeginFrame(SkISize frame_size, - GrContext* context, - double device_pixel_ratio) { +void AndroidExternalViewEmbedder::BeginFrame( + SkISize frame_size, + GrContext* context, + double device_pixel_ratio, + fml::RefPtr raster_thread_merger) { Reset(); frame_size_ = frame_size; + device_pixel_ratio_ = device_pixel_ratio; + // JNI method must be called on the platform thread. + if (raster_thread_merger->IsOnPlatformThread()) { + jni_facade_->FlutterViewBeginFrame(); + } } // |ExternalViewEmbedder| @@ -119,6 +260,11 @@ void AndroidExternalViewEmbedder::EndFrame( raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration); should_run_rasterizer_on_platform_thread_ = false; } + surface_pool_->RecycleLayers(); + // JNI method must be called on the platform thread. + if (raster_thread_merger->IsOnPlatformThread()) { + jni_facade_->FlutterViewEndFrame(); + } } } // namespace flutter diff --git a/shell/platform/android/external_view_embedder/external_view_embedder.h b/shell/platform/android/external_view_embedder/external_view_embedder.h index 6cfa82a8d58e1..41b6e68f1155d 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder.h +++ b/shell/platform/android/external_view_embedder/external_view_embedder.h @@ -6,16 +6,30 @@ #define FLUTTER_SHELL_PLATFORM_ANDROID_EXTERNAL_VIEW_EMBEDDER_H_ #include "flutter/flow/embedded_views.h" - +#include "flutter/flow/rtree.h" +#include "flutter/shell/platform/android/context/android_context.h" +#include "flutter/shell/platform/android/external_view_embedder/surface_pool.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" #include "third_party/skia/include/core/SkPictureRecorder.h" namespace flutter { +//------------------------------------------------------------------------------ +/// Allows to embed Android views into a Flutter application. +/// +/// This class calls Java methods via |PlatformViewAndroidJNI| to manage the +/// lifecycle of the Android view corresponding to |flutter::PlatformViewLayer|. +/// +/// It also orchestrates overlay surfaces. These are Android views +/// that render above (by Z order) the Android view corresponding to +/// |flutter::PlatformViewLayer|. +/// class AndroidExternalViewEmbedder final : public ExternalViewEmbedder { public: AndroidExternalViewEmbedder( - std::shared_ptr jni_facade); + std::shared_ptr android_context, + std::shared_ptr jni_facade, + const AndroidSurface::Factory& surface_factory); // |ExternalViewEmbedder| void PrerollCompositeEmbeddedView( @@ -40,9 +54,11 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder { SkCanvas* GetRootCanvas() override; // |ExternalViewEmbedder| - void BeginFrame(SkISize frame_size, - GrContext* context, - double device_pixel_ratio) override; + void BeginFrame( + SkISize frame_size, + GrContext* context, + double device_pixel_ratio, + fml::RefPtr raster_thread_merger) override; // |ExternalViewEmbedder| void CancelFrame() override; @@ -51,9 +67,12 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder { void EndFrame( fml::RefPtr raster_thread_merger) override; + // Gets the rect based on the device pixel ratio of a platform view displayed + // on the screen. + SkRect GetViewRect(int view_id) const; + private: - // Allows to call methods in Java. - const std::shared_ptr jni_facade_; + static const int kMaxLayerAllocations = 2; // The number of frames the rasterizer task runner will continue // to run on the platform thread after no platform view is rendered. @@ -62,13 +81,29 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder { // where the platform view might be momentarily off the screen. static const int kDefaultMergedLeaseDuration = 10; + // Provides metadata to the Android surfaces. + const std::shared_ptr android_context_; + + // Allows to call methods in Java. + const std::shared_ptr jni_facade_; + + // Allows to create surfaces. + const AndroidSurface::Factory surface_factory_; + + // Holds surfaces. Allows to recycle surfaces or allocate new ones. + const std::unique_ptr surface_pool_; + // Whether the rasterizer task runner should run on the platform thread. // When this is true, the current frame is cancelled and resubmitted. bool should_run_rasterizer_on_platform_thread_ = false; - // The size of the background canvas. + // The size of the root canvas. SkISize frame_size_; + // The pixel ratio used to determinate the size of a platform view layer + // relative to the device layout system. + double device_pixel_ratio_; + // The order of composition. Each entry contains a unique id for the platform // view. std::vector composition_order_; @@ -76,10 +111,25 @@ class AndroidExternalViewEmbedder final : public ExternalViewEmbedder { // The platform view's picture recorder keyed off the platform view id, which // contains any subsequent operation until the next platform view or the end // of the last leaf node in the layer tree. - std::map> picture_recorders_; + std::unordered_map> + picture_recorders_; - /// Resets the state. + // The params for a platform view, which contains the size, position and + // mutation stack. + std::unordered_map view_params_; + + // The r-tree that captures the operations for the picture recorders. + std::unordered_map> view_rtrees_; + + // Resets the state. void Reset(); + + // Creates a Surface when needed or recycles an existing one. + // Finally, draws the picture on the frame's canvas. + std::unique_ptr CreateSurfaceIfNeeded(GrContext* context, + int64_t view_id, + sk_sp picture, + const SkRect& rect); }; } // namespace flutter diff --git a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc index d0ef824cb6724..bba1c126564a1 100644 --- a/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc +++ b/shell/platform/android/external_view_embedder/external_view_embedder_unittests.cc @@ -2,18 +2,81 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "flutter/flow/surface.h" #include "flutter/fml/raster_thread_merger.h" #include "flutter/fml/thread.h" #include "flutter/shell/platform/android/external_view_embedder/external_view_embedder.h" +#include "flutter/shell/platform/android/jni/jni_mock.h" +#include "flutter/shell/platform/android/surface/android_surface_mock.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" +#include "third_party/skia/include/gpu/GrContext.h" namespace flutter { namespace testing { +using ::testing::ByMove; +using ::testing::Return; + +class SurfaceMock : public Surface { + public: + MOCK_METHOD(bool, IsValid, (), (override)); + + MOCK_METHOD(std::unique_ptr, + AcquireFrame, + (const SkISize& size), + (override)); + + MOCK_METHOD(SkMatrix, GetRootTransformation, (), (const, override)); + + MOCK_METHOD(GrContext*, GetContext, (), (override)); + + MOCK_METHOD(flutter::ExternalViewEmbedder*, + GetExternalViewEmbedder, + (), + (override)); + + MOCK_METHOD(std::unique_ptr, + MakeRenderContextCurrent, + (), + (override)); +}; + +fml::RefPtr GetThreadMergerFromPlatformThread() { + auto rasterizer_thread = new fml::Thread("rasterizer"); + auto rasterizer_queue_id = + rasterizer_thread->GetTaskRunner()->GetTaskQueueId(); + + // Assume the current thread is the platform thread. + fml::MessageLoop::EnsureInitializedForCurrentThread(); + auto platform_queue_id = fml::MessageLoop::GetCurrentTaskQueueId(); + + return fml::MakeRefCounted(platform_queue_id, + rasterizer_queue_id); +} + +fml::RefPtr GetThreadMergerFromRasterThread() { + auto platform_thread = new fml::Thread("rasterizer"); + auto platform_queue_id = platform_thread->GetTaskRunner()->GetTaskQueueId(); + + // Assume the current thread is the raster thread. + fml::MessageLoop::EnsureInitializedForCurrentThread(); + auto rasterizer_queue_id = fml::MessageLoop::GetCurrentTaskQueueId(); + + return fml::MakeRefCounted(platform_queue_id, + rasterizer_queue_id); +} + TEST(AndroidExternalViewEmbedder, GetCurrentCanvases) { - auto embedder = new AndroidExternalViewEmbedder(nullptr); + auto jni_mock = std::make_shared(); + + auto embedder = + std::make_unique(nullptr, jni_mock, nullptr); + auto raster_thread_merger = GetThreadMergerFromPlatformThread(); - embedder->BeginFrame(SkISize::Make(10, 20), nullptr, 1.0); + EXPECT_CALL(*jni_mock, FlutterViewBeginFrame()); + embedder->BeginFrame(SkISize::Make(10, 20), nullptr, 1.0, + raster_thread_merger); embedder->PrerollCompositeEmbeddedView( 0, std::make_unique()); @@ -26,20 +89,46 @@ TEST(AndroidExternalViewEmbedder, GetCurrentCanvases) { ASSERT_EQ(SkISize::Make(10, 20), canvases[1]->getBaseLayerSize()); } +TEST(AndroidExternalViewEmbedder, GetCurrentCanvases__CompositeOrder) { + auto jni_mock = std::make_shared(); + + auto embedder = + std::make_unique(nullptr, jni_mock, nullptr); + auto raster_thread_merger = GetThreadMergerFromPlatformThread(); + + EXPECT_CALL(*jni_mock, FlutterViewBeginFrame()); + embedder->BeginFrame(SkISize::Make(10, 20), nullptr, 1.0, + raster_thread_merger); + + embedder->PrerollCompositeEmbeddedView( + 0, std::make_unique()); + embedder->PrerollCompositeEmbeddedView( + 1, std::make_unique()); + + auto canvases = embedder->GetCurrentCanvases(); + ASSERT_EQ(2UL, canvases.size()); + ASSERT_EQ(embedder->CompositeEmbeddedView(0), canvases[0]); + ASSERT_EQ(embedder->CompositeEmbeddedView(1), canvases[1]); +} + TEST(AndroidExternalViewEmbedder, CompositeEmbeddedView) { - auto embedder = new AndroidExternalViewEmbedder(nullptr); + auto embedder = + std::make_unique(nullptr, nullptr, nullptr); + ASSERT_EQ(nullptr, embedder->CompositeEmbeddedView(0)); embedder->PrerollCompositeEmbeddedView( 0, std::make_unique()); - ASSERT_TRUE(embedder->CompositeEmbeddedView(0) != nullptr); + ASSERT_NE(nullptr, embedder->CompositeEmbeddedView(0)); + ASSERT_EQ(nullptr, embedder->CompositeEmbeddedView(1)); embedder->PrerollCompositeEmbeddedView( 1, std::make_unique()); - ASSERT_TRUE(embedder->CompositeEmbeddedView(1) != nullptr); + ASSERT_NE(nullptr, embedder->CompositeEmbeddedView(1)); } TEST(AndroidExternalViewEmbedder, CancelFrame) { - auto embedder = new AndroidExternalViewEmbedder(nullptr); + auto embedder = + std::make_unique(nullptr, nullptr, nullptr); embedder->PrerollCompositeEmbeddedView( 0, std::make_unique()); @@ -50,18 +139,16 @@ TEST(AndroidExternalViewEmbedder, CancelFrame) { } TEST(AndroidExternalViewEmbedder, RasterizerRunsOnPlatformThread) { - auto embedder = new AndroidExternalViewEmbedder(nullptr); - auto platform_thread = new fml::Thread("platform"); - auto rasterizer_thread = new fml::Thread("rasterizer"); - auto platform_queue_id = platform_thread->GetTaskRunner()->GetTaskQueueId(); - auto rasterizer_queue_id = - rasterizer_thread->GetTaskRunner()->GetTaskQueueId(); + auto jni_mock = std::make_shared(); + auto embedder = + std::make_unique(nullptr, jni_mock, nullptr); - auto raster_thread_merger = fml::MakeRefCounted( - platform_queue_id, rasterizer_queue_id); + auto raster_thread_merger = GetThreadMergerFromPlatformThread(); ASSERT_FALSE(raster_thread_merger->IsMerged()); - embedder->BeginFrame(SkISize::Make(10, 20), nullptr, 1.0); + EXPECT_CALL(*jni_mock, FlutterViewBeginFrame()); + embedder->BeginFrame(SkISize::Make(10, 20), nullptr, 1.0, + raster_thread_merger); // Push a platform view. embedder->PrerollCompositeEmbeddedView( 0, std::make_unique()); @@ -70,7 +157,9 @@ TEST(AndroidExternalViewEmbedder, RasterizerRunsOnPlatformThread) { ASSERT_EQ(PostPrerollResult::kResubmitFrame, postpreroll_result); ASSERT_TRUE(embedder->SubmitFrame(nullptr, nullptr)); + EXPECT_CALL(*jni_mock, FlutterViewEndFrame()); embedder->EndFrame(raster_thread_merger); + ASSERT_TRUE(raster_thread_merger->IsMerged()); int pending_frames = 0; @@ -82,23 +171,221 @@ TEST(AndroidExternalViewEmbedder, RasterizerRunsOnPlatformThread) { } TEST(AndroidExternalViewEmbedder, RasterizerRunsOnRasterizerThread) { - auto embedder = new AndroidExternalViewEmbedder(nullptr); - auto platform_thread = new fml::Thread("platform"); - auto rasterizer_thread = new fml::Thread("rasterizer"); - auto platform_queue_id = platform_thread->GetTaskRunner()->GetTaskQueueId(); - auto rasterizer_queue_id = - rasterizer_thread->GetTaskRunner()->GetTaskQueueId(); + auto jni_mock = std::make_shared(); + auto embedder = + std::make_unique(nullptr, jni_mock, nullptr); - auto raster_thread_merger = fml::MakeRefCounted( - platform_queue_id, rasterizer_queue_id); + auto raster_thread_merger = GetThreadMergerFromPlatformThread(); ASSERT_FALSE(raster_thread_merger->IsMerged()); PostPrerollResult result = embedder->PostPrerollAction(raster_thread_merger); ASSERT_EQ(PostPrerollResult::kSuccess, result); + EXPECT_CALL(*jni_mock, FlutterViewEndFrame()); embedder->EndFrame(raster_thread_merger); + ASSERT_FALSE(raster_thread_merger->IsMerged()); } +TEST(AndroidExternalViewEmbedder, PlatformViewRect) { + auto jni_mock = std::make_shared(); + + auto embedder = + std::make_unique(nullptr, jni_mock, nullptr); + auto raster_thread_merger = GetThreadMergerFromPlatformThread(); + + EXPECT_CALL(*jni_mock, FlutterViewBeginFrame()); + embedder->BeginFrame(SkISize::Make(100, 100), nullptr, 1.5, + raster_thread_merger); + + auto view_params = std::make_unique(); + view_params->offsetPixels = SkPoint::Make(10, 20); + view_params->sizePoints = SkSize::Make(30, 40); + + auto view_id = 0; + embedder->PrerollCompositeEmbeddedView(view_id, std::move(view_params)); + ASSERT_EQ(SkRect::MakeXYWH(10, 20, 45, 60), embedder->GetViewRect(view_id)); +} + +TEST(AndroidExternalViewEmbedder, PlatformViewRect__ChangedParams) { + auto jni_mock = std::make_shared(); + + auto embedder = + std::make_unique(nullptr, jni_mock, nullptr); + auto raster_thread_merger = GetThreadMergerFromPlatformThread(); + + EXPECT_CALL(*jni_mock, FlutterViewBeginFrame()); + embedder->BeginFrame(SkISize::Make(100, 100), nullptr, 1.5, + raster_thread_merger); + + auto view_id = 0; + auto view_params_1 = std::make_unique(); + view_params_1->offsetPixels = SkPoint::Make(10, 20); + view_params_1->sizePoints = SkSize::Make(30, 40); + embedder->PrerollCompositeEmbeddedView(view_id, std::move(view_params_1)); + + auto view_params_2 = std::make_unique(); + view_params_2->offsetPixels = SkPoint::Make(50, 60); + view_params_2->sizePoints = SkSize::Make(70, 80); + embedder->PrerollCompositeEmbeddedView(view_id, std::move(view_params_2)); + + ASSERT_EQ(SkRect::MakeXYWH(50, 60, 105, 120), embedder->GetViewRect(view_id)); +} + +TEST(AndroidExternalViewEmbedder, SubmitFrame__RecycleSurfaces) { + auto jni_mock = std::make_shared(); + auto android_context = + std::make_shared(AndroidRenderingAPI::kSoftware); + + auto window = fml::MakeRefCounted(nullptr); + auto gr_context = GrContext::MakeMock(nullptr); + auto frame_size = SkISize::Make(1000, 1000); + auto surface_factory = + [gr_context, window, frame_size]( + std::shared_ptr android_context, + std::shared_ptr jni_facade) { + auto surface_frame_1 = std::make_unique( + SkSurface::MakeNull(1000, 1000), false, + [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { + return true; + }); + auto surface_frame_2 = std::make_unique( + SkSurface::MakeNull(1000, 1000), false, + [](const SurfaceFrame& surface_frame, SkCanvas* canvas) { + return true; + }); + + auto surface_mock = std::make_unique(); + EXPECT_CALL(*surface_mock, AcquireFrame(frame_size)) + .Times(2 /* frames */) + .WillOnce(Return(ByMove(std::move(surface_frame_1)))) + .WillOnce(Return(ByMove(std::move(surface_frame_2)))); + + auto android_surface_mock = std::make_unique(); + EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); + + EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get())) + .WillOnce(Return(ByMove(std::move(surface_mock)))); + + EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); + + return android_surface_mock; + }; + auto embedder = std::make_unique( + android_context, jni_mock, surface_factory); + auto raster_thread_merger = GetThreadMergerFromPlatformThread(); + + // ------------------ First frame ------------------ // + { + EXPECT_CALL(*jni_mock, FlutterViewBeginFrame()); + embedder->BeginFrame(frame_size, nullptr, 1.5, raster_thread_merger); + + // Add an Android view. + auto view_params_1 = std::make_unique(); + view_params_1->offsetPixels = SkPoint::Make(100, 100); + // TODO(egarciad): Investigate why Flow applies the device pixel ratio to + // the offsetPixels, but not the sizePoints. + view_params_1->sizePoints = SkSize::Make(200, 200); + embedder->PrerollCompositeEmbeddedView(0, std::move(view_params_1)); + // This is the recording canvas flow writes to. + auto canvas_1 = embedder->CompositeEmbeddedView(0); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + rect_paint.setStyle(SkPaint::Style::kFill_Style); + + // This simulates Flutter UI that doesn't intersect with the Android view. + canvas_1->drawRect(SkRect::MakeXYWH(0, 0, 50, 50), rect_paint); + // This simulates Flutter UI that intersects with the Android view. + canvas_1->drawRect(SkRect::MakeXYWH(50, 50, 200, 200), rect_paint); + canvas_1->drawRect(SkRect::MakeXYWH(150, 150, 100, 100), rect_paint); + + // Create a new overlay surface. + EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface()) + .WillOnce(Return( + ByMove(std::make_unique( + 0, window)))); + // The JNI call to display the Android view. + EXPECT_CALL(*jni_mock, + FlutterViewOnDisplayPlatformView(0, 100, 100, 300, 300)); + // The JNI call to display the overlay surface. + EXPECT_CALL(*jni_mock, + FlutterViewDisplayOverlaySurface(0, 50, 50, 200, 200)); + + auto surface_frame = + std::make_unique(SkSurface::MakeNull(1000, 1000), false, + [](const SurfaceFrame& surface_frame, + SkCanvas* canvas) { return true; }); + + embedder->SubmitFrame(gr_context.get(), std::move(surface_frame)); + + EXPECT_CALL(*jni_mock, FlutterViewEndFrame()); + embedder->EndFrame(raster_thread_merger); + } + + // ------------------ Second frame ------------------ // + { + EXPECT_CALL(*jni_mock, FlutterViewBeginFrame()); + embedder->BeginFrame(frame_size, nullptr, 1.5, raster_thread_merger); + + // Add an Android view. + auto view_params_1 = std::make_unique(); + view_params_1->offsetPixels = SkPoint::Make(100, 100); + // TODO(egarciad): Investigate why Flow applies the device pixel ratio to + // the offsetPixels, but not the sizePoints. + view_params_1->sizePoints = SkSize::Make(200, 200); + embedder->PrerollCompositeEmbeddedView(0, std::move(view_params_1)); + // This is the recording canvas flow writes to. + auto canvas_1 = embedder->CompositeEmbeddedView(0); + + auto rect_paint = SkPaint(); + rect_paint.setColor(SkColors::kCyan); + rect_paint.setStyle(SkPaint::Style::kFill_Style); + + // This simulates Flutter UI that doesn't intersect with the Android view. + canvas_1->drawRect(SkRect::MakeXYWH(0, 0, 50, 50), rect_paint); + // This simulates Flutter UI that intersects with the Android view. + canvas_1->drawRect(SkRect::MakeXYWH(50, 50, 200, 200), rect_paint); + canvas_1->drawRect(SkRect::MakeXYWH(150, 150, 100, 100), rect_paint); + + // Don't create a new overlay surface since it's recycled from the first + // frame. + EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface()).Times(0); + // The JNI call to display the Android view. + EXPECT_CALL(*jni_mock, + FlutterViewOnDisplayPlatformView(0, 100, 100, 300, 300)); + // The JNI call to display the overlay surface. + EXPECT_CALL(*jni_mock, + FlutterViewDisplayOverlaySurface(0, 50, 50, 200, 200)); + + auto surface_frame = + std::make_unique(SkSurface::MakeNull(1000, 1000), false, + [](const SurfaceFrame& surface_frame, + SkCanvas* canvas) { return true; }); + embedder->SubmitFrame(gr_context.get(), std::move(surface_frame)); + + EXPECT_CALL(*jni_mock, FlutterViewEndFrame()); + embedder->EndFrame(raster_thread_merger); + } +} + +TEST(AndroidExternalViewEmbedder, DoesNotCallJNIPlatformThreadOnlyMethods) { + auto jni_mock = std::make_shared(); + + auto embedder = + std::make_unique(nullptr, jni_mock, nullptr); + + // While on the raster thread, don't make JNI calls as these methods can only + // run on the platform thread. + auto raster_thread_merger = GetThreadMergerFromRasterThread(); + + EXPECT_CALL(*jni_mock, FlutterViewBeginFrame()).Times(0); + embedder->BeginFrame(SkISize::Make(10, 20), nullptr, 1.0, + raster_thread_merger); + + EXPECT_CALL(*jni_mock, FlutterViewEndFrame()).Times(0); + embedder->EndFrame(raster_thread_merger); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/android/external_view_embedder/surface_pool.cc b/shell/platform/android/external_view_embedder/surface_pool.cc new file mode 100644 index 0000000000000..8258234686b73 --- /dev/null +++ b/shell/platform/android/external_view_embedder/surface_pool.cc @@ -0,0 +1,81 @@ +// 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/android/external_view_embedder/surface_pool.h" + +namespace flutter { + +OverlayLayer::OverlayLayer(int id, + std::unique_ptr android_surface, + std::unique_ptr surface) + : id(id), + android_surface(std::move(android_surface)), + surface(std::move(surface)){}; + +OverlayLayer::~OverlayLayer() = default; + +SurfacePool::SurfacePool() = default; + +SurfacePool::~SurfacePool() = default; + +std::shared_ptr SurfacePool::GetLayer( + GrContext* gr_context, + std::shared_ptr android_context, + std::shared_ptr jni_facade, + const AndroidSurface::Factory& surface_factory) { + intptr_t gr_context_key = reinterpret_cast(gr_context); + // Allocate a new surface if there isn't one available. + if (available_layer_index_ >= layers_.size()) { + std::unique_ptr android_surface = + surface_factory(android_context, jni_facade); + + FML_CHECK(android_surface && android_surface->IsValid()) + << "Could not create an OpenGL, Vulkan or Software surface to setup " + "rendering."; + + std::unique_ptr java_metadata = + jni_facade->FlutterViewCreateOverlaySurface(); + + FML_CHECK(java_metadata->window); + android_surface->SetNativeWindow(java_metadata->window); + + std::unique_ptr surface = + android_surface->CreateGPUSurface(gr_context); + + std::shared_ptr layer = + std::make_shared(java_metadata->id, // + std::move(android_surface), // + std::move(surface) // + ); + layer->gr_context_key = gr_context_key; + layers_.push_back(layer); + } + std::shared_ptr layer = layers_[available_layer_index_]; + // Since the surfaces are recycled, it's possible that the GrContext is + // different. + if (gr_context_key != layer->gr_context_key) { + layer->gr_context_key = gr_context_key; + // The overlay already exists, but the GrContext was changed so we need to + // recreate the rendering surface with the new GrContext. + std::unique_ptr surface = + layer->android_surface->CreateGPUSurface(gr_context); + layer->surface = std::move(surface); + } + available_layer_index_++; + return layer; +} + +void SurfacePool::RecycleLayers() { + available_layer_index_ = 0; +} + +std::vector> SurfacePool::GetUnusedLayers() { + std::vector> results; + for (size_t i = available_layer_index_; i < layers_.size(); i++) { + results.push_back(layers_[i]); + } + return results; +} + +} // namespace flutter diff --git a/shell/platform/android/external_view_embedder/surface_pool.h b/shell/platform/android/external_view_embedder/surface_pool.h new file mode 100644 index 0000000000000..e4eee52d66042 --- /dev/null +++ b/shell/platform/android/external_view_embedder/surface_pool.h @@ -0,0 +1,86 @@ +// 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_ANDROID_EXTERNAL_VIEW_EMBEDDER_SURFACE_POOL_H_ +#define FLUTTER_SHELL_PLATFORM_ANDROID_EXTERNAL_VIEW_EMBEDDER_SURFACE_POOL_H_ + +#include "flutter/flow/surface.h" +#include "flutter/shell/platform/android/context/android_context.h" +#include "flutter/shell/platform/android/surface/android_surface.h" + +namespace flutter { + +//------------------------------------------------------------------------------ +/// An Overlay layer represents an `android.view.View` in the C side. +/// +/// The `id` is used to uniquely identify the layer and recycle it between +/// frames. +/// +struct OverlayLayer { + OverlayLayer(int id, + std::unique_ptr android_surface, + std::unique_ptr surface); + + ~OverlayLayer(); + + // A unique id to identify the overlay when it gets recycled. + const int id; + + // A GPU surface. + const std::unique_ptr android_surface; + + // A GPU surface. This may change when the overlay is recycled. + std::unique_ptr surface; + + // The `GrContext` that is currently used by the overlay surfaces. + // We track this to know when the GrContext for the Flutter app has changed + // so we can update the overlay with the new context. + // + // This may change when the overlay is recycled. + intptr_t gr_context_key; +}; + +// This class isn't thread safe. +class SurfacePool { + public: + SurfacePool(); + + ~SurfacePool(); + + // Gets a layer from the pool if available, or allocates a new one. + // Finally, it marks the layer as used. That is, it increments + // `available_layer_index_`. + std::shared_ptr GetLayer( + GrContext* gr_context, + std::shared_ptr android_context, + std::shared_ptr jni_facade, + const AndroidSurface::Factory& surface_factory); + + // Gets the layers in the pool that aren't currently used. + // This method doesn't mark the layers as unused. + std::vector> GetUnusedLayers(); + + // Marks the layers in the pool as available for reuse. + void RecycleLayers(); + + private: + // The index of the entry in the layers_ vector that determines the beginning + // of the unused layers. For example, consider the following vector: + // _____ + // | 0 | + // |---| + // | 1 | <-- `available_layer_index_` + // |---| + // | 2 | + // |---| + // + // This indicates that entries starting from 1 can be reused meanwhile the + // entry at position 0 cannot be reused. + size_t available_layer_index_ = 0; + std::vector> layers_; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_ANDROID_EXTERNAL_VIEW_EMBEDDER_SURFACE_POOL_H_ diff --git a/shell/platform/android/external_view_embedder/surface_pool_unittests.cc b/shell/platform/android/external_view_embedder/surface_pool_unittests.cc new file mode 100644 index 0000000000000..72488c9cb03aa --- /dev/null +++ b/shell/platform/android/external_view_embedder/surface_pool_unittests.cc @@ -0,0 +1,168 @@ +// 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/fml/make_copyable.h" +#include "flutter/shell/platform/android/external_view_embedder/surface_pool.h" +#include "flutter/shell/platform/android/jni/jni_mock.h" +#include "flutter/shell/platform/android/surface/android_surface_mock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "third_party/skia/include/gpu/GrContext.h" + +namespace flutter { +namespace testing { + +using ::testing::ByMove; +using ::testing::Return; + +TEST(SurfacePool, GetLayer__AllocateOneLayer) { + auto pool = std::make_unique(); + + auto gr_context = GrContext::MakeMock(nullptr); + auto android_context = + std::make_shared(AndroidRenderingAPI::kSoftware); + + auto jni_mock = std::make_shared(); + auto window = fml::MakeRefCounted(nullptr); + EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface()) + .WillOnce(Return( + ByMove(std::make_unique( + 0, window)))); + + auto surface_factory = + [gr_context, window](std::shared_ptr android_context, + std::shared_ptr jni_facade) { + auto android_surface_mock = std::make_unique(); + EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get())); + EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); + EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); + return android_surface_mock; + }; + auto layer = pool->GetLayer(gr_context.get(), android_context, jni_mock, + surface_factory); + + ASSERT_NE(nullptr, layer); + ASSERT_EQ(reinterpret_cast(gr_context.get()), + layer->gr_context_key); +} + +TEST(SurfacePool, GetUnusedLayers) { + auto pool = std::make_unique(); + + auto gr_context = GrContext::MakeMock(nullptr); + auto android_context = + std::make_shared(AndroidRenderingAPI::kSoftware); + + auto jni_mock = std::make_shared(); + auto window = fml::MakeRefCounted(nullptr); + EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface()) + .WillOnce(Return( + ByMove(std::make_unique( + 0, window)))); + + auto surface_factory = + [gr_context, window](std::shared_ptr android_context, + std::shared_ptr jni_facade) { + auto android_surface_mock = std::make_unique(); + EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get())); + EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); + EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); + return android_surface_mock; + }; + auto layer = pool->GetLayer(gr_context.get(), android_context, jni_mock, + surface_factory); + ASSERT_EQ(0UL, pool->GetUnusedLayers().size()); + + pool->RecycleLayers(); + + ASSERT_EQ(1UL, pool->GetUnusedLayers().size()); + ASSERT_EQ(layer, pool->GetUnusedLayers()[0]); +} + +TEST(SurfacePool, GetLayer__Recycle) { + auto pool = std::make_unique(); + + auto gr_context_1 = GrContext::MakeMock(nullptr); + auto jni_mock = std::make_shared(); + auto window = fml::MakeRefCounted(nullptr); + EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface()) + .WillOnce(Return( + ByMove(std::make_unique( + 0, window)))); + + auto android_context = + std::make_shared(AndroidRenderingAPI::kSoftware); + + auto gr_context_2 = GrContext::MakeMock(nullptr); + auto surface_factory = + [gr_context_1, gr_context_2, window]( + std::shared_ptr android_context, + std::shared_ptr jni_facade) { + auto android_surface_mock = std::make_unique(); + // Allocate two GPU surfaces for each gr context. + EXPECT_CALL(*android_surface_mock, + CreateGPUSurface(gr_context_1.get())); + EXPECT_CALL(*android_surface_mock, + CreateGPUSurface(gr_context_2.get())); + // Set the native window once. + EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); + EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); + return android_surface_mock; + }; + auto layer_1 = pool->GetLayer(gr_context_1.get(), android_context, jni_mock, + surface_factory); + + pool->RecycleLayers(); + + auto layer_2 = pool->GetLayer(gr_context_2.get(), android_context, jni_mock, + surface_factory); + + ASSERT_NE(nullptr, layer_1); + ASSERT_EQ(layer_1, layer_2); + ASSERT_EQ(reinterpret_cast(gr_context_2.get()), + layer_1->gr_context_key); + ASSERT_EQ(reinterpret_cast(gr_context_2.get()), + layer_2->gr_context_key); +} + +TEST(SurfacePool, GetLayer__AllocateTwoLayers) { + auto pool = std::make_unique(); + + auto gr_context = GrContext::MakeMock(nullptr); + auto android_context = + std::make_shared(AndroidRenderingAPI::kSoftware); + + auto jni_mock = std::make_shared(); + auto window = fml::MakeRefCounted(nullptr); + EXPECT_CALL(*jni_mock, FlutterViewCreateOverlaySurface()) + .Times(2) + .WillOnce(Return( + ByMove(std::make_unique( + 0, window)))) + .WillOnce(Return( + ByMove(std::make_unique( + 1, window)))); + + auto surface_factory = + [gr_context, window](std::shared_ptr android_context, + std::shared_ptr jni_facade) { + auto android_surface_mock = std::make_unique(); + EXPECT_CALL(*android_surface_mock, CreateGPUSurface(gr_context.get())); + EXPECT_CALL(*android_surface_mock, SetNativeWindow(window)); + EXPECT_CALL(*android_surface_mock, IsValid()).WillOnce(Return(true)); + return android_surface_mock; + }; + auto layer_1 = pool->GetLayer(gr_context.get(), android_context, jni_mock, + surface_factory); + auto layer_2 = pool->GetLayer(gr_context.get(), android_context, jni_mock, + surface_factory); + ASSERT_NE(nullptr, layer_1); + ASSERT_NE(nullptr, layer_2); + ASSERT_NE(layer_1, layer_2); + ASSERT_EQ(0, layer_1->id); + ASSERT_EQ(1, layer_2->id); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/android/jni/BUILD.gn b/shell/platform/android/jni/BUILD.gn index 700dfb5c6676e..4929ed62646ae 100644 --- a/shell/platform/android/jni/BUILD.gn +++ b/shell/platform/android/jni/BUILD.gn @@ -3,6 +3,7 @@ # found in the LICENSE file. import("//flutter/common/config.gni") +import("//flutter/testing/testing.gni") source_set("jni") { sources = [ @@ -15,6 +16,41 @@ source_set("jni") { deps = [ "//flutter/fml", "//flutter/lib/ui", + "//flutter/shell/platform/android/surface:native_window", "//third_party/skia", ] } + +source_set("jni_mock") { + testonly = true + + sources = [ + "jni_mock.h", + ] + + public_configs = [ "//flutter:config" ] + + deps = [ + ":jni", + ] +} + +test_fixtures("jni_fixtures") { + fixtures = [] +} + +executable("jni_unittests") { + testonly = true + + sources = [ + "jni_mock_unittest.cc", + ] + + deps = [ + ":jni_fixtures", + ":jni_mock", + "//flutter/testing", + "//flutter/testing:dart", + "//flutter/testing:skia", + ] +} diff --git a/shell/platform/android/jni/jni_mock.h b/shell/platform/android/jni/jni_mock.h new file mode 100644 index 0000000000000..245c8e78688c2 --- /dev/null +++ b/shell/platform/android/jni/jni_mock.h @@ -0,0 +1,91 @@ +// 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_ANDROID_JNI_MOCK_H_ +#define FLUTTER_SHELL_PLATFORM_ANDROID_JNI_MOCK_H_ + +#include "flutter/shell/platform/android/jni/platform_view_android_jni.h" +#include "gmock/gmock.h" + +namespace flutter { + +//------------------------------------------------------------------------------ +/// Mock for |PlatformViewAndroidJNI|. This implementation can be used in unit +/// tests without requiring the Android toolchain. +/// +class JNIMock final : public PlatformViewAndroidJNI { + public: + MOCK_METHOD(void, + FlutterViewHandlePlatformMessage, + (fml::RefPtr message, int responseId), + (override)); + + MOCK_METHOD(void, + FlutterViewHandlePlatformMessageResponse, + (int responseId, std::unique_ptr data), + (override)); + + MOCK_METHOD(void, + FlutterViewUpdateSemantics, + (std::vector buffer, std::vector strings), + (override)); + + MOCK_METHOD(void, + FlutterViewUpdateCustomAccessibilityActions, + (std::vector actions_buffer, + std::vector strings), + (override)); + + MOCK_METHOD(void, FlutterViewOnFirstFrame, (), (override)); + + MOCK_METHOD(void, FlutterViewOnPreEngineRestart, (), (override)); + + MOCK_METHOD(void, + SurfaceTextureAttachToGLContext, + (JavaWeakGlobalRef surface_texture, int textureId), + (override)); + + MOCK_METHOD(void, + SurfaceTextureUpdateTexImage, + (JavaWeakGlobalRef surface_texture), + (override)); + + MOCK_METHOD(void, + SurfaceTextureGetTransformMatrix, + (JavaWeakGlobalRef surface_texture, SkMatrix& transform), + (override)); + + MOCK_METHOD(void, + SurfaceTextureDetachFromGLContext, + (JavaWeakGlobalRef surface_texture), + (override)); + + MOCK_METHOD(void, + FlutterViewOnDisplayPlatformView, + (int view_id, int x, int y, int width, int height), + (override)); + + MOCK_METHOD(void, + FlutterViewDisplayOverlaySurface, + (int surface_id, int x, int y, int width, int height), + (override)); + + MOCK_METHOD(void, FlutterViewBeginFrame, (), (override)); + + MOCK_METHOD(void, FlutterViewEndFrame, (), (override)); + + MOCK_METHOD(std::unique_ptr, + FlutterViewCreateOverlaySurface, + (), + (override)); + + MOCK_METHOD(std::unique_ptr>, + FlutterViewComputePlatformResolvedLocale, + (std::vector supported_locales_data), + (override)); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_ANDROID_JNI_MOCK_H_ diff --git a/shell/platform/android/jni/jni_mock_unittest.cc b/shell/platform/android/jni/jni_mock_unittest.cc new file mode 100644 index 0000000000000..738c49ce8d730 --- /dev/null +++ b/shell/platform/android/jni/jni_mock_unittest.cc @@ -0,0 +1,25 @@ +// 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/android/jni/jni_mock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +TEST(JNIMock, FlutterViewHandlePlatformMessage) { + JNIMock mock; + + auto message = + fml::MakeRefCounted("", nullptr); + auto response_id = 1; + + EXPECT_CALL(mock, FlutterViewHandlePlatformMessage(message, response_id)); + + mock.FlutterViewHandlePlatformMessage(message, response_id); +} + +} // namespace testing +} // namespace flutter diff --git a/shell/platform/android/jni/platform_view_android_jni.h b/shell/platform/android/jni/platform_view_android_jni.h index 51f11a1db7b16..311209d982ab5 100644 --- a/shell/platform/android/jni/platform_view_android_jni.h +++ b/shell/platform/android/jni/platform_view_android_jni.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef FLUTTER_SHELL_PLATFORM_ANDROID_PLATFORM_VIEW_ANDROID_JNI_H_ -#define FLUTTER_SHELL_PLATFORM_ANDROID_PLATFORM_VIEW_ANDROID_JNI_H_ +#ifndef FLUTTER_SHELL_PLATFORM_ANDROID_JNI_PLATFORM_VIEW_ANDROID_JNI_H_ +#define FLUTTER_SHELL_PLATFORM_ANDROID_JNI_PLATFORM_VIEW_ANDROID_JNI_H_ #include "flutter/fml/macros.h" #include "flutter/fml/mapping.h" @@ -13,6 +13,7 @@ #endif #include "flutter/lib/ui/window/platform_message.h" +#include "flutter/shell/platform/android/surface/android_native_window.h" #include "third_party/skia/include/core/SkMatrix.h" namespace flutter { @@ -147,12 +148,32 @@ class PlatformViewAndroidJNI { /// virtual void FlutterViewEndFrame() = 0; + //------------------------------------------------------------------------------ + /// The metadata returned from Java which is converted into an |OverlayLayer| + /// by |SurfacePool|. + /// + struct OverlayMetadata { + OverlayMetadata(int id, fml::RefPtr window) + : id(id), window(window){}; + + ~OverlayMetadata() = default; + + // A unique id to identify the overlay when it gets recycled. + const int id; + + // Holds a reference to the native window. That is, an `ANativeWindow`, + // which is the C counterpart of the `android.view.Surface` object in Java. + const fml::RefPtr window; + }; + //---------------------------------------------------------------------------- - /// @brief Instantiates an overlay surface in hybrid composition. + /// @brief Instantiates an overlay surface in hybrid composition and + /// provides the necessary metadata to operate the surface in C. /// /// @note Must be called from the platform thread. /// - virtual void FlutterViewCreateOverlaySurface() = 0; + virtual std::unique_ptr + FlutterViewCreateOverlaySurface() = 0; //---------------------------------------------------------------------------- /// @brief Computes the locale Android would select. @@ -164,4 +185,4 @@ class PlatformViewAndroidJNI { } // namespace flutter -#endif // FLUTTER_SHELL_PLATFORM_ANDROID_PLATFORM_VIEW_ANDROID_JNI_H_ +#endif // FLUTTER_SHELL_PLATFORM_ANDROID_JNI_PLATFORM_VIEW_ANDROID_JNI_H_ diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 311028dce920f..c7aaf1ef6b3a7 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -10,15 +10,43 @@ #include "flutter/fml/synchronization/waitable_event.h" #include "flutter/shell/common/shell_io_manager.h" #include "flutter/shell/gpu/gpu_surface_gl_delegate.h" -#include "flutter/shell/platform/android/android_context.h" +#include "flutter/shell/platform/android/android_context_gl.h" #include "flutter/shell/platform/android/android_external_texture_gl.h" #include "flutter/shell/platform/android/android_surface_gl.h" +#include "flutter/shell/platform/android/android_surface_software.h" + +#if SHELL_ENABLE_VULKAN +#include "flutter/shell/platform/android/android_surface_vulkan.h" +#endif // SHELL_ENABLE_VULKAN + +#include "flutter/shell/platform/android/context/android_context.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" #include "flutter/shell/platform/android/platform_message_response_android.h" #include "flutter/shell/platform/android/vsync_waiter_android.h" namespace flutter { +std::unique_ptr SurfaceFactory( + std::shared_ptr android_context, + std::shared_ptr jni_facade) { + FML_CHECK(SurfaceFactory); + switch (android_context->RenderingApi()) { + case AndroidRenderingAPI::kSoftware: + return std::make_unique( + android_context, jni_facade, SurfaceFactory); + case AndroidRenderingAPI::kOpenGLES: + return std::make_unique(android_context, jni_facade, + SurfaceFactory); + case AndroidRenderingAPI::kVulkan: +#if SHELL_ENABLE_VULKAN + return std::make_unique(android_context, jni_facade, + SurfaceFactory); +#endif // SHELL_ENABLE_VULKAN + return nullptr; + } + return nullptr; +} + PlatformViewAndroid::PlatformViewAndroid( PlatformView::Delegate& delegate, flutter::TaskRunners task_runners, @@ -27,16 +55,23 @@ PlatformViewAndroid::PlatformViewAndroid( : PlatformView(delegate, std::move(task_runners)), jni_facade_(jni_facade) { std::shared_ptr android_context; if (use_software_rendering) { - android_context = AndroidContext::Create(AndroidRenderingAPI::kSoftware); + android_context = + std::make_shared(AndroidRenderingAPI::kSoftware); } else { #if SHELL_ENABLE_VULKAN - android_context = AndroidContext::Create(AndroidRenderingAPI::kVulkan); + android_context = + std::make_shared(AndroidRenderingAPI::kVulkan); #else // SHELL_ENABLE_VULKAN - android_context = AndroidContext::Create(AndroidRenderingAPI::kOpenGLES); + android_context = std::make_shared( + AndroidRenderingAPI::kOpenGLES, + fml::MakeRefCounted()); #endif // SHELL_ENABLE_VULKAN } - android_surface_ = AndroidSurface::Create(android_context, jni_facade); - FML_CHECK(android_surface_) + FML_CHECK(android_context && android_context->IsValid()) + << "Could not create an Android context."; + + android_surface_ = SurfaceFactory(std::move(android_context), jni_facade); + FML_CHECK(android_surface_ && android_surface_->IsValid()) << "Could not create an OpenGL, Vulkan or Software surface to setup " "rendering."; } diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index 2a3474e8d2194..c0b6a1d79a4d8 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -15,9 +15,9 @@ #include "flutter/fml/platform/android/scoped_java_ref.h" #include "flutter/lib/ui/window/platform_message.h" #include "flutter/shell/common/platform_view.h" -#include "flutter/shell/platform/android/android_native_window.h" -#include "flutter/shell/platform/android/android_surface.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" +#include "flutter/shell/platform/android/surface/android_native_window.h" +#include "flutter/shell/platform/android/surface/android_surface.h" namespace flutter { diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index c400eda7f7694..17239099d5a43 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -1104,17 +1104,21 @@ void PlatformViewAndroidJNIImpl::FlutterViewEndFrame() { FML_CHECK(CheckException(env)); } -void PlatformViewAndroidJNIImpl::FlutterViewCreateOverlaySurface() { +std::unique_ptr +PlatformViewAndroidJNIImpl::FlutterViewCreateOverlaySurface() { JNIEnv* env = fml::jni::AttachCurrentThread(); auto java_object = java_object_.get(env); if (java_object.is_null()) { - return; + return nullptr; } env->CallVoidMethod(java_object.obj(), g_create_overlay_surface_method); FML_CHECK(CheckException(env)); + // TODO(egarciad): Wire this up. + // https://github.com/flutter/flutter/issues/55270 + return std::make_unique(0, nullptr); } std::unique_ptr> diff --git a/shell/platform/android/platform_view_android_jni_impl.h b/shell/platform/android/platform_view_android_jni_impl.h index b6fa849006d40..b496f2d753897 100644 --- a/shell/platform/android/platform_view_android_jni_impl.h +++ b/shell/platform/android/platform_view_android_jni_impl.h @@ -66,7 +66,8 @@ class PlatformViewAndroidJNIImpl final : public PlatformViewAndroidJNI { void FlutterViewEndFrame() override; - void FlutterViewCreateOverlaySurface() override; + std::unique_ptr + FlutterViewCreateOverlaySurface() override; std::unique_ptr> FlutterViewComputePlatformResolvedLocale( diff --git a/shell/platform/android/surface/BUILD.gn b/shell/platform/android/surface/BUILD.gn new file mode 100644 index 0000000000000..7764b932a9eac --- /dev/null +++ b/shell/platform/android/surface/BUILD.gn @@ -0,0 +1,54 @@ +# 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. + +source_set("surface") { + sources = [ + "android_surface.cc", + "android_surface.h", + ] + + public_configs = [ "//flutter:config" ] + + deps = [ + ":native_window", + "//flutter/flow", + "//flutter/fml", + "//flutter/shell/platform/android/context", + "//flutter/shell/platform/android/jni", + "//third_party/skia", + ] +} + +source_set("native_window") { + sources = [ + "android_native_window.cc", + "android_native_window.h", + ] + + public_configs = [ "//flutter:config" ] + + deps = [ + "//flutter/fml", + "//third_party/skia", + ] +} + +source_set("surface_mock") { + testonly = true + + sources = [ + "android_surface_mock.cc", + "android_surface_mock.h", + ] + + public_configs = [ "//flutter:config" ] + + deps = [ + ":surface", + "//flutter/shell/gpu:gpu_surface_gl", + "//third_party/googletest:gmock", + "//third_party/googletest:gtest", + "//third_party/skia", + ] +} diff --git a/shell/platform/android/android_native_window.cc b/shell/platform/android/surface/android_native_window.cc similarity index 80% rename from shell/platform/android/android_native_window.cc rename to shell/platform/android/surface/android_native_window.cc index fac43fa89d45b..e701b986f48e9 100644 --- a/shell/platform/android/android_native_window.cc +++ b/shell/platform/android/surface/android_native_window.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "flutter/shell/platform/android/android_native_window.h" +#include "flutter/shell/platform/android/surface/android_native_window.h" namespace flutter { @@ -10,7 +10,9 @@ AndroidNativeWindow::AndroidNativeWindow(Handle window) : window_(window) {} AndroidNativeWindow::~AndroidNativeWindow() { if (window_ != nullptr) { +#if OS_ANDROID ANativeWindow_release(window_); +#endif // OS_ANDROID window_ = nullptr; } } @@ -24,9 +26,13 @@ AndroidNativeWindow::Handle AndroidNativeWindow::handle() const { } SkISize AndroidNativeWindow::GetSize() const { +#if OS_ANDROID return window_ == nullptr ? SkISize::Make(0, 0) : SkISize::Make(ANativeWindow_getWidth(window_), ANativeWindow_getHeight(window_)); +#else // OS_ANDROID + return SkISize::Make(0, 0); +#endif // OS_ANDROID } } // namespace flutter diff --git a/shell/platform/android/android_native_window.h b/shell/platform/android/surface/android_native_window.h similarity index 87% rename from shell/platform/android/android_native_window.h rename to shell/platform/android/surface/android_native_window.h index 7e9626d63d8ea..bcb1c19e5a292 100644 --- a/shell/platform/android/android_native_window.h +++ b/shell/platform/android/surface/android_native_window.h @@ -5,10 +5,14 @@ #ifndef FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_NATIVE_WINDOW_H_ #define FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_NATIVE_WINDOW_H_ +#include "flutter/fml/build_config.h" + +#if OS_ANDROID #include +#endif // OS_ANDROID + #include "flutter/fml/macros.h" #include "flutter/fml/memory/ref_counted.h" -#include "flutter/fml/memory/ref_ptr.h" #include "third_party/skia/include/core/SkSize.h" namespace flutter { @@ -16,7 +20,11 @@ namespace flutter { class AndroidNativeWindow : public fml::RefCountedThreadSafe { public: +#if OS_ANDROID using Handle = ANativeWindow*; +#else // OS_ANDROID + using Handle = std::nullptr_t; +#endif // OS_ANDROID bool IsValid() const; diff --git a/shell/platform/android/surface/android_surface.cc b/shell/platform/android/surface/android_surface.cc new file mode 100644 index 0000000000000..1f5032d8917cf --- /dev/null +++ b/shell/platform/android/surface/android_surface.cc @@ -0,0 +1,11 @@ +// 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/android/surface/android_surface.h" + +namespace flutter { + +AndroidSurface::~AndroidSurface() = default; + +} // namespace flutter diff --git a/shell/platform/android/android_surface.h b/shell/platform/android/surface/android_surface.h similarity index 73% rename from shell/platform/android/android_surface.h rename to shell/platform/android/surface/android_surface.h index 4997588b6a649..26604f4ff7717 100644 --- a/shell/platform/android/android_surface.h +++ b/shell/platform/android/surface/android_surface.h @@ -5,25 +5,20 @@ #ifndef FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_SURFACE_H_ #define FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_SURFACE_H_ -#include - #include "flutter/flow/surface.h" #include "flutter/fml/macros.h" -#include "flutter/fml/platform/android/jni_util.h" -#include "flutter/fml/platform/android/jni_weak_ref.h" -#include "flutter/shell/common/platform_view.h" -#include "flutter/shell/platform/android/android_context.h" -#include "flutter/shell/platform/android/android_native_window.h" +#include "flutter/shell/platform/android/context/android_context.h" #include "flutter/shell/platform/android/jni/platform_view_android_jni.h" +#include "flutter/shell/platform/android/surface/android_native_window.h" #include "third_party/skia/include/core/SkSize.h" namespace flutter { class AndroidSurface { public: - static std::unique_ptr Create( + using Factory = std::function( std::shared_ptr android_context, - std::shared_ptr jni_facade); + std::shared_ptr jni_facade)>; virtual ~AndroidSurface(); diff --git a/shell/platform/android/surface/android_surface_mock.cc b/shell/platform/android/surface/android_surface_mock.cc new file mode 100644 index 0000000000000..5b0e7adad8cdd --- /dev/null +++ b/shell/platform/android/surface/android_surface_mock.cc @@ -0,0 +1,29 @@ +// 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/android/surface/android_surface_mock.h" + +namespace flutter { + +std::unique_ptr AndroidSurfaceMock::GLContextMakeCurrent() { + return std::make_unique(/*static_result=*/true); +} + +bool AndroidSurfaceMock::GLContextClearCurrent() { + return true; +} + +bool AndroidSurfaceMock::GLContextPresent() { + return true; +} + +intptr_t AndroidSurfaceMock::GLContextFBO() const { + return 0; +} + +ExternalViewEmbedder* AndroidSurfaceMock::GetExternalViewEmbedder() { + return nullptr; +} + +} // namespace flutter diff --git a/shell/platform/android/surface/android_surface_mock.h b/shell/platform/android/surface/android_surface_mock.h new file mode 100644 index 0000000000000..23c3083567c50 --- /dev/null +++ b/shell/platform/android/surface/android_surface_mock.h @@ -0,0 +1,59 @@ +// 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_ANDROID_ANDROID_SURFACE_MOCK_H_ +#define FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_SURFACE_MOCK_H_ + +#include "flutter/shell/gpu/gpu_surface_gl.h" +#include "flutter/shell/platform/android/surface/android_surface.h" +#include "gmock/gmock.h" + +namespace flutter { + +//------------------------------------------------------------------------------ +/// Mock for |AndroidSurface|. This implementation can be used in unit +/// tests without requiring the Android toolchain. +/// +class AndroidSurfaceMock final : public GPUSurfaceGLDelegate, + public AndroidSurface { + public: + MOCK_METHOD(bool, IsValid, (), (const, override)); + + MOCK_METHOD(void, TeardownOnScreenContext, (), (override)); + + MOCK_METHOD(std::unique_ptr, + CreateGPUSurface, + (GrContext * gr_context), + (override)); + + MOCK_METHOD(bool, OnScreenSurfaceResize, (const SkISize& size), (override)); + + MOCK_METHOD(bool, ResourceContextMakeCurrent, (), (override)); + + MOCK_METHOD(bool, ResourceContextClearCurrent, (), (override)); + + MOCK_METHOD(bool, + SetNativeWindow, + (fml::RefPtr window), + (override)); + + // |GPUSurfaceGLDelegate| + std::unique_ptr GLContextMakeCurrent() override; + + // |GPUSurfaceGLDelegate| + bool GLContextClearCurrent() override; + + // |GPUSurfaceGLDelegate| + bool GLContextPresent() override; + + // |GPUSurfaceGLDelegate| + intptr_t GLContextFBO() const override; + + // |GPUSurfaceGLDelegate| + ExternalViewEmbedder* GetExternalViewEmbedder() override; +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_ANDROID_ANDROID_SURFACE_MOCK_H_ diff --git a/shell/platform/darwin/ios/ios_surface.h b/shell/platform/darwin/ios/ios_surface.h index fc795b0e62fb1..5fb6f78f6d7cc 100644 --- a/shell/platform/darwin/ios/ios_surface.h +++ b/shell/platform/darwin/ios/ios_surface.h @@ -62,7 +62,11 @@ class IOSSurface : public ExternalViewEmbedder { void CancelFrame() override; // |ExternalViewEmbedder| - void BeginFrame(SkISize frame_size, GrContext* context, double device_pixel_ratio) override; + void BeginFrame(SkISize frame_size, + GrContext* context, + double device_pixel_ratio, + fml::RefPtr raster_thread_merger) override; + // |ExternalViewEmbedder| void PrerollCompositeEmbeddedView(int view_id, std::unique_ptr params) override; diff --git a/shell/platform/darwin/ios/ios_surface.mm b/shell/platform/darwin/ios/ios_surface.mm index 36598c1b96a2b..26eca6cf26b90 100644 --- a/shell/platform/darwin/ios/ios_surface.mm +++ b/shell/platform/darwin/ios/ios_surface.mm @@ -94,7 +94,10 @@ bool IsIosEmbeddedViewsPreviewEnabled() { } // |ExternalViewEmbedder| -void IOSSurface::BeginFrame(SkISize frame_size, GrContext* context, double device_pixel_ratio) { +void IOSSurface::BeginFrame(SkISize frame_size, + GrContext* context, + double device_pixel_ratio, + fml::RefPtr raster_thread_merger) { TRACE_EVENT0("flutter", "IOSSurface::BeginFrame"); FML_CHECK(platform_views_controller_ != nullptr); platform_views_controller_->SetFrameSize(frame_size); diff --git a/shell/platform/embedder/embedder_external_view_embedder.cc b/shell/platform/embedder/embedder_external_view_embedder.cc index 752efec53998c..fcb8067e2a9e0 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.cc +++ b/shell/platform/embedder/embedder_external_view_embedder.cc @@ -47,9 +47,11 @@ void EmbedderExternalViewEmbedder::CancelFrame() { } // |ExternalViewEmbedder| -void EmbedderExternalViewEmbedder::BeginFrame(SkISize frame_size, - GrContext* context, - double device_pixel_ratio) { +void EmbedderExternalViewEmbedder::BeginFrame( + SkISize frame_size, + GrContext* context, + double device_pixel_ratio, + fml::RefPtr raster_thread_merger) { Reset(); pending_frame_size_ = frame_size; diff --git a/shell/platform/embedder/embedder_external_view_embedder.h b/shell/platform/embedder/embedder_external_view_embedder.h index c78e39fd75415..6fc0eec0c9ca6 100644 --- a/shell/platform/embedder/embedder_external_view_embedder.h +++ b/shell/platform/embedder/embedder_external_view_embedder.h @@ -73,9 +73,11 @@ class EmbedderExternalViewEmbedder final : public ExternalViewEmbedder { void CancelFrame() override; // |ExternalViewEmbedder| - void BeginFrame(SkISize frame_size, - GrContext* context, - double device_pixel_ratio) override; + void BeginFrame( + SkISize frame_size, + GrContext* context, + double device_pixel_ratio, + fml::RefPtr raster_thread_merger) override; // |ExternalViewEmbedder| void PrerollCompositeEmbeddedView( diff --git a/testing/BUILD.gn b/testing/BUILD.gn index 1fbfa4b3d3874..0a8f690bb080f 100644 --- a/testing/BUILD.gn +++ b/testing/BUILD.gn @@ -18,6 +18,7 @@ source_set("testing_lib") { public_deps = [ "//flutter/fml", + "//third_party/googletest:gmock", "//third_party/googletest:gtest", ] public_configs = [ "//flutter:config" ] diff --git a/testing/run_tests.py b/testing/run_tests.py index 8ba97770a1719..b799030aba56c 100755 --- a/testing/run_tests.py +++ b/testing/run_tests.py @@ -131,16 +131,17 @@ def RunCCTests(build_dir, filter): RunEngineExecutable(build_dir, 'runtime_unittests', filter, shuffle_flags) - # https://github.com/flutter/flutter/issues/36295 if not IsWindows(): + # https://github.com/flutter/flutter/issues/36295 RunEngineExecutable(build_dir, 'shell_unittests', filter, shuffle_flags) + # https://github.com/google/googletest/issues/2490 + RunEngineExecutable(build_dir, 'android_external_view_embedder_unittests', filter, shuffle_flags) + RunEngineExecutable(build_dir, 'jni_unittests', filter, shuffle_flags) RunEngineExecutable(build_dir, 'ui_unittests', filter, shuffle_flags) RunEngineExecutable(build_dir, 'testing_unittests', filter, shuffle_flags) - RunEngineExecutable(build_dir, 'android_external_view_embedder_unittests', filter, shuffle_flags) - # These unit-tests are Objective-C and can only run on Darwin. if IsMac(): RunEngineExecutable(build_dir, 'flutter_channels_unittests', filter, shuffle_flags)