diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index ee34aba0ce224..52ffbc92c2d32 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1462,6 +1462,7 @@ FILE: ../../../flutter/shell/platform/fuchsia/flutter/runner_tzdata_unittest.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/runner_unittest.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/surface.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/surface.h +FILE: ../../../flutter/shell/platform/fuchsia/flutter/surface_producer.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/task_runner_adapter.cc FILE: ../../../flutter/shell/platform/fuchsia/flutter/task_runner_adapter.h FILE: ../../../flutter/shell/platform/fuchsia/flutter/unique_fdio_ns.h diff --git a/shell/platform/fuchsia/flutter/BUILD.gn b/shell/platform/fuchsia/flutter/BUILD.gn index 694d22bffc8f2..0a25f697c07f9 100644 --- a/shell/platform/fuchsia/flutter/BUILD.gn +++ b/shell/platform/fuchsia/flutter/BUILD.gn @@ -90,6 +90,7 @@ template("runner_sources") { "runner.h", "surface.cc", "surface.h", + "surface_producer.h", "task_runner_adapter.cc", "task_runner_adapter.h", "unique_fdio_ns.h", @@ -465,6 +466,7 @@ executable("flutter_runner_unittests") { "tests/engine_unittests.cc", "tests/fake_session_unittests.cc", "tests/flutter_runner_product_configuration_unittests.cc", + "tests/fuchsia_external_view_embedder_unittests.cc", "tests/gfx_session_connection_unittests.cc", ] diff --git a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc index 1071d4e881dbb..413ab7ad53cf2 100644 --- a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc +++ b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.cc @@ -17,7 +17,7 @@ FlatlandExternalViewEmbedder::FlatlandExternalViewEmbedder( fidl::InterfaceRequest parent_viewport_watcher_request, FlatlandConnection& flatland, - VulkanSurfaceProducer& surface_producer, + SurfaceProducer& surface_producer, bool intercept_all_input) : flatland_(flatland), surface_producer_(surface_producer) { flatland_.flatland()->CreateView(std::move(view_creation_token), @@ -309,7 +309,7 @@ void FlatlandExternalViewEmbedder::SubmitFrame( { TRACE_EVENT0("flutter", "PresentSurfaces"); - surface_producer_.OnSurfacesPresented(std::move(frame_surfaces)); + surface_producer_.SubmitSurfaces(std::move(frame_surfaces)); } // Submit the underlying render-backend-specific frame for processing. diff --git a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h index 9ee0c42dcdff3..72e794022ec60 100644 --- a/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h +++ b/shell/platform/fuchsia/flutter/flatland_external_view_embedder.h @@ -25,11 +25,8 @@ #include "third_party/skia/include/core/SkSize.h" #include "third_party/skia/include/gpu/GrDirectContext.h" -#include "flutter/flow/embedded_views.h" -#include "flutter/fml/macros.h" - #include "flatland_connection.h" -#include "vulkan_surface_producer.h" +#include "surface_producer.h" namespace flutter_runner { @@ -50,7 +47,7 @@ class FlatlandExternalViewEmbedder final fidl::InterfaceRequest parent_viewport_watcher_request, FlatlandConnection& flatland, - VulkanSurfaceProducer& surface_producer, + SurfaceProducer& surface_producer, bool intercept_all_input = false); ~FlatlandExternalViewEmbedder(); @@ -167,7 +164,7 @@ class FlatlandExternalViewEmbedder final }; FlatlandConnection& flatland_; - VulkanSurfaceProducer& surface_producer_; + SurfaceProducer& surface_producer_; fuchsia::ui::composition::ParentViewportWatcherPtr parent_viewport_watcher_; fuchsia::ui::composition::TransformId root_transform_id_; diff --git a/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.cc b/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.cc index c485c04edb482..eb6783113f724 100644 --- a/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.cc +++ b/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.cc @@ -17,12 +17,6 @@ namespace flutter_runner { namespace { -// Layer separation is as infinitesimal as possible without introducing -// Z-fighting. -constexpr float kScenicZElevationBetweenLayers = 0.0001f; -constexpr float kScenicZElevationForPlatformView = 100.f; -constexpr float kScenicElevationForInputInterceptor = 500.f; - ViewMutators ParseMutatorStack(const flutter::MutatorsStack& mutators_stack) { ViewMutators mutators; SkMatrix total_transform = SkMatrix::I(); @@ -110,7 +104,7 @@ FuchsiaExternalViewEmbedder::FuchsiaExternalViewEmbedder( fuchsia::ui::views::ViewToken view_token, scenic::ViewRefPair view_ref_pair, GfxSessionConnection& session, - VulkanSurfaceProducer& surface_producer, + SurfaceProducer& surface_producer, bool intercept_all_input) : session_(session), surface_producer_(surface_producer), @@ -491,7 +485,7 @@ void FuchsiaExternalViewEmbedder::SubmitFrame( // This does not cause visual problems in practice, but probably has // performance implications. const SkAlpha layer_opacity = - first_layer ? SK_AlphaOPAQUE : SK_AlphaOPAQUE - 1; + first_layer ? kBackgroundLayerOpacity : kOverlayLayerOpacity; const float layer_elevation = kScenicZElevationBetweenLayers * scenic_layer_index + embedded_views_height; @@ -569,7 +563,7 @@ void FuchsiaExternalViewEmbedder::SubmitFrame( { TRACE_EVENT0("flutter", "PresentSurfaces"); - surface_producer_.OnSurfacesPresented(std::move(frame_surfaces)); + surface_producer_.SubmitSurfaces(std::move(frame_surfaces)); } // Submit the underlying render-backend-specific frame for processing. diff --git a/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h b/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h index a5eb09d23d442..7021a3df3332e 100644 --- a/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h +++ b/shell/platform/fuchsia/flutter/fuchsia_external_view_embedder.h @@ -28,7 +28,7 @@ #include "third_party/skia/include/gpu/GrDirectContext.h" #include "gfx_session_connection.h" -#include "vulkan_surface_producer.h" +#include "surface_producer.h" namespace flutter_runner { @@ -64,11 +64,19 @@ struct ViewMutators { // correctly in a unified scene. class FuchsiaExternalViewEmbedder final : public flutter::ExternalViewEmbedder { public: + // Layer separation is as infinitesimal as possible without introducing + // Z-fighting. + constexpr static float kScenicZElevationBetweenLayers = 0.0001f; + constexpr static float kScenicZElevationForPlatformView = 100.f; + constexpr static float kScenicElevationForInputInterceptor = 500.f; + constexpr static SkAlpha kBackgroundLayerOpacity = SK_AlphaOPAQUE; + constexpr static SkAlpha kOverlayLayerOpacity = SK_AlphaOPAQUE - 1; + FuchsiaExternalViewEmbedder(std::string debug_label, fuchsia::ui::views::ViewToken view_token, scenic::ViewRefPair view_ref_pair, GfxSessionConnection& session, - VulkanSurfaceProducer& surface_producer, + SurfaceProducer& surface_producer, bool intercept_all_input = false); ~FuchsiaExternalViewEmbedder(); @@ -170,7 +178,7 @@ class FuchsiaExternalViewEmbedder final : public flutter::ExternalViewEmbedder { }; GfxSessionConnection& session_; - VulkanSurfaceProducer& surface_producer_; + SurfaceProducer& surface_producer_; scenic::View root_view_; scenic::EntityNode metrics_node_; diff --git a/shell/platform/fuchsia/flutter/gfx_session_connection.cc b/shell/platform/fuchsia/flutter/gfx_session_connection.cc index 9846f67bc8b5d..4a960a224d465 100644 --- a/shell/platform/fuchsia/flutter/gfx_session_connection.cc +++ b/shell/platform/fuchsia/flutter/gfx_session_connection.cc @@ -249,11 +249,9 @@ GfxSessionConnection::GfxSessionConnection( // Scenic retired a given number of frames, so mark them as completed. // Inspect updates must run on the inspect dispatcher. async::PostTask( - inspect_dispatcher_, - [this, num_presents_handled, - now = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds()]() { + inspect_dispatcher_, [this, now = Now(), num_presents_handled]() { presents_completed_.Add(num_presents_handled); - last_frame_completed_.Set(now); + last_frame_completed_.Set(now.ToEpochDelta().ToNanoseconds()); }); if (fire_callback_request_pending_) { @@ -300,17 +298,15 @@ void GfxSessionConnection::Present() { next_present_session_trace_id_); ++next_present_session_trace_id_; - auto now = fml::TimePoint::Now(); + auto now = Now(); present_requested_time_ = now; // Flutter is requesting a frame here, so mark it as such. // Inspect updates must run on the inspect dispatcher. - async::PostTask( - inspect_dispatcher_, - [this, now = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds()]() { - presents_requested_.Add(1); - last_frame_requested_.Set(now); - }); + async::PostTask(inspect_dispatcher_, [this, now]() { + presents_requested_.Add(1); + last_frame_requested_.Set(now.ToEpochDelta().ToNanoseconds()); + }); // Throttle frame submission to Scenic if we already have the maximum amount // of frames in flight. This allows the paint tasks for this frame to execute @@ -333,12 +329,10 @@ void GfxSessionConnection::AwaitVsync(FireCallbackCallback callback) { // Flutter is requesting a vsync here, so mark it as such. // Inspect updates must run on the inspect dispatcher. - async::PostTask( - inspect_dispatcher_, - [this, now = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds()]() { - vsyncs_requested_.Add(1); - last_vsync_requested_.Set(now); - }); + async::PostTask(inspect_dispatcher_, [this, now = Now()]() { + vsyncs_requested_.Add(1); + last_vsync_requested_.Set(now.ToEpochDelta().ToNanoseconds()); + }); FireCallbackMaybe(); } @@ -352,12 +346,10 @@ void GfxSessionConnection::AwaitVsyncForSecondaryCallback( // Flutter is requesting a secondary vsync here, so mark it as such. // Inspect updates must run on the inspect dispatcher. - async::PostTask( - inspect_dispatcher_, - [this, now = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds()]() { - secondary_vsyncs_completed_.Add(1); - last_secondary_vsync_completed_.Set(now); - }); + async::PostTask(inspect_dispatcher_, [this, now = Now()]() { + secondary_vsyncs_completed_.Add(1); + last_secondary_vsync_completed_.Set(now.ToEpochDelta().ToNanoseconds()); + }); FlutterFrameTimes times = GetTargetTimesHelper(/*secondary_callback=*/true); fire_callback_(times.frame_start, times.frame_target); @@ -391,12 +383,10 @@ void GfxSessionConnection::PresentSession() { // Flutter is presenting a frame here, so mark it as such. // Inspect updates must run on the inspect dispatcher. - async::PostTask( - inspect_dispatcher_, - [this, now = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds()]() { - presents_submitted_.Add(1); - last_frame_presented_.Set(now); - }); + async::PostTask(inspect_dispatcher_, [this, now = Now()]() { + presents_submitted_.Add(1); + last_frame_presented_.Set(now.ToEpochDelta().ToNanoseconds()); + }); session_wrapper_.Present2( /*requested_presentation_time=*/next_latch_point.ToEpochDelta() @@ -441,12 +431,10 @@ void GfxSessionConnection::FireCallbackMaybe() { // Scenic completed a vsync here, so mark it as such. // Inspect updates must run on the inspect dispatcher. - async::PostTask( - inspect_dispatcher_, - [this, now = fml::TimePoint::Now().ToEpochDelta().ToNanoseconds()]() { - vsyncs_completed_.Add(1); - last_vsync_completed_.Set(now); - }); + async::PostTask(inspect_dispatcher_, [this, now = Now()]() { + vsyncs_completed_.Add(1); + last_vsync_completed_.Set(now.ToEpochDelta().ToNanoseconds()); + }); fire_callback_(times.frame_start, times.frame_target); } else { diff --git a/shell/platform/fuchsia/flutter/surface_producer.h b/shell/platform/fuchsia/flutter/surface_producer.h new file mode 100644 index 0000000000000..c09cb187ce47e --- /dev/null +++ b/shell/platform/fuchsia/flutter/surface_producer.h @@ -0,0 +1,78 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_SURFACE_PRODUCER_H_ +#define FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_SURFACE_PRODUCER_H_ + +#include +#include + +#include +#include +#include + +#include "third_party/skia/include/core/SkSize.h" +#include "third_party/skia/include/core/SkSurface.h" + +namespace flutter_runner { + +using ReleaseImageCallback = std::function; + +// This represents an abstract notion of a "rendering surface", which is a +// destination for pixels drawn by some rendering engine. In this case, the +// rendering engine is Skia combined with one of its rendering backends. +// +// In addition to allowing Skia-based drawing via `GetSkiaSurface`, this +// rendering surface can be shared with Scenic via `SetImageId`, +// `GetBufferCollectionImportToken`, `GetAcquireFene`, and `GetReleaseFence`. +class SurfaceProducerSurface { + public: + virtual ~SurfaceProducerSurface() = default; + + virtual bool IsValid() const = 0; + + virtual SkISize GetSize() const = 0; + + virtual void SetImageId(uint32_t image_id) = 0; + + virtual uint32_t GetImageId() = 0; + + virtual sk_sp GetSkiaSurface() const = 0; + + virtual fuchsia::ui::composition::BufferCollectionImportToken + GetBufferCollectionImportToken() = 0; + + virtual zx::event GetAcquireFence() = 0; + + virtual zx::event GetReleaseFence() = 0; + + virtual void SetReleaseImageCallback( + ReleaseImageCallback release_image_callback) = 0; + + virtual size_t AdvanceAndGetAge() = 0; + + virtual bool FlushSessionAcquireAndReleaseEvents() = 0; + + virtual void SignalWritesFinished( + const std::function& on_writes_committed) = 0; +}; + +// This represents an abstract notion of "surface producer", which serves as a +// source for `SurfaceProducerSurface`s. Produces surfaces should be returned +// to this `SurfaceProducer` via `SubmitSurfaces`, at which point they will be +// shared with Scenic. +class SurfaceProducer { + public: + virtual ~SurfaceProducer() = default; + + virtual std::unique_ptr ProduceSurface( + const SkISize& size) = 0; + + virtual void SubmitSurfaces( + std::vector> surfaces) = 0; +}; + +} // namespace flutter_runner + +#endif // FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_SURFACE_PRODUCER_H_ diff --git a/shell/platform/fuchsia/flutter/tests/fake_session_unittests.cc b/shell/platform/fuchsia/flutter/tests/fake_session_unittests.cc index c7526229bb5cf..966d133f499f1 100644 --- a/shell/platform/fuchsia/flutter/tests/fake_session_unittests.cc +++ b/shell/platform/fuchsia/flutter/tests/fake_session_unittests.cc @@ -85,8 +85,25 @@ Matcher IsEmptySceneGraph() { return FieldsAre(IsEmpty(), IsEmpty(), IsEmpty(), kInvalidFakeResourceId); } +MATCHER_P2(IsEntityNodeSceneGraph, node_label, node_id, "") { + static_assert(std::is_same_v>); + static_assert( + std::is_constructible_v>); + static_assert( + std::is_same_v>); + + return ExplainMatchResult( + FieldsAre( + IsEmpty(), + AllOf(SizeIs(1u), + Contains(Pair(node_id, Pointee(IsEntityNode(node_id, node_label, + IsEmpty()))))), + _, kInvalidFakeResourceId), + arg, result_listener); +} + MATCHER_P5(IsBasicSceneGraph, - debug_label, + view_label, node_label, view_holder_koid, view_ref_control_koid, @@ -94,9 +111,9 @@ MATCHER_P5(IsBasicSceneGraph, "") { static_assert(std::is_same_v>); static_assert( - std::is_same_v>); + std::is_constructible_v>); static_assert( - std::is_same_v>); + std::is_constructible_v>); static_assert( std::is_same_v>); static_assert( @@ -114,7 +131,7 @@ MATCHER_P5(IsBasicSceneGraph, FakeResource::kDefaultEmptyEventMask, VariantWith(FieldsAre( view_holder_koid, view_ref_control_koid, - view_ref_koid, debug_label, + view_ref_koid, view_label, ElementsAre(Pointee(IsEntityNode( _, node_label, IsEmpty()))), FakeView::kDebugBoundsDisbaled))))))), @@ -148,11 +165,11 @@ class FakeSessionTest : public ::testing::Test, private: // |fuchsia::ui::scenic::SessionListener| - void OnScenicError(std::string error) override { FML_CHECK(false); } + void OnScenicError(std::string error) override { FAIL(); } // |fuchsia::ui::scenic::SessionListener| void OnScenicEvent(std::vector events) override { - FML_CHECK(false); + FAIL(); } async::TestLoop loop_; // Must come before FIDL bindings. @@ -177,95 +194,82 @@ TEST_F(FakeSessionTest, Initialization) { } TEST_F(FakeSessionTest, DebugLabel) { - const std::string debug_label = GetCurrentTestName(); scenic::Session session = CreateSession(); // Set the session's debug name. The `SetDebugName` hasn't been processed // yet, so the session's view of the debug name is still empty. - session.SetDebugName(debug_label); + const std::string kDebugLabel = GetCurrentTestName(); + session.SetDebugName(kDebugLabel); + session.Flush(); // Bypass local command caching. EXPECT_EQ(fake_session().debug_name(), ""); // Pump the loop; the contents of the initial `SetDebugName` should be // processed. loop().RunUntilIdle(); - EXPECT_EQ(fake_session().debug_name(), debug_label); + EXPECT_EQ(fake_session().debug_name(), kDebugLabel); } -TEST_F(FakeSessionTest, SimpleResourceLifecycle) { - const std::string node_label = "EntityNode"; +TEST_F(FakeSessionTest, CommandQueueInvariants) { scenic::Session session = CreateSession(); + // The scene graph is initially empty. + EXPECT_THAT(fake_session().SceneGraph(), IsEmptySceneGraph()); + // Create entity node for testing; no creation commands have been processed // yet, so the session's view of the scene graph is empty. std::optional node(&session); + session.Flush(); // Bypass local command caching. EXPECT_EQ(fake_session().command_queue().size(), 0u); EXPECT_THAT(fake_session().SceneGraph(), IsEmptySceneGraph()); // Pump the loop; the initial creation command should be enqueued but still // not processed yet, so the session's view of the scene graph is empty. loop().RunUntilIdle(); - EXPECT_GE(fake_session().command_queue().size(), 0u); + EXPECT_GT(fake_session().command_queue().size(), 0u); EXPECT_THAT(fake_session().SceneGraph(), IsEmptySceneGraph()); // Present initial scene graph. The `Present` hasn't been processed yet, so // the session's view of the scene graph is still empty. session.Present2(0u, 0u, [](auto...) {}); - EXPECT_GE(fake_session().command_queue().size(), 0u); + EXPECT_GT(fake_session().command_queue().size(), 0u); EXPECT_THAT(fake_session().SceneGraph(), IsEmptySceneGraph()); // Pump the loop; the contents of the initial `Present` should be processed. loop().RunUntilIdle(); EXPECT_EQ(fake_session().command_queue().size(), 0u); - { - auto scene_graph = fake_session().SceneGraph(); - EXPECT_EQ(scene_graph.root_view_id, kInvalidFakeResourceId); - EXPECT_EQ(scene_graph.buffer_collection_map.size(), 0u); - EXPECT_EQ(scene_graph.resource_map.size(), 1u); - EXPECT_EQ(scene_graph.labels_map.size(), 1u); - ASSERT_EQ(scene_graph.labels_map.count(""), 1u); - - const auto first_resource_it = scene_graph.resource_map.begin(); - const auto null_label_resources = scene_graph.labels_map[""]; - EXPECT_EQ(null_label_resources.size(), 1u); - EXPECT_FALSE(null_label_resources[0].expired()); - EXPECT_EQ(null_label_resources[0].lock(), first_resource_it->second); - EXPECT_THAT(first_resource_it->second, - Pointee(IsEntityNode(_, "", IsEmpty()))); - } + EXPECT_THAT(fake_session().SceneGraph(), + IsEntityNodeSceneGraph("", node->id())); +} + +TEST_F(FakeSessionTest, SimpleResourceLifecycle) { + scenic::Session session = CreateSession(); + + // The scene graph is initially empty. + EXPECT_THAT(fake_session().SceneGraph(), IsEmptySceneGraph()); + + // Present an initial entity node, pumping the loop to process commands. + std::optional node(&session); + session.Present2(0u, 0u, [](auto...) {}); + loop().RunUntilIdle(); + EXPECT_THAT(fake_session().SceneGraph(), + IsEntityNodeSceneGraph("", node->id())); // Present a simple property update on the test entity node. - node->SetLabel(node_label); + const std::string kNodeLabel = "EntityNode"; + node->SetLabel(kNodeLabel); session.Present2(0u, 0u, [](auto...) {}); loop().RunUntilIdle(); - EXPECT_EQ(fake_session().command_queue().size(), 0u); - { - auto scene_graph = fake_session().SceneGraph(); - EXPECT_EQ(scene_graph.root_view_id, kInvalidFakeResourceId); - EXPECT_EQ(scene_graph.buffer_collection_map.size(), 0u); - EXPECT_EQ(scene_graph.resource_map.size(), 1u); - EXPECT_EQ(scene_graph.labels_map.size(), 1u); - ASSERT_EQ(scene_graph.labels_map.count(""), 0u); - ASSERT_EQ(scene_graph.labels_map.count(node_label), 1u); - - const auto first_resource_it = scene_graph.resource_map.begin(); - const auto node_label_resources = scene_graph.labels_map[node_label]; - EXPECT_EQ(node_label_resources.size(), 1u); - EXPECT_FALSE(node_label_resources[0].expired()); - EXPECT_EQ(node_label_resources[0].lock(), first_resource_it->second); - EXPECT_THAT(first_resource_it->second, - Pointee(IsEntityNode(_, node_label, IsEmpty()))); - } + EXPECT_THAT(fake_session().SceneGraph(), + IsEntityNodeSceneGraph(kNodeLabel, node->id())); // Present the destruction of the entity node. node.reset(); session.Present2(0u, 0u, [](auto...) {}); loop().RunUntilIdle(); - EXPECT_EQ(fake_session().command_queue().size(), 0u); EXPECT_THAT(fake_session().SceneGraph(), IsEmptySceneGraph()); } TEST_F(FakeSessionTest, ResourceReferenceCounting) { - const std::string node_label = "EntityNode"; scenic::Session session = CreateSession(); // Present a chain of 4 entity nodes for testing. @@ -274,8 +278,9 @@ TEST_F(FakeSessionTest, ResourceReferenceCounting) { std::optional(&session), std::optional(&session), std::optional(&session)}; + const std::string kNodeLabel = "EntityNode"; for (size_t i = 0; i < 4; i++) { - nodes[i]->SetLabel(node_label + std::string(1, '0' + i)); + nodes[i]->SetLabel(kNodeLabel + std::string(1, '0' + i)); if (i < 3) { nodes[i]->AddChild(*nodes[i + 1]); } @@ -287,14 +292,14 @@ TEST_F(FakeSessionTest, ResourceReferenceCounting) { EXPECT_EQ(scene_graph.root_view_id, kInvalidFakeResourceId); EXPECT_EQ(scene_graph.buffer_collection_map.size(), 0u); EXPECT_EQ(scene_graph.resource_map.size(), 4u); - EXPECT_EQ(scene_graph.labels_map.size(), 4u); + EXPECT_EQ(scene_graph.label_map.size(), 4u); for (size_t i = 0; i < 4; i++) { - const std::string node_i_label = node_label + std::string(1, '0' + i); + const std::string node_i_label = kNodeLabel + std::string(1, '0' + i); ASSERT_EQ(scene_graph.resource_map.count(nodes[i]->id()), 1u); - ASSERT_EQ(scene_graph.labels_map.count(node_i_label), 1u); + ASSERT_EQ(scene_graph.label_map.count(node_i_label), 1u); const auto node_i = scene_graph.resource_map[nodes[i]->id()]; - const auto node_i_label_resources = scene_graph.labels_map[node_i_label]; + const auto node_i_label_resources = scene_graph.label_map[node_i_label]; EXPECT_EQ(node_i_label_resources.size(), 1u); EXPECT_FALSE(node_i_label_resources[0].expired()); EXPECT_EQ(node_i_label_resources[0].lock(), node_i); @@ -303,13 +308,13 @@ TEST_F(FakeSessionTest, ResourceReferenceCounting) { EXPECT_THAT( scene_graph.resource_map[nodes[0]->id()], Pointee(IsEntityNode( - nodes[0]->id(), node_label + std::string(1, '0'), + nodes[0]->id(), kNodeLabel + std::string(1, '0'), ElementsAre(Pointee(IsEntityNode( - nodes[1]->id(), node_label + std::string(1, '1'), + nodes[1]->id(), kNodeLabel + std::string(1, '1'), ElementsAre(Pointee(IsEntityNode( - nodes[2]->id(), node_label + std::string(1, '2'), + nodes[2]->id(), kNodeLabel + std::string(1, '2'), ElementsAre(Pointee(IsEntityNode( - nodes[3]->id(), node_label + std::string(1, '3'), + nodes[3]->id(), kNodeLabel + std::string(1, '3'), IsEmpty())))))))))))); } @@ -322,14 +327,14 @@ TEST_F(FakeSessionTest, ResourceReferenceCounting) { EXPECT_EQ(scene_graph.root_view_id, kInvalidFakeResourceId); EXPECT_EQ(scene_graph.buffer_collection_map.size(), 0u); EXPECT_EQ(scene_graph.resource_map.size(), 3u); - EXPECT_EQ(scene_graph.labels_map.size(), 3u); + EXPECT_EQ(scene_graph.label_map.size(), 3u); for (size_t i = 1; i < 4; i++) { - const std::string node_i_label = node_label + std::string(1, '0' + i); + const std::string node_i_label = kNodeLabel + std::string(1, '0' + i); ASSERT_EQ(scene_graph.resource_map.count(nodes[i]->id()), 1u); - ASSERT_EQ(scene_graph.labels_map.count(node_i_label), 1u); + ASSERT_EQ(scene_graph.label_map.count(node_i_label), 1u); const auto node_i = scene_graph.resource_map[nodes[i]->id()]; - const auto node_i_label_resources = scene_graph.labels_map[node_i_label]; + const auto node_i_label_resources = scene_graph.label_map[node_i_label]; EXPECT_EQ(node_i_label_resources.size(), 1u); EXPECT_FALSE(node_i_label_resources[0].expired()); EXPECT_EQ(node_i_label_resources[0].lock(), node_i); @@ -338,11 +343,11 @@ TEST_F(FakeSessionTest, ResourceReferenceCounting) { EXPECT_EQ(scene_graph.resource_map.count(nodes[0]->id()), 0u); EXPECT_THAT(scene_graph.resource_map[nodes[1]->id()], Pointee(IsEntityNode( - nodes[1]->id(), node_label + std::string(1, '1'), + nodes[1]->id(), kNodeLabel + std::string(1, '1'), ElementsAre(Pointee(IsEntityNode( - nodes[2]->id(), node_label + std::string(1, '2'), + nodes[2]->id(), kNodeLabel + std::string(1, '2'), ElementsAre(Pointee(IsEntityNode( - nodes[3]->id(), node_label + std::string(1, '3'), + nodes[3]->id(), kNodeLabel + std::string(1, '3'), IsEmpty()))))))))); } @@ -356,14 +361,14 @@ TEST_F(FakeSessionTest, ResourceReferenceCounting) { EXPECT_EQ(scene_graph.root_view_id, kInvalidFakeResourceId); EXPECT_EQ(scene_graph.buffer_collection_map.size(), 0u); EXPECT_EQ(scene_graph.resource_map.size(), 2u); - EXPECT_EQ(scene_graph.labels_map.size(), 3u); + EXPECT_EQ(scene_graph.label_map.size(), 3u); for (size_t i = 1; i < 4; i++) { - const std::string node_i_label = node_label + std::string(1, '0' + i); - ASSERT_EQ(scene_graph.labels_map.count(node_i_label), 1u); + const std::string node_i_label = kNodeLabel + std::string(1, '0' + i); + ASSERT_EQ(scene_graph.label_map.count(node_i_label), 1u); ASSERT_EQ(scene_graph.resource_map.count(nodes[i]->id()), i != 2 ? 1u : 0u); - const auto node_i_label_resources = scene_graph.labels_map[node_i_label]; + const auto node_i_label_resources = scene_graph.label_map[node_i_label]; EXPECT_EQ(node_i_label_resources.size(), 1u); EXPECT_FALSE(node_i_label_resources[0].expired()); @@ -377,11 +382,11 @@ TEST_F(FakeSessionTest, ResourceReferenceCounting) { EXPECT_THAT(scene_graph.resource_map[nodes[1]->id()], Pointee(IsEntityNode( - nodes[1]->id(), node_label + std::string(1, '1'), + nodes[1]->id(), kNodeLabel + std::string(1, '1'), ElementsAre(Pointee(IsEntityNode( - nodes[2]->id(), node_label + std::string(1, '2'), + nodes[2]->id(), kNodeLabel + std::string(1, '2'), ElementsAre(Pointee(IsEntityNode( - nodes[3]->id(), node_label + std::string(1, '3'), + nodes[3]->id(), kNodeLabel + std::string(1, '3'), IsEmpty()))))))))); } @@ -395,14 +400,14 @@ TEST_F(FakeSessionTest, ResourceReferenceCounting) { EXPECT_EQ(scene_graph.root_view_id, kInvalidFakeResourceId); EXPECT_EQ(scene_graph.buffer_collection_map.size(), 0u); EXPECT_EQ(scene_graph.resource_map.size(), 1u); - EXPECT_EQ(scene_graph.labels_map.size(), 3u); + EXPECT_EQ(scene_graph.label_map.size(), 3u); for (size_t i = 1; i < 4; i++) { - const std::string node_i_label = node_label + std::string(1, '0' + i); - ASSERT_EQ(scene_graph.labels_map.count(node_i_label), 1u); + const std::string node_i_label = kNodeLabel + std::string(1, '0' + i); + ASSERT_EQ(scene_graph.label_map.count(node_i_label), 1u); ASSERT_EQ(scene_graph.resource_map.count(nodes[i]->id()), i < 2 ? 1u : 0u); - const auto node_i_label_resources = scene_graph.labels_map[node_i_label]; + const auto node_i_label_resources = scene_graph.label_map[node_i_label]; EXPECT_EQ(node_i_label_resources.size(), 1u); EXPECT_FALSE(node_i_label_resources[0].expired()); @@ -416,41 +421,38 @@ TEST_F(FakeSessionTest, ResourceReferenceCounting) { EXPECT_THAT(scene_graph.resource_map[nodes[1]->id()], Pointee(IsEntityNode( - nodes[1]->id(), node_label + std::string(1, '1'), + nodes[1]->id(), kNodeLabel + std::string(1, '1'), ElementsAre(Pointee(IsEntityNode( - nodes[2]->id(), node_label + std::string(1, '2'), + nodes[2]->id(), kNodeLabel + std::string(1, '2'), ElementsAre(Pointee(IsEntityNode( - nodes[3]->id(), node_label + std::string(1, '3'), + nodes[3]->id(), kNodeLabel + std::string(1, '3'), IsEmpty()))))))))); } } TEST_F(FakeSessionTest, BasicSceneGraph) { - const std::string debug_label = GetCurrentTestName(); - const std::string node_label = "ChildNode"; scenic::Session session = CreateSession(); - // Create and present initial scene graph. The `Present` hasn't been - // processed yet, so the session's view of the scene graph is still empty. + // The scene graph is initially empty. + EXPECT_THAT(fake_session().SceneGraph(), IsEmptySceneGraph()); + + // Create and present initial scene graph. + const std::string kViewDebugString = GetCurrentTestName(); + const std::string kNodeLabel = "ChildNode"; fuchsia::ui::views::ViewRef view_ref; auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); auto view_ref_pair = scenic::ViewRefPair::New(); view_ref_pair.view_ref.Clone(&view_ref); scenic::View root_view(&session, std::move(view_token), std::move(view_ref_pair.control_ref), - std::move(view_ref_pair.view_ref), debug_label); + std::move(view_ref_pair.view_ref), kViewDebugString); scenic::EntityNode child_node(&session); - child_node.SetLabel(node_label); + child_node.SetLabel(kNodeLabel); root_view.AddChild(child_node); session.Present2(0u, 0u, [](auto...) {}); - EXPECT_GE(fake_session().command_queue().size(), 0u); - EXPECT_THAT(fake_session().SceneGraph(), IsEmptySceneGraph()); - - // Pump the loop; the contents of the initial `Present` should be processed. loop().RunUntilIdle(); - EXPECT_EQ(fake_session().command_queue().size(), 0u); EXPECT_THAT(fake_session().SceneGraph(), - IsBasicSceneGraph(debug_label, node_label, + IsBasicSceneGraph(kViewDebugString, kNodeLabel, GetPeerKoid(view_holder_token.value.get()), GetPeerKoid(view_ref.reference.get()), GetKoid(view_ref.reference.get()))); diff --git a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_resources.cc b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_resources.cc index 2f7ca047a01bd..222becf5be419 100644 --- a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_resources.cc +++ b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_resources.cc @@ -290,9 +290,9 @@ FakeSceneGraph SceneGraphFromState(const FakeSceneGraphState& state) { } // Snapshot labels in the map. - for (auto& label_resources : state.labels_map) { + for (auto& label_resources : state.label_map) { auto [label_iter, label_success] = - scene_graph.labels_map.emplace(std::make_pair( + scene_graph.label_map.emplace(std::make_pair( label_resources.first, std::vector>())); FML_CHECK(label_success); auto& snapshot_label_resources = label_iter->second; diff --git a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_resources.h b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_resources.h index f737b8511cca2..ea60725e9cb48 100644 --- a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_resources.h +++ b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_resources.h @@ -70,8 +70,8 @@ struct StateT { template struct HandleT { - T value; - zx_koid_t koid; + T value{}; + zx_koid_t koid{ZX_KOID_INVALID}; }; template @@ -82,7 +82,7 @@ struct StateT { }; // Tag type for a Resource "snapshot". The Resource snapshot only stores koids -// for any handles assocaites with the resource; in this way it doesn't have any +// for any handles associated with the resource; in this way it doesn't have any // control over the underlying handle lifetime. // // "snapshot" Resources are generated from "state" Resources during @@ -386,7 +386,7 @@ struct FakeSceneGraphT { std::unordered_map>> resource_map; std::unordered_map>>> - labels_map; + label_map; FakeResourceId root_view_id{kInvalidFakeResourceId}; }; using FakeSceneGraphState = FakeSceneGraphT; diff --git a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_session.cc b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_session.cc index 0e248fdd2e25a..1d83ad98574df 100644 --- a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_session.cc +++ b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_session.cc @@ -214,9 +214,9 @@ void FakeSession::AddResource(FakeResourceState&& resource) { } // Add to initial spot in labels map. - auto empty_label_it = scene_graph_.labels_map.find(""); - if (empty_label_it == scene_graph_.labels_map.end()) { - auto [emplace_it, empty_label_success] = scene_graph_.labels_map.emplace( + auto empty_label_it = scene_graph_.label_map.find(""); + if (empty_label_it == scene_graph_.label_map.end()) { + auto [emplace_it, empty_label_success] = scene_graph_.label_map.emplace( std::make_pair("", std::vector>())); FML_CHECK(empty_label_success); empty_label_it = emplace_it; @@ -274,7 +274,7 @@ void FakeSession::DetachResourceFromParent( } void FakeSession::PruneDeletedResourceRefs() { - // Remove expired resurces from the parents map. + // Remove expired resources from the parents map. for (auto parent_it = parents_map_.begin(), parent_end = parents_map_.end(); parent_it != parent_end;) { if (parent_it->second.first.expired()) { @@ -285,8 +285,8 @@ void FakeSession::PruneDeletedResourceRefs() { } // Remove expired resurces from the labels map. - for (auto scene_it = scene_graph_.labels_map.begin(), - scene_end = scene_graph_.labels_map.end(); + for (auto scene_it = scene_graph_.label_map.begin(), + scene_end = scene_graph_.label_map.end(); scene_it != scene_end;) { auto erase_it = std::remove_if( scene_it->second.begin(), scene_it->second.end(), @@ -296,7 +296,7 @@ void FakeSession::PruneDeletedResourceRefs() { } if (scene_it->second.empty()) { - scene_it = scene_graph_.labels_map.erase(scene_it); + scene_it = scene_graph_.label_map.erase(scene_it); } else { ++scene_it; } @@ -905,8 +905,8 @@ void FakeSession::ApplySetLabelCmd(fuchsia::ui::gfx::SetLabelCmd command) { auto resource_ptr = GetResource(command.id); // Erase from old spot in the labels map. - auto current_label_it = scene_graph_.labels_map.find(resource_ptr->label); - FML_CHECK(current_label_it != scene_graph_.labels_map.end()); + auto current_label_it = scene_graph_.label_map.find(resource_ptr->label); + FML_CHECK(current_label_it != scene_graph_.label_map.end()); auto current_erase_it = std::remove_if( current_label_it->second.begin(), current_label_it->second.end(), [&resource_ptr](const auto& weak_resource) { @@ -916,10 +916,10 @@ void FakeSession::ApplySetLabelCmd(fuchsia::ui::gfx::SetLabelCmd command) { current_label_it->second.erase(current_erase_it); // Add to new spot in labels map. - auto new_label_it = scene_graph_.labels_map.find(command.label); - if (new_label_it == scene_graph_.labels_map.end()) { + auto new_label_it = scene_graph_.label_map.find(command.label); + if (new_label_it == scene_graph_.label_map.end()) { auto [emplace_it, current_label_success] = - scene_graph_.labels_map.emplace(std::make_pair( + scene_graph_.label_map.emplace(std::make_pair( command.label, std::vector>())); FML_CHECK(current_label_success); new_label_it = emplace_it; diff --git a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_session.h b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_session.h index ecf1fabc32f80..14e6bf6dedeb9 100644 --- a/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_session.h +++ b/shell/platform/fuchsia/flutter/tests/fakes/scenic/fake_session.h @@ -40,13 +40,8 @@ namespace flutter_runner::testing { // stubbed-out `FuturePresentationTimes`, but more crucially it mimics the // real scenic behavior of only processing commands when a `Present` is // invoked. -// + It allows the user to inspect a snapshot of the scene graph at any moment -// in time, via the `SceneGraph()` accessor. -// + It stores the various session resources generated by commands into a -// std::unordered_map, and also correctly manages the resource lifetimes via -// reference counting. This allows a resource to stay alive if its parent -// still holds a reference to it, in the same way the real scenic -// implementation would. +// + It allows the user to inspect a snapshot of the session's local scene +// graph at any moment in time, via the `SceneGraph()` accessor. // + The resources returned by `SceneGraph()` that the test uses for // inspection are decoupled from the resources managed internally by the // `FakeSession` itself -- they are a snapshot of the scene graph at that @@ -54,20 +49,22 @@ namespace flutter_runner::testing { // scene graph state. This allows the `FakeSession` and test to naturally use // `shared_ptr` for reference counting and mimic the real scenic behavior // exactly, instead of an awkward index-based API. +// + It stores the various session resources generated by commands into a +// std::unordered_map, and also correctly manages the resource lifetimes via +// reference counting. This allows a resource to stay alive if its parent +// still holds a reference to it, in the same way the real scenic +// implementation would. // -// Error handling / session disconnection is still WIP. FakeSession will likely -// generate a CHECK in any place where the real scenic would disconnect the -// session or send a ScenicError. -// -// root_presenter-only commands e.g. CreateLayer are not handled. -// -// Deprecated / obsolete commands are not handled. -// -// Input is not handled. -// -// Rendering is not handled. -// -// Cross-session links are not handled. +// Limitations: +// +Error handling / session disconnection is still WIP. FakeSession will +// likely generate a CHECK in any place where the real scenic would disconnect +// the session or send a ScenicError. +// +root_presenter-only commands e.g. CreateLayer are not handled. +// +Deprecated / obsolete commands are not handled. +// +Input is not handled. +// +Rendering is not handled. +// +Cross-session links are not handled; the FakeSession only stores the +// tokens provided to it in a FakeResourceState. class FakeSession : public fuchsia::ui::scenic::testing::Session_TestBase { public: using PresentHandler = @@ -110,7 +107,8 @@ class FakeSession : public fuchsia::ui::scenic::testing::Session_TestBase { void SetRequestPresentationTimesHandler( RequestPresentationTimesHandler request_presentation_times_handler); - // Fire an `OnFramePresented` event. + // Call after a successful `Present` or `Present2` to fire an + // `OnFramePresented` event, which simulates the frame being displayed. void FireOnFramePresentedEvent( fuchsia::scenic::scheduling::FramePresentedInfo frame_presented_info); @@ -220,12 +218,18 @@ class FakeSession : public fuchsia::ui::scenic::testing::Session_TestBase { std::string debug_name_; + FakeSceneGraphState scene_graph_; std::deque command_queue_; + + // This map is used to cache parent refs for `AddChildCmd`. + // + // Ideally we would like to map weak(parent) -> weak(child), but std::weak_ptr + // cannot be the Key for an associative container. Instead we key on the raw + // parent pointer and store pair(weak(parent), weak(child)) in the map. std::unordered_map, std::weak_ptr>> - parents_map_; // Used to cache parent refs for `AddChildCmd` - FakeSceneGraphState scene_graph_; + parents_map_; PresentHandler present_handler_; Present2Handler present2_handler_; diff --git a/shell/platform/fuchsia/flutter/tests/fuchsia_external_view_embedder_unittests.cc b/shell/platform/fuchsia/flutter/tests/fuchsia_external_view_embedder_unittests.cc new file mode 100644 index 0000000000000..edf3830e112d6 --- /dev/null +++ b/shell/platform/fuchsia/flutter/tests/fuchsia_external_view_embedder_unittests.cc @@ -0,0 +1,662 @@ +// 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/fuchsia/flutter/fuchsia_external_view_embedder.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "flutter/flow/embedded_views.h" +#include "flutter/fml/logging.h" +#include "flutter/fml/time/time_delta.h" +#include "flutter/fml/time/time_point.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkSize.h" +#include "third_party/skia/include/core/SkSurface.h" + +#include "fakes/scenic/fake_resources.h" +#include "fakes/scenic/fake_session.h" +#include "flutter/shell/platform/fuchsia/flutter/surface_producer.h" + +#include "gmock/gmock.h" // For EXPECT_THAT and matchers +#include "gtest/gtest.h" + +using fuchsia::scenic::scheduling::FramePresentedInfo; +using fuchsia::scenic::scheduling::FuturePresentationTimes; +using fuchsia::scenic::scheduling::PresentReceivedInfo; +using ::testing::_; +using ::testing::ElementsAre; +using ::testing::FieldsAre; +using ::testing::IsEmpty; +using ::testing::IsNull; +using ::testing::Matcher; +using ::testing::Pointee; +using ::testing::SizeIs; +using ::testing::VariantWith; + +namespace flutter_runner::testing { +namespace { + +class FakeSurfaceProducerSurface : public SurfaceProducerSurface { + public: + explicit FakeSurfaceProducerSurface(scenic::Session& session, + const SkISize& size, + uint32_t buffer_id) + : session_(session), + surface_(SkSurface::MakeNull(size.width(), size.height())), + image_id_(session_.AllocResourceId()), + buffer_id_(buffer_id) { + FML_CHECK(buffer_id_ != 0); + + fuchsia::sysmem::BufferCollectionTokenSyncPtr token; + buffer_binding_ = token.NewRequest(); + + session_.RegisterBufferCollection(buffer_id_, std::move(token)); + session_.Enqueue(scenic::NewCreateImage2Cmd( + image_id_, surface_->width(), surface_->height(), buffer_id_, 0)); + } + ~FakeSurfaceProducerSurface() override { + session_.DeregisterBufferCollection(buffer_id_); + session_.Enqueue(scenic::NewReleaseResourceCmd(image_id_)); + } + + bool IsValid() const override { return true; } + + SkISize GetSize() const override { + return SkISize::Make(surface_->width(), surface_->height()); + } + + void SetImageId(uint32_t image_id) override { FAIL(); } + uint32_t GetImageId() override { return image_id_; } + + sk_sp GetSkiaSurface() const override { return surface_; } + + fuchsia::ui::composition::BufferCollectionImportToken + GetBufferCollectionImportToken() override { + return fuchsia::ui::composition::BufferCollectionImportToken{}; + } + + zx::event GetAcquireFence() override { return zx::event{}; } + + zx::event GetReleaseFence() override { return zx::event{}; } + + void SetReleaseImageCallback( + ReleaseImageCallback release_image_callback) override {} + + size_t AdvanceAndGetAge() override { return 0; } + bool FlushSessionAcquireAndReleaseEvents() override { return true; } + void SignalWritesFinished( + const std::function& on_writes_committed) override {} + + private: + scenic::Session& session_; + + sk_sp surface_; + + fidl::InterfaceRequest + buffer_binding_; + FakeResourceId image_id_{kInvalidFakeResourceId}; + uint32_t buffer_id_{0}; +}; + +class FakeSurfaceProducer : public SurfaceProducer { + public: + explicit FakeSurfaceProducer(scenic::Session& session) : session_(session) {} + ~FakeSurfaceProducer() override = default; + + std::unique_ptr ProduceSurface( + const SkISize& size) override { + return std::make_unique(session_, size, + buffer_id_++); + } + + void SubmitSurfaces( + std::vector> surfaces) override {} + + private: + scenic::Session& session_; + + uint32_t buffer_id_{1}; +}; + +struct FakeCompositorLayer { + enum class LayerType : uint32_t { + Image, + View, + }; + + std::shared_ptr layer_root; + + LayerType layer_type{LayerType::Image}; + size_t layer_index{0}; +}; + +std::string GetCurrentTestName() { + return ::testing::UnitTest::GetInstance()->current_test_info()->name(); +} + +zx_koid_t GetKoid(zx_handle_t handle) { + if (handle == ZX_HANDLE_INVALID) { + return ZX_KOID_INVALID; + } + + zx_info_handle_basic_t info; + zx_status_t status = zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, + sizeof(info), nullptr, nullptr); + return status == ZX_OK ? info.koid : ZX_KOID_INVALID; +} + +zx_koid_t GetPeerKoid(zx_handle_t handle) { + if (handle == ZX_HANDLE_INVALID) { + return ZX_KOID_INVALID; + } + + zx_info_handle_basic_t info; + zx_status_t status = zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, + sizeof(info), nullptr, nullptr); + return status == ZX_OK ? info.related_koid : ZX_KOID_INVALID; +} + +MATCHER_P(MaybeIsEmpty, assert_empty, "") { + return assert_empty ? ExplainMatchResult(IsEmpty(), arg, result_listener) + : ExplainMatchResult(_, arg, result_listener); +} + +Matcher IsEmptySceneGraph() { + return FieldsAre(IsEmpty(), IsEmpty(), IsEmpty(), kInvalidFakeResourceId); +} + +void AssertRootSceneGraph(const FakeSceneGraph& scene_graph, + bool assert_empty) { + ASSERT_NE(scene_graph.root_view_id, kInvalidFakeResourceId); + ASSERT_EQ(scene_graph.resource_map.count(scene_graph.root_view_id), 1u); + auto scene_graph_root = + scene_graph.resource_map.find(scene_graph.root_view_id); + ASSERT_THAT( + scene_graph_root->second, + Pointee(FieldsAre( + scene_graph.root_view_id, "", FakeResource::kDefaultEmptyEventMask, + VariantWith(FieldsAre( + _, _, _, _, + ElementsAre(Pointee(FieldsAre( + _, "Flutter::MetricsWatcher", + fuchsia::ui::gfx::kMetricsEventMask, + VariantWith(FieldsAre( + FieldsAre( + ElementsAre(Pointee(FieldsAre( + _, "Flutter::LayerTree", + FakeResource::kDefaultEmptyEventMask, + VariantWith(FieldsAre( + FieldsAre(MaybeIsEmpty(assert_empty), + FakeNode::kDefaultZeroRotation, + FakeNode::kDefaultOneScale, + FakeNode::kDefaultZeroTranslation, + FakeNode::kDefaultZeroAnchor, + FakeNode::kIsHitTestable, + FakeNode::kIsSemanticallyVisible), + IsEmpty()))))), + FakeNode::kDefaultZeroRotation, + FakeNode::kDefaultOneScale, + FakeNode::kDefaultZeroTranslation, + FakeNode::kDefaultZeroAnchor, + FakeNode::kIsHitTestable, + FakeNode::kIsSemanticallyVisible), + IsEmpty()))))), + FakeView::kDebugBoundsDisbaled))))); +} + +void ExpectRootSceneGraph( + const FakeSceneGraph& scene_graph, + const std::string& debug_name, + const fuchsia::ui::views::ViewHolderToken& view_holder_token, + const fuchsia::ui::views::ViewRef& view_ref) { + AssertRootSceneGraph(scene_graph, true); + + // These are safe to do unchecked due to `AssertRootSceneGraph` above. + auto root_view_it = scene_graph.resource_map.find(scene_graph.root_view_id); + auto* root_view_state = std::get_if(&root_view_it->second->state); + EXPECT_EQ(root_view_state->token, GetPeerKoid(view_holder_token.value.get())); + EXPECT_EQ(root_view_state->control_ref, + GetPeerKoid(view_ref.reference.get())); + EXPECT_EQ(root_view_state->view_ref, GetKoid(view_ref.reference.get())); + EXPECT_EQ(root_view_state->debug_name, debug_name); + EXPECT_EQ(scene_graph.resource_map.size(), 3u); +} + +void ExpectImageCompositorLayer(const FakeCompositorLayer& layer, + const SkISize layer_size) { + const SkSize float_layer_size = + SkSize::Make(layer_size.width(), layer_size.height()); + const size_t flutter_layer_index = + (layer.layer_index + 1) / 2; // Integer division + const float views_under_layer_depth = + flutter_layer_index * + FuchsiaExternalViewEmbedder::kScenicZElevationForPlatformView; + const float layer_depth = + flutter_layer_index * + FuchsiaExternalViewEmbedder::kScenicZElevationBetweenLayers + + views_under_layer_depth; + const bool layer_hit_testable = (flutter_layer_index == 0) + ? FakeNode::kIsHitTestable + : FakeNode::kIsNotHitTestable; + const float layer_opacity = + (flutter_layer_index == 0) + ? FuchsiaExternalViewEmbedder::kBackgroundLayerOpacity / 255.f + : FuchsiaExternalViewEmbedder::kOverlayLayerOpacity / 255.f; + EXPECT_EQ(layer.layer_type, FakeCompositorLayer::LayerType::Image); + EXPECT_EQ(layer.layer_index % 2, 0u); + EXPECT_THAT( + layer.layer_root, + Pointee(FieldsAre( + _, "Flutter::Layer", FakeResource::kDefaultEmptyEventMask, + VariantWith(FieldsAre( + FieldsAre(IsEmpty(), FakeNode::kDefaultZeroRotation, + FakeNode::kDefaultOneScale, + std::array{float_layer_size.width() / 2.f, + float_layer_size.height() / 2.f, + -layer_depth}, + FakeNode::kDefaultZeroAnchor, layer_hit_testable, + FakeNode::kIsSemanticallyVisible), + Pointee( + FieldsAre(_, "", FakeResource::kDefaultEmptyEventMask, + VariantWith( + FieldsAre(VariantWith( + FieldsAre(float_layer_size.width(), + float_layer_size.height())))))), + Pointee(FieldsAre( + _, "", FakeResource::kDefaultEmptyEventMask, + VariantWith(FieldsAre( + Pointee(FieldsAre( + _, "", FakeResource::kDefaultEmptyEventMask, + VariantWith(FieldsAre( + VariantWith( + FieldsAre(_, 0, float_layer_size.width(), + float_layer_size.height())), + IsNull())))), + std::array{1.f, 1.f, 1.f, + layer_opacity}))))))))); +} + +void ExpectViewCompositorLayer(const FakeCompositorLayer& layer, + const fuchsia::ui::views::ViewToken& view_token, + const flutter::EmbeddedViewParams& view_params) { + const size_t flutter_layer_index = + (layer.layer_index + 1) / 2; // Integer division + const float views_under_layer_depth = + flutter_layer_index > 0 + ? (flutter_layer_index - 1) * + FuchsiaExternalViewEmbedder::kScenicZElevationForPlatformView + : 0.f; + const float layer_depth = + flutter_layer_index * + FuchsiaExternalViewEmbedder::kScenicZElevationBetweenLayers + + views_under_layer_depth; + EXPECT_EQ(layer.layer_type, FakeCompositorLayer::LayerType::View); + EXPECT_EQ(layer.layer_index % 2, 1u); + EXPECT_THAT( + layer.layer_root, + Pointee(FieldsAre( + _, _ /*"Flutter::PlatformView::OpacityMutator" */, + FakeResource::kDefaultEmptyEventMask, + VariantWith(FieldsAre( + FieldsAre( + ElementsAre(Pointee(FieldsAre( + _, _ /*"Flutter::PlatformView::TransformMutator" */, + FakeResource::kDefaultEmptyEventMask, + VariantWith(FieldsAre( + FieldsAre( + ElementsAre(Pointee(FieldsAre( + _, "", FakeResource::kDefaultEmptyEventMask, + VariantWith(FieldsAre( + FieldsAre( + IsEmpty(), + FakeNode::kDefaultZeroRotation, + FakeNode::kDefaultOneScale, + FakeNode::kDefaultZeroTranslation, + FakeNode::kDefaultZeroAnchor, + FakeNode::kIsHitTestable, + FakeNode::kIsSemanticallyVisible), + GetPeerKoid(view_token.value.get()), + "Flutter::PlatformView", + fuchsia::ui::gfx::ViewProperties{ + .bounding_box = + fuchsia::ui::gfx::BoundingBox{ + .min = {0.f, 0.f, -1000.f}, + .max = + {view_params.sizePoints() + .width(), + view_params.sizePoints() + .height(), + 0.f}, + }}, + FakeViewHolder:: + kDefaultBoundsColorWhite))))), + FakeNode::kDefaultZeroRotation, + FakeNode::kDefaultOneScale, + std::array{0.f, 0.f, -layer_depth}, + FakeNode::kDefaultZeroAnchor, + FakeNode::kIsHitTestable, + FakeNode::kIsSemanticallyVisible), + IsEmpty()))))), + FakeNode::kDefaultZeroRotation, FakeNode::kDefaultOneScale, + FakeNode::kDefaultZeroTranslation, + FakeNode::kDefaultZeroAnchor, FakeNode::kIsHitTestable, + FakeNode::kIsSemanticallyVisible), + FakeOpacityNode::kDefaultOneOpacity))))); +} + +std::vector ExtractLayersFromSceneGraph( + const FakeSceneGraph& scene_graph) { + AssertRootSceneGraph(scene_graph, false); + + // These are safe to do unchecked due to `AssertRootSceneGraph` above. + auto root_view_it = scene_graph.resource_map.find(scene_graph.root_view_id); + auto* root_view_state = std::get_if(&root_view_it->second->state); + auto* metrics_watcher_state = + std::get_if(&root_view_state->children[0]->state); + auto* layer_tree_state = std::get_if( + &metrics_watcher_state->node_state.children[0]->state); + + std::vector layers; + for (auto& layer_resource : layer_tree_state->node_state.children) { + const size_t layer_index = layers.size(); + const FakeCompositorLayer::LayerType layer_type = + (layer_index % 2 == 0) ? FakeCompositorLayer::LayerType::Image + : FakeCompositorLayer::LayerType::View; + layers.emplace_back(FakeCompositorLayer{ + .layer_root = layer_resource, + .layer_type = layer_type, + .layer_index = layer_index, + }); + } + + return layers; +} + +void DrawSimpleFrame(FuchsiaExternalViewEmbedder& external_view_embedder, + SkISize frame_size, + float frame_dpr, + std::function draw_callback) { + external_view_embedder.BeginFrame(frame_size, nullptr, frame_dpr, nullptr); + { + SkCanvas* root_canvas = external_view_embedder.GetRootCanvas(); + external_view_embedder.PostPrerollAction(nullptr); + draw_callback(root_canvas); + } + external_view_embedder.EndFrame(false, nullptr); + external_view_embedder.SubmitFrame( + nullptr, std::make_unique( + nullptr, true, + [](const flutter::SurfaceFrame& surface_frame, + SkCanvas* canvas) { return true; })); +} + +void DrawFrameWithView(FuchsiaExternalViewEmbedder& external_view_embedder, + SkISize frame_size, + float frame_dpr, + int view_id, + flutter::EmbeddedViewParams& view_params, + std::function background_draw_callback, + std::function overlay_draw_callback) { + external_view_embedder.BeginFrame(frame_size, nullptr, frame_dpr, nullptr); + { + SkCanvas* root_canvas = external_view_embedder.GetRootCanvas(); + external_view_embedder.PrerollCompositeEmbeddedView( + view_id, std::make_unique(view_params)); + external_view_embedder.PostPrerollAction(nullptr); + background_draw_callback(root_canvas); + SkCanvas* overlay_canvas = + external_view_embedder.CompositeEmbeddedView(view_id); + overlay_draw_callback(overlay_canvas); + } + external_view_embedder.EndFrame(false, nullptr); + external_view_embedder.SubmitFrame( + nullptr, std::make_unique( + nullptr, true, + [](const flutter::SurfaceFrame& surface_frame, + SkCanvas* canvas) { return true; })); +} + +FramePresentedInfo MakeFramePresentedInfoForOnePresent( + int64_t latched_time, + int64_t frame_presented_time) { + std::vector present_infos; + present_infos.emplace_back(); + present_infos.back().set_present_received_time(0); + present_infos.back().set_latched_time(0); + return FramePresentedInfo{ + .actual_presentation_time = 0, + .presentation_infos = std::move(present_infos), + .num_presents_allowed = 1, + }; +} + +}; // namespace + +class FuchsiaExternalViewEmbedderTest + : public ::testing::Test, + public fuchsia::ui::scenic::SessionListener { + protected: + FuchsiaExternalViewEmbedderTest() + : session_listener_(this), + session_subloop_(loop_.StartNewLoop()), + session_connection_(CreateSessionConnection()), + fake_surface_producer_(*session_connection_.get()) {} + ~FuchsiaExternalViewEmbedderTest() override = default; + + async::TestLoop& loop() { return loop_; } + + FakeSession& fake_session() { return fake_session_; } + + FakeSurfaceProducer& fake_surface_producer() { + return fake_surface_producer_; + } + + GfxSessionConnection& session_connection() { return session_connection_; } + + private: + // |fuchsia::ui::scenic::SessionListener| + void OnScenicError(std::string error) override { FAIL(); } + + // |fuchsia::ui::scenic::SessionListener| + void OnScenicEvent(std::vector events) override { + FAIL(); + } + + GfxSessionConnection CreateSessionConnection() { + FML_CHECK(!fake_session_.is_bound()); + FML_CHECK(!session_listener_.is_bound()); + + inspect::Node inspect_node = + inspector_.GetRoot().CreateChild("FuchsiaExternalViewEmbedderTest"); + + auto [session, session_listener] = + fake_session_.Bind(session_subloop_->dispatcher()); + session_listener_.Bind(std::move(session_listener)); + + return GfxSessionConnection( + GetCurrentTestName(), std::move(inspect_node), std::move(session), + []() { FAIL(); }, [](auto...) {}, 1, fml::TimeDelta::Zero()); + } + + async::TestLoop loop_; // Must come before FIDL bindings. + + inspect::Inspector inspector_; + + fidl::Binding session_listener_; + + std::unique_ptr session_subloop_; + FakeSession fake_session_; + GfxSessionConnection session_connection_; + + FakeSurfaceProducer fake_surface_producer_; +}; + +TEST_F(FuchsiaExternalViewEmbedderTest, RootScene) { + const std::string debug_name = GetCurrentTestName(); + auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); + auto view_ref_pair = scenic::ViewRefPair::New(); + fuchsia::ui::views::ViewRef view_ref; + view_ref_pair.view_ref.Clone(&view_ref); + + FuchsiaExternalViewEmbedder external_view_embedder( + debug_name, std::move(view_token), std::move(view_ref_pair), + session_connection(), fake_surface_producer()); + EXPECT_EQ(fake_session().debug_name(), ""); + EXPECT_THAT(fake_session().SceneGraph(), IsEmptySceneGraph()); + + // Pump the loop; the contents of the initial `Present` should be processed. + loop().RunUntilIdle(); + EXPECT_EQ(fake_session().debug_name(), debug_name); + ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name, + view_holder_token, view_ref); + + // Fire the `OnFramePresented` event associated with the first `Present`, then + // pump the loop. The `OnFramePresented` event is resolved. + // + // The scene graph shouldn't change. + fake_session().FireOnFramePresentedEvent( + MakeFramePresentedInfoForOnePresent(0, 0)); + loop().RunUntilIdle(); + ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name, + view_holder_token, view_ref); +} + +TEST_F(FuchsiaExternalViewEmbedderTest, SimpleScene) { + const std::string debug_name = GetCurrentTestName(); + auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); + auto view_ref_pair = scenic::ViewRefPair::New(); + fuchsia::ui::views::ViewRef view_ref; + view_ref_pair.view_ref.Clone(&view_ref); + + // Create the `FuchsiaExternalViewEmbedder` and pump the message loop until + // the initial scene graph is setup. + FuchsiaExternalViewEmbedder external_view_embedder( + debug_name, std::move(view_token), std::move(view_ref_pair), + session_connection(), fake_surface_producer()); + loop().RunUntilIdle(); + fake_session().FireOnFramePresentedEvent( + MakeFramePresentedInfoForOnePresent(0, 0)); + loop().RunUntilIdle(); + ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name, + view_holder_token, view_ref); + + // Draw the scene. The scene graph shouldn't change yet. + const SkISize frame_size = SkISize::Make(512, 512); + DrawSimpleFrame( + external_view_embedder, frame_size, 1.f, [](SkCanvas* canvas) { + const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(), + canvas->imageInfo().height()); + SkPaint rect_paint; + rect_paint.setColor(SK_ColorGREEN); + canvas->translate(canvas_size.width() / 4.f, + canvas_size.height() / 2.f); + canvas->drawRect(SkRect::MakeWH(canvas_size.width() / 32.f, + canvas_size.height() / 32.f), + rect_paint); + }); + ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name, + view_holder_token, view_ref); + + // Pump the message loop. The scene updates should propogate to Scenic. + loop().RunUntilIdle(); + std::vector compositor_layers = + ExtractLayersFromSceneGraph(fake_session().SceneGraph()); + EXPECT_EQ(compositor_layers.size(), 1u); + ExpectImageCompositorLayer(compositor_layers[0], frame_size); +} + +TEST_F(FuchsiaExternalViewEmbedderTest, SceneWithOneView) { + const std::string debug_name = GetCurrentTestName(); + auto [view_token, view_holder_token] = scenic::ViewTokenPair::New(); + auto view_ref_pair = scenic::ViewRefPair::New(); + fuchsia::ui::views::ViewRef view_ref; + view_ref_pair.view_ref.Clone(&view_ref); + + // Create the `FuchsiaExternalViewEmbedder` and pump the message loop until + // the initial scene graph is setup. + FuchsiaExternalViewEmbedder external_view_embedder( + debug_name, std::move(view_token), std::move(view_ref_pair), + session_connection(), fake_surface_producer()); + loop().RunUntilIdle(); + fake_session().FireOnFramePresentedEvent( + MakeFramePresentedInfoForOnePresent(0, 0)); + loop().RunUntilIdle(); + ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name, + view_holder_token, view_ref); + + // Create the view before drawing the scene. + const SkSize child_view_size = SkSize::Make(256.f, 512.f); + auto [child_view_token, child_view_holder_token] = + scenic::ViewTokenPair::New(); + const uint32_t child_view_id = child_view_holder_token.value.get(); + flutter::EmbeddedViewParams child_view_params(SkMatrix::I(), child_view_size, + flutter::MutatorsStack()); + external_view_embedder.CreateView( + child_view_id, []() {}, [](scenic::ResourceId) {}); + + // Draw the scene. The scene graph shouldn't change yet. + const SkISize frame_size = SkISize::Make(512, 512); + DrawFrameWithView( + external_view_embedder, frame_size, 1.f, child_view_id, child_view_params, + [](SkCanvas* canvas) { + const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(), + canvas->imageInfo().height()); + SkPaint rect_paint; + rect_paint.setColor(SK_ColorGREEN); + canvas->translate(canvas_size.width() / 4.f, + canvas_size.height() / 2.f); + canvas->drawRect(SkRect::MakeWH(canvas_size.width() / 32.f, + canvas_size.height() / 32.f), + rect_paint); + }, + [](SkCanvas* canvas) { + const SkSize canvas_size = SkSize::Make(canvas->imageInfo().width(), + canvas->imageInfo().height()); + SkPaint rect_paint; + rect_paint.setColor(SK_ColorRED); + canvas->translate(canvas_size.width() * 3.f / 4.f, + canvas_size.height() / 2.f); + canvas->drawRect(SkRect::MakeWH(canvas_size.width() / 32.f, + canvas_size.height() / 32.f), + rect_paint); + }); + ExpectRootSceneGraph(fake_session().SceneGraph(), debug_name, + view_holder_token, view_ref); + + // Pump the message loop. The scene updates should propagate to Scenic. + loop().RunUntilIdle(); + std::vector compositor_layers = + ExtractLayersFromSceneGraph(fake_session().SceneGraph()); + EXPECT_EQ(compositor_layers.size(), 3u); + ExpectImageCompositorLayer(compositor_layers[0], frame_size); + ExpectViewCompositorLayer(compositor_layers[1], child_view_token, + child_view_params); + ExpectImageCompositorLayer(compositor_layers[2], frame_size); + + // Destroy the view. + external_view_embedder.DestroyView(child_view_id, [](scenic::ResourceId) {}); + + // Pump the message loop. + loop().RunUntilIdle(); +} + +} // namespace flutter_runner::testing diff --git a/shell/platform/fuchsia/flutter/tests/gfx_session_connection_unittests.cc b/shell/platform/fuchsia/flutter/tests/gfx_session_connection_unittests.cc index 26bf6c5e69d43..9a7e314ca0602 100644 --- a/shell/platform/fuchsia/flutter/tests/gfx_session_connection_unittests.cc +++ b/shell/platform/fuchsia/flutter/tests/gfx_session_connection_unittests.cc @@ -8,12 +8,12 @@ #include #include #include -#include #include #include #include +#include "flutter/fml/logging.h" #include "flutter/fml/time/time_delta.h" #include "flutter/fml/time/time_point.h" #include "gtest/gtest.h" @@ -89,11 +89,11 @@ class GfxSessionConnectionTest : public ::testing::Test, protected: GfxSessionConnectionTest() : session_listener_(this), session_subloop_(loop_.StartNewLoop()) { - FakeSession::SessionAndListenerClientPair session_and_listener = + auto [session, session_listener] = fake_session().Bind(session_subloop_->dispatcher()); - session_ = std::move(session_and_listener.first); - session_listener_.Bind(std::move(session_and_listener.second)); + session_ = std::move(session); + session_listener_.Bind(std::move(session_listener)); } ~GfxSessionConnectionTest() override = default; @@ -110,38 +110,13 @@ class GfxSessionConnectionTest : public ::testing::Test, return std::move(session_); } - void SetUpSessionStubs( - FakeSession::RequestPresentationTimesHandler - request_presentation_times_handler = nullptr, - FakeSession::Present2Handler present_handler = nullptr) { - auto non_null_request_presentation_times_handler = - request_presentation_times_handler ? request_presentation_times_handler - : [](auto...) -> auto { - return FuturePresentationTimes{ - .future_presentations = {}, - .remaining_presents_in_flight_allowed = 1, - }; - }; - fake_session().SetRequestPresentationTimesHandler( - std::move(non_null_request_presentation_times_handler)); - - auto non_null_present_handler = - present_handler ? present_handler : [](auto...) -> auto { - return FuturePresentationTimes{ - .future_presentations = {}, - .remaining_presents_in_flight_allowed = 1, - }; - }; - fake_session().SetPresent2Handler(std::move(non_null_present_handler)); - } - private: // |fuchsia::ui::scenic::SessionListener| - void OnScenicError(std::string error) override { FML_CHECK(false); } + void OnScenicError(std::string error) override { FAIL(); } // |fuchsia::ui::scenic::SessionListener| void OnScenicEvent(std::vector events) override { - FML_CHECK(false); + FAIL(); } async::TestLoop loop_; @@ -156,15 +131,12 @@ class GfxSessionConnectionTest : public ::testing::Test, }; TEST_F(GfxSessionConnectionTest, Initialization) { - SetUpSessionStubs(); // So we don't CHECK - // Create the GfxSessionConnection but don't pump the loop. No FIDL calls are // completed yet. const std::string debug_name = GetCurrentTestName(); flutter_runner::GfxSessionConnection session_connection( - debug_name, GetInspectNode(), TakeSessionHandle(), - []() { FML_CHECK(false); }, [](auto...) { FML_CHECK(false); }, 1, - fml::TimeDelta::Zero()); + debug_name, GetInspectNode(), TakeSessionHandle(), []() { FAIL(); }, + [](auto...) { FAIL(); }, 1, fml::TimeDelta::Zero()); EXPECT_EQ(fake_session().debug_name(), ""); EXPECT_TRUE(fake_session().command_queue().empty()); @@ -182,8 +154,6 @@ TEST_F(GfxSessionConnectionTest, Initialization) { } TEST_F(GfxSessionConnectionTest, SessionDisconnect) { - SetUpSessionStubs(); // So we don't CHECK - // Set up a callback which allows sensing of the session error state. bool session_error_fired = false; fml::closure on_session_error = [&session_error_fired]() { @@ -194,7 +164,7 @@ TEST_F(GfxSessionConnectionTest, SessionDisconnect) { // completed yet. flutter_runner::GfxSessionConnection session_connection( GetCurrentTestName(), GetInspectNode(), TakeSessionHandle(), - std::move(on_session_error), [](auto...) { FML_CHECK(false); }, 1, + std::move(on_session_error), [](auto...) { FAIL(); }, 1, fml::TimeDelta::Zero()); EXPECT_FALSE(session_error_fired); @@ -210,21 +180,21 @@ TEST_F(GfxSessionConnectionTest, BasicPresent) { // (`RequestPresentationTimes` or `Present` calls) were handled. size_t request_times_called = 0u; size_t presents_called = 0u; - SetUpSessionStubs( - [&request_times_called](auto...) -> auto { - request_times_called++; - return FuturePresentationTimes{ - .future_presentations = {}, - .remaining_presents_in_flight_allowed = 1, - }; - }, - [&presents_called](auto...) -> auto { - presents_called++; - return FuturePresentationTimes{ - .future_presentations = {}, - .remaining_presents_in_flight_allowed = 1, - }; - }); + fake_session().SetRequestPresentationTimesHandler([&request_times_called]( + auto...) -> auto { + request_times_called++; + return FuturePresentationTimes{ + .future_presentations = {}, + .remaining_presents_in_flight_allowed = 1, + }; + }); + fake_session().SetPresent2Handler([&presents_called](auto...) -> auto { + presents_called++; + return FuturePresentationTimes{ + .future_presentations = {}, + .remaining_presents_in_flight_allowed = 1, + }; + }); // Set up a callback which allows sensing of how many vsync's // (`OnFramePresented` events) were handled. @@ -237,7 +207,7 @@ TEST_F(GfxSessionConnectionTest, BasicPresent) { // completed yet. flutter_runner::GfxSessionConnection session_connection( GetCurrentTestName(), GetInspectNode(), TakeSessionHandle(), - []() { FML_CHECK(false); }, std::move(on_frame_presented), 1, + []() { FAIL(); }, std::move(on_frame_presented), 1, fml::TimeDelta::Zero()); EXPECT_TRUE(fake_session().command_queue().empty()); EXPECT_EQ(request_times_called, 0u); @@ -303,15 +273,13 @@ TEST_F(GfxSessionConnectionTest, AwaitVsyncBackpressure) { // Set up a callback which allows sensing of how many presents // (`Present` calls) were handled. size_t presents_called = 0u; - SetUpSessionStubs( - nullptr /* request_presentation_times_handler */, - [&presents_called](auto...) -> auto { - presents_called++; - return FuturePresentationTimes{ - .future_presentations = {}, - .remaining_presents_in_flight_allowed = 1, - }; - }); + fake_session().SetPresent2Handler([&presents_called](auto...) -> auto { + presents_called++; + return FuturePresentationTimes{ + .future_presentations = {}, + .remaining_presents_in_flight_allowed = 1, + }; + }); // Set up a callback which allows sensing of how many vsync's // (`OnFramePresented` events) were handled. @@ -324,7 +292,7 @@ TEST_F(GfxSessionConnectionTest, AwaitVsyncBackpressure) { // completed yet. flutter_runner::GfxSessionConnection session_connection( GetCurrentTestName(), GetInspectNode(), TakeSessionHandle(), - []() { FML_CHECK(false); }, std::move(on_frame_presented), 1, + []() { FAIL(); }, std::move(on_frame_presented), 1, fml::TimeDelta::Zero()); EXPECT_EQ(presents_called, 0u); EXPECT_EQ(vsyncs_handled, 0u); @@ -389,15 +357,13 @@ TEST_F(GfxSessionConnectionTest, PresentBackpressure) { // Set up a callback which allows sensing of how many presents // (`Present` calls) were handled. size_t presents_called = 0u; - SetUpSessionStubs( - nullptr /* request_presentation_times_handler */, - [&presents_called](auto...) -> auto { - presents_called++; - return FuturePresentationTimes{ - .future_presentations = {}, - .remaining_presents_in_flight_allowed = 1, - }; - }); + fake_session().SetPresent2Handler([&presents_called](auto...) -> auto { + presents_called++; + return FuturePresentationTimes{ + .future_presentations = {}, + .remaining_presents_in_flight_allowed = 1, + }; + }); // Set up a callback which allows sensing of how many vsync's // (`OnFramePresented` events) were handled. @@ -410,7 +376,7 @@ TEST_F(GfxSessionConnectionTest, PresentBackpressure) { // completed yet. flutter_runner::GfxSessionConnection session_connection( GetCurrentTestName(), GetInspectNode(), TakeSessionHandle(), - []() { FML_CHECK(false); }, std::move(on_frame_presented), 1, + []() { FAIL(); }, std::move(on_frame_presented), 1, fml::TimeDelta::Zero()); EXPECT_EQ(presents_called, 0u); EXPECT_EQ(vsyncs_handled, 0u); diff --git a/shell/platform/fuchsia/flutter/vulkan_surface.h b/shell/platform/fuchsia/flutter/vulkan_surface.h index fcaed40d4b94c..eefd0889d7104 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface.h +++ b/shell/platform/fuchsia/flutter/vulkan_surface.h @@ -13,7 +13,6 @@ #include #include -#include "flutter/flow/raster_cache_key.h" #include "flutter/fml/macros.h" #include "flutter/vulkan/vulkan_command_buffer.h" #include "flutter/vulkan/vulkan_handle.h" @@ -21,52 +20,9 @@ #include "flutter/vulkan/vulkan_provider.h" #include "third_party/skia/include/core/SkSurface.h" -namespace flutter_runner { - -using ReleaseImageCallback = std::function; - -class SurfaceProducerSurface { - public: - virtual ~SurfaceProducerSurface() = default; - - virtual size_t AdvanceAndGetAge() = 0; - - virtual bool FlushSessionAcquireAndReleaseEvents() = 0; - - virtual bool IsValid() const = 0; - - virtual SkISize GetSize() const = 0; - - virtual void SignalWritesFinished( - const std::function& on_writes_committed) = 0; - - virtual void SetImageId(uint32_t image_id) = 0; - - virtual uint32_t GetImageId() = 0; +#include "surface_producer.h" - virtual sk_sp GetSkiaSurface() const = 0; - - virtual fuchsia::ui::composition::BufferCollectionImportToken - GetBufferCollectionImportToken() = 0; - - virtual zx::event GetAcquireFence() = 0; - - virtual zx::event GetReleaseFence() = 0; - - virtual void SetReleaseImageCallback( - ReleaseImageCallback release_image_callback) = 0; -}; - -class SurfaceProducer { - public: - virtual ~SurfaceProducer() = default; - - virtual std::unique_ptr ProduceSurface( - const SkISize& size) = 0; - - virtual void SubmitSurface( - std::unique_ptr surface) = 0; -}; +namespace flutter_runner { // A |VkImage| and its relevant metadata. struct VulkanImage { diff --git a/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc b/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc index 53bf3b3eae6cf..b341956c9ab34 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc +++ b/shell/platform/fuchsia/flutter/vulkan_surface_producer.cc @@ -157,9 +157,9 @@ bool VulkanSurfaceProducer::Initialize(scenic::Session* scenic_session) { return true; } -void VulkanSurfaceProducer::OnSurfacesPresented( +void VulkanSurfaceProducer::SubmitSurfaces( std::vector> surfaces) { - TRACE_EVENT0("flutter", "VulkanSurfaceProducer::OnSurfacesPresented"); + TRACE_EVENT0("flutter", "VulkanSurfaceProducer::SubmitSurfaces"); // Do a single flush for all canvases derived from the context. { diff --git a/shell/platform/fuchsia/flutter/vulkan_surface_producer.h b/shell/platform/fuchsia/flutter/vulkan_surface_producer.h index 0b4ffd8e33e22..dc0092587f7a6 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface_producer.h +++ b/shell/platform/fuchsia/flutter/vulkan_surface_producer.h @@ -25,26 +25,23 @@ namespace flutter_runner { class VulkanSurfaceProducer final : public SurfaceProducer, public vulkan::VulkanProvider { public: - VulkanSurfaceProducer(scenic::Session* scenic_session); - - ~VulkanSurfaceProducer(); + explicit VulkanSurfaceProducer(scenic::Session* scenic_session); + ~VulkanSurfaceProducer() override; bool IsValid() const { return valid_; } - // |SurfaceProducer| - std::unique_ptr ProduceSurface( - const SkISize& size) override; + GrDirectContext* gr_context() const { return context_.get(); } std::unique_ptr ProduceOffscreenSurface( const SkISize& size); // |SurfaceProducer| - void SubmitSurface(std::unique_ptr surface) override; - - void OnSurfacesPresented( - std::vector> surfaces); + std::unique_ptr ProduceSurface( + const SkISize& size) override; - GrDirectContext* gr_context() const { return context_.get(); } + // |SurfaceProducer| + void SubmitSurfaces( + std::vector> surfaces) override; private: // VulkanProvider @@ -53,6 +50,9 @@ class VulkanSurfaceProducer final : public SurfaceProducer, return logical_device_->GetHandle(); } + bool Initialize(scenic::Session* scenic_session); + + void SubmitSurface(std::unique_ptr surface); bool TransitionSurfacesToExternal( const std::vector>& surfaces); @@ -71,8 +71,6 @@ class VulkanSurfaceProducer final : public SurfaceProducer, zx::time last_produce_time_ = async::Now(async_get_default_dispatcher()); fml::WeakPtrFactory weak_factory_{this}; - bool Initialize(scenic::Session* scenic_session); - // Disallow copy and assignment. VulkanSurfaceProducer(const VulkanSurfaceProducer&) = delete; VulkanSurfaceProducer& operator=(const VulkanSurfaceProducer&) = delete;