From 8bb493406e2ff3fcd00619b99709a280914d18b3 Mon Sep 17 00:00:00 2001 From: mikejurka Date: Mon, 23 Mar 2020 16:38:07 -0700 Subject: [PATCH] [Fuchsia] Move physical shape layer compositing to Flutter (flutter/engine#17005) * [fuchsia] Add labels to Scenic nodes. * [fuchsia] Skip creating Scenic nodes for identity Transforms. * [fuchsia] Assign elevation to Scenic nodes based on paint order. * [fuchsia] Create Scenic OpacityNodes at leaf nodes. * [fuchsia] Composite PhysicalShapeLayers using Skia, except when they need to float above child views. In that case, they will still need to be pulled into separate Scenic nodes to be composited on top of the child view[s]. * [fuchsia] Add tests for Fuchsia-specific layer behavior. Inspect commands going to Scenic and make sure they match what is expected. Also, restructure code to need less member variables, and other cleanups based on review feedback. --- .../ci/licenses_golden/licenses_flutter | 1 + engine/src/flutter/flow/BUILD.gn | 9 + .../flutter/flow/layers/child_scene_layer.cc | 5 +- .../flow/layers/fuchsia_layer_unittests.cc | 764 ++++++++++++++++++ engine/src/flutter/flow/layers/layer.h | 5 + engine/src/flutter/flow/layers/layer_tree.cc | 2 +- .../src/flutter/flow/layers/opacity_layer.cc | 51 +- .../src/flutter/flow/layers/opacity_layer.h | 1 - .../flow/layers/physical_shape_layer.cc | 93 ++- .../flow/layers/physical_shape_layer.h | 3 + .../layers/physical_shape_layer_unittests.cc | 12 - .../flutter/flow/layers/transform_layer.cc | 8 +- .../src/flutter/flow/scene_update_context.cc | 50 +- .../src/flutter/flow/scene_update_context.h | 37 +- engine/src/flutter/flow/view_holder.cc | 10 +- engine/src/flutter/flow/view_holder.h | 2 + .../platform/fuchsia/flutter/vulkan_surface.h | 7 +- .../fuchsia/flutter/vulkan_surface_pool.h | 3 +- .../fuchsia/flutter/vulkan_surface_producer.h | 2 +- 19 files changed, 935 insertions(+), 130 deletions(-) create mode 100644 engine/src/flutter/flow/layers/fuchsia_layer_unittests.cc diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 1f029bb647d..135e7a141dd 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -49,6 +49,7 @@ FILE: ../../../flutter/flow/layers/color_filter_layer_unittests.cc FILE: ../../../flutter/flow/layers/container_layer.cc FILE: ../../../flutter/flow/layers/container_layer.h FILE: ../../../flutter/flow/layers/container_layer_unittests.cc +FILE: ../../../flutter/flow/layers/fuchsia_layer_unittests.cc FILE: ../../../flutter/flow/layers/image_filter_layer.cc FILE: ../../../flutter/flow/layers/image_filter_layer.h FILE: ../../../flutter/flow/layers/image_filter_layer_unittests.cc diff --git a/engine/src/flutter/flow/BUILD.gn b/engine/src/flutter/flow/BUILD.gn index 14ceec4dadc..e25b5dc8957 100644 --- a/engine/src/flutter/flow/BUILD.gn +++ b/engine/src/flutter/flow/BUILD.gn @@ -88,6 +88,7 @@ source_set("flow") { if (using_fuchsia_sdk) { public_deps += [ + "$fuchsia_sdk_root/fidl:fuchsia.ui.app", "$fuchsia_sdk_root/fidl:fuchsia.ui.gfx", "$fuchsia_sdk_root/pkg:scenic_cpp", ] @@ -159,6 +160,10 @@ executable("flow_unittests") { "texture_unittests.cc", ] + if (is_fuchsia) { + sources += [ "layers/fuchsia_layer_unittests.cc" ] + } + deps = [ ":flow", ":flow_fixtures", @@ -170,6 +175,10 @@ executable("flow_unittests") { "//third_party/googletest:gtest", "//third_party/skia", ] + + if (is_fuchsia) { + deps += [ "//build/fuchsia/pkg:sys_cpp_testing" ] + } } if (is_fuchsia) { diff --git a/engine/src/flutter/flow/layers/child_scene_layer.cc b/engine/src/flutter/flow/layers/child_scene_layer.cc index 4a535805392..4e533130c1d 100644 --- a/engine/src/flutter/flow/layers/child_scene_layer.cc +++ b/engine/src/flutter/flow/layers/child_scene_layer.cc @@ -20,6 +20,7 @@ ChildSceneLayer::ChildSceneLayer(zx_koid_t layer_id, void ChildSceneLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { TRACE_EVENT0("flutter", "ChildSceneLayer::Preroll"); set_needs_system_composite(true); + context->child_scene_layer_exists_below = true; // An alpha "hole punch" is required if the frame behind us is not opaque. if (!context->is_opaque) { @@ -49,7 +50,9 @@ void ChildSceneLayer::UpdateScene(SceneUpdateContext& context) { auto* view_holder = ViewHolder::FromId(layer_id_); FML_DCHECK(view_holder); - view_holder->UpdateScene(context, offset_, size_, hit_testable_); + view_holder->UpdateScene(context, offset_, size_, + SkScalarRoundToInt(context.alphaf() * 255), + hit_testable_); } } // namespace flutter diff --git a/engine/src/flutter/flow/layers/fuchsia_layer_unittests.cc b/engine/src/flutter/flow/layers/fuchsia_layer_unittests.cc new file mode 100644 index 00000000000..2a859226ab5 --- /dev/null +++ b/engine/src/flutter/flow/layers/fuchsia_layer_unittests.cc @@ -0,0 +1,764 @@ +// 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 + +#include "gtest/gtest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "flutter/flow/layers/child_scene_layer.h" +#include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/layers/opacity_layer.h" +#include "flutter/flow/layers/physical_shape_layer.h" +#include "flutter/flow/view_holder.h" +#include "flutter/fml/platform/fuchsia/message_loop_fuchsia.h" +#include "flutter/fml/task_runner.h" + +namespace flutter { +namespace testing { + +using FuchsiaLayerTest = ::testing::Test; + +class MockSession : public fuchsia::ui::scenic::testing::Session_TestBase { + public: + MockSession() : binding_(this) {} + + void NotImplemented_(const std::string& name) final {} + + void Bind(fidl::InterfaceRequest<::fuchsia::ui::scenic::Session> request, + ::fuchsia::ui::scenic::SessionListenerPtr listener) { + binding_.Bind(std::move(request)); + listener_ = std::move(listener); + } + + static std::string Vec3ValueToString(fuchsia::ui::gfx::Vector3Value value) { + return "{" + std::to_string(value.value.x) + ", " + + std::to_string(value.value.y) + ", " + + std::to_string(value.value.z) + "}"; + } + + static std::string GfxCreateResourceCmdToString( + const fuchsia::ui::gfx::CreateResourceCmd& cmd) { + std::string id = " id: " + std::to_string(cmd.id); + switch (cmd.resource.Which()) { + case fuchsia::ui::gfx::ResourceArgs::Tag::kRectangle: + return "Rectangle" + id; + case fuchsia::ui::gfx::ResourceArgs::Tag::kRoundedRectangle: + return "RoundedRectangle" + id; + case fuchsia::ui::gfx::ResourceArgs::Tag::kViewHolder: + return "ViewHolder" + id; + case fuchsia::ui::gfx::ResourceArgs::Tag::kOpacityNode: + return "OpacityNode" + id; + case fuchsia::ui::gfx::ResourceArgs::Tag::kEntityNode: + return "EntityNode" + id; + case fuchsia::ui::gfx::ResourceArgs::Tag::kShapeNode: + return "ShapeNode" + id; + case fuchsia::ui::gfx::ResourceArgs::Tag::kMaterial: + return "Material" + id; + case fuchsia::ui::gfx::ResourceArgs::Tag::kImage: + return "Image" + id + ", memory_id: " + + std::to_string(cmd.resource.image().memory_id) + + ", memory_offset: " + + std::to_string(cmd.resource.image().memory_offset); + default: + return "Unhandled CreateResource command" + + std::to_string(cmd.resource.Which()); + } + } + + static std::string GfxCmdToString(const fuchsia::ui::gfx::Command& cmd) { + switch (cmd.Which()) { + case fuchsia::ui::gfx::Command::Tag::kCreateResource: + return "CreateResource: " + + GfxCreateResourceCmdToString(cmd.create_resource()); + case fuchsia::ui::gfx::Command::Tag::kReleaseResource: + return "ReleaseResource id: " + + std::to_string(cmd.release_resource().id); + case fuchsia::ui::gfx::Command::Tag::kAddChild: + return "AddChild id: " + std::to_string(cmd.add_child().node_id) + + " child_id: " + std::to_string(cmd.add_child().child_id); + case fuchsia::ui::gfx::Command::Tag::kSetTranslation: + return "SetTranslation id: " + + std::to_string(cmd.set_translation().id) + + " value: " + Vec3ValueToString(cmd.set_translation().value); + case fuchsia::ui::gfx::Command::Tag::kSetScale: + return "SetScale id: " + std::to_string(cmd.set_scale().id) + + " value: " + Vec3ValueToString(cmd.set_translation().value); + case fuchsia::ui::gfx::Command::Tag::kSetRotation: + return "SetRotation id: " + std::to_string(cmd.set_rotation().id); + case fuchsia::ui::gfx::Command::Tag::kSetOpacity: + return "SetOpacity id: " + std::to_string(cmd.set_opacity().node_id) + + ", opacity: " + std::to_string(cmd.set_opacity().opacity); + case fuchsia::ui::gfx::Command::Tag::kSetColor: + return "SetColor id: " + std::to_string(cmd.set_color().material_id) + + ", rgba: (" + std::to_string(cmd.set_color().color.value.red) + + ", " + std::to_string(cmd.set_color().color.value.green) + ", " + + std::to_string(cmd.set_color().color.value.blue) + ", " + + std::to_string(cmd.set_color().color.value.alpha) + ")"; + case fuchsia::ui::gfx::Command::Tag::kSetLabel: + return "SetLabel id: " + std::to_string(cmd.set_label().id) + " " + + cmd.set_label().label; + case fuchsia::ui::gfx::Command::Tag::kSetHitTestBehavior: + return "SetHitTestBehavior node_id: " + + std::to_string(cmd.set_hit_test_behavior().node_id); + case fuchsia::ui::gfx::Command::Tag::kSetClipPlanes: + return "SetClipPlanes node_id: " + + std::to_string(cmd.set_clip_planes().node_id); + case fuchsia::ui::gfx::Command::Tag::kSetShape: + return "SetShape node_id: " + std::to_string(cmd.set_shape().node_id) + + ", shape_id: " + std::to_string(cmd.set_shape().shape_id); + case fuchsia::ui::gfx::Command::Tag::kSetMaterial: + return "SetMaterial node_id: " + + std::to_string(cmd.set_material().node_id) + ", material_id: " + + std::to_string(cmd.set_material().material_id); + case fuchsia::ui::gfx::Command::Tag::kSetTexture: + return "SetTexture material_id: " + + std::to_string(cmd.set_texture().material_id) + + ", texture_id: " + std::to_string(cmd.set_texture().texture_id); + + default: + return "Unhandled gfx command" + std::to_string(cmd.Which()); + } + } + + static std::string ScenicCmdToString( + const fuchsia::ui::scenic::Command& cmd) { + if (cmd.Which() != fuchsia::ui::scenic::Command::Tag::kGfx) { + return "Unhandled non-gfx command: " + std::to_string(cmd.Which()); + } + return GfxCmdToString(cmd.gfx()); + } + + // |fuchsia::ui::scenic::Session| + void Enqueue(std::vector cmds) override { + for (const auto& cmd : cmds) { + num_enqueued_commands_++; + EXPECT_FALSE(expected_.empty()) + << "Received more commands than expected; command: <" + << ScenicCmdToString(cmd) + << ">, num_enqueued_commands: " << num_enqueued_commands_; + if (!expected_.empty()) { + EXPECT_TRUE(AreCommandsEqual(expected_.front(), cmd)) + << "actual command: <" << ScenicCmdToString(cmd) + << ">, expected command: <" << ScenicCmdToString(expected_.front()) + << ">, num_enqueued_commands: " << num_enqueued_commands_; + expected_.pop_front(); + } + } + } + + void SetExpectedCommands(std::vector gfx_cmds) { + std::deque scenic_commands; + for (auto it = gfx_cmds.begin(); it != gfx_cmds.end(); it++) { + scenic_commands.push_back(scenic::NewCommand(std::move((*it)))); + } + expected_ = std::move(scenic_commands); + num_enqueued_commands_ = 0; + } + + size_t num_enqueued_commands() { return num_enqueued_commands_; } + + private: + static bool IsGfxCommand(const fuchsia::ui::scenic::Command& cmd, + fuchsia::ui::gfx::Command::Tag tag) { + return cmd.Which() == fuchsia::ui::scenic::Command::Tag::kGfx && + cmd.gfx().Which() == tag; + } + + static bool IsCreateResourceCommand(const fuchsia::ui::scenic::Command& cmd, + fuchsia::ui::gfx::ResourceArgs::Tag tag) { + return IsGfxCommand(cmd, fuchsia::ui::gfx::Command::Tag::kCreateResource) && + cmd.gfx().create_resource().resource.Which() == tag; + } + + static bool AreCommandsEqual(const fuchsia::ui::scenic::Command& command1, + const fuchsia::ui::scenic::Command& command2) { + // For CreateViewHolderCommand, just compare the id and ignore the + // view_holder_token. + if (IsCreateResourceCommand( + command1, fuchsia::ui::gfx::ResourceArgs::Tag::kViewHolder)) { + return IsCreateResourceCommand( + command2, fuchsia::ui::gfx::ResourceArgs::Tag::kViewHolder) && + command1.gfx().create_resource().id == + command2.gfx().create_resource().id; + } + // For CreateImageCommand, just compare the id and memory_id. + if (IsCreateResourceCommand(command1, + fuchsia::ui::gfx::ResourceArgs::Tag::kImage)) { + return IsCreateResourceCommand( + command2, fuchsia::ui::gfx::ResourceArgs::Tag::kImage) && + command1.gfx().create_resource().id == + command2.gfx().create_resource().id && + command1.gfx().create_resource().resource.image().memory_id == + command2.gfx().create_resource().resource.image().memory_id; + } + // For SetHitTestBehaviorCommand, just compare the node_id. + if (IsGfxCommand(command1, + fuchsia::ui::gfx::Command::Tag::kSetHitTestBehavior)) { + return IsGfxCommand( + command2, + fuchsia::ui::gfx::Command::Tag::kSetHitTestBehavior) && + command1.gfx().set_hit_test_behavior().node_id == + command2.gfx().set_hit_test_behavior().node_id; + } + // For SetHitTestBehaviorCommand, just compare the node_id. + if (IsGfxCommand(command1, + fuchsia::ui::gfx::Command::Tag::kSetClipPlanes)) { + return IsGfxCommand(command2, + fuchsia::ui::gfx::Command::Tag::kSetClipPlanes) && + command1.gfx().set_clip_planes().node_id == + command2.gfx().set_clip_planes().node_id; + } + return fidl::Equals(command1, command2); + } + + std::deque expected_; + size_t num_enqueued_commands_ = 0; + fidl::Binding binding_; + fuchsia::ui::scenic::SessionListenerPtr listener_; +}; + +class MockSurfaceProducerSurface + : public SceneUpdateContext::SurfaceProducerSurface { + public: + MockSurfaceProducerSurface(scenic::Session* session, const SkISize& size) + : image_(session, 0, 0, {}), size_(size) {} + + size_t AdvanceAndGetAge() override { return 0; } + + bool FlushSessionAcquireAndReleaseEvents() override { return false; } + + bool IsValid() const override { return false; } + + SkISize GetSize() const override { return size_; } + + void SignalWritesFinished( + const std::function& on_writes_committed) override {} + + scenic::Image* GetImage() override { return &image_; }; + + sk_sp GetSkiaSurface() const override { return nullptr; }; + + private: + scenic::Image image_; + SkISize size_; +}; + +class MockSurfaceProducer : public SceneUpdateContext::SurfaceProducer { + public: + MockSurfaceProducer(scenic::Session* session) : session_(session) {} + std::unique_ptr ProduceSurface( + const SkISize& size, + const LayerRasterCacheKey& layer_key, + std::unique_ptr entity_node) override { + return std::make_unique(session_, size); + } + + // Query a retained entity node (owned by a retained surface) for retained + // rendering. + bool HasRetainedNode(const LayerRasterCacheKey& key) const override { + return false; + } + + scenic::EntityNode* GetRetainedNode(const LayerRasterCacheKey& key) override { + return nullptr; + } + + void SubmitSurface(std::unique_ptr + surface) override {} + + private: + scenic::Session* session_; +}; + +struct TestContext { + // Message loop. + fml::RefPtr loop; + fml::RefPtr task_runner; + + // Session. + MockSession mock_session; + fidl::InterfaceRequest listener_request; + std::unique_ptr session; + + // SceneUpdateContext. + std::unique_ptr mock_surface_producer; + std::unique_ptr scene_update_context; + + // PrerollContext. + MutatorsStack unused_stack; + const Stopwatch unused_stopwatch; + TextureRegistry unused_texture_registry; + std::unique_ptr preroll_context; +}; + +std::unique_ptr InitTest() { + std::unique_ptr context = std::make_unique(); + + // Init message loop. + context->loop = fml::MakeRefCounted(); + context->task_runner = fml::MakeRefCounted(context->loop); + + // Init Session. + fuchsia::ui::scenic::SessionPtr session_ptr; + fuchsia::ui::scenic::SessionListenerPtr listener; + context->listener_request = listener.NewRequest(); + context->mock_session.Bind(session_ptr.NewRequest(), std::move(listener)); + context->session = std::make_unique(std::move(session_ptr)); + + // Init SceneUpdateContext. + context->mock_surface_producer = + std::make_unique(context->session.get()); + context->scene_update_context = std::make_unique( + context->session.get(), context->mock_surface_producer.get()); + context->scene_update_context->set_metrics( + fidl::MakeOptional(fuchsia::ui::gfx::Metrics{1.f, 1.f, 1.f})); + + // Init PrerollContext. + context->preroll_context = std::unique_ptr(new PrerollContext{ + nullptr, // raster_cache (don't consult the cache) + nullptr, // gr_context (used for the raster cache) + nullptr, // external view embedder + context->unused_stack, // mutator stack + nullptr, // SkColorSpace* dst_color_space + kGiantRect, // SkRect cull_rect + false, // layer reads from surface + context->unused_stopwatch, // frame time (dont care) + context->unused_stopwatch, // engine time (dont care) + context->unused_texture_registry, // texture registry (not + // supported) + false, // checkerboard_offscreen_layers + 100.f, // maximum depth allowed for rendering + 1.f // ratio between logical and physical + }); + + return context; +} + +zx_koid_t GetChildLayerId() { + static zx_koid_t sChildLayerId = 17324; + return sChildLayerId++; +} + +class AutoDestroyChildLayerId { + public: + AutoDestroyChildLayerId(zx_koid_t id) : id_(id) {} + ~AutoDestroyChildLayerId() { ViewHolder::Destroy(id_); } + + private: + zx_koid_t id_; +}; + +// Create a hierarchy with PhysicalShapeLayers and ChildSceneLayers, and +// inspect the commands sent to Scenic. +// +// +// What we expect: +// +// The Scenic elevations of the PhysicalShapeLayers are monotically +// increasing, even though the elevations we gave them when creating them are +// decreasing. The two should not have any correlation; we're merely mirror +// the paint order using Scenic elevation. +// +// PhysicalShapeLayers created before/below a ChildView do not get their own +// node; PhysicalShapeLayers created afterward do. +// +// Nested PhysicalShapeLayers are collapsed. +TEST_F(FuchsiaLayerTest, PhysicalShapeLayersAndChildSceneLayers) { + auto test_context = InitTest(); + + // Root. + auto root = std::make_shared(); + SkPath path; + path.addRect(SkRect::MakeWH(10.f, 10.f)); + + // Child #1: PhysicalShapeLayer. + auto physical_shape1 = std::make_shared( + /*color=*/SK_ColorCYAN, + /*shadow_color=*/SK_ColorBLACK, + /*elevation*/ 23.f, path, Clip::antiAlias); + root->Add(physical_shape1); + + // Child #2: ChildSceneLayer. + const zx_koid_t kChildLayerId1 = GetChildLayerId(); + auto [unused_view_token1, unused_view_holder_token1] = + scenic::ViewTokenPair::New(); + ViewHolder::Create(kChildLayerId1, test_context->task_runner, + std::move(unused_view_holder_token1), + /*bind_callback=*/[](scenic::ResourceId id) {}); + // Will destroy only when we go out of scope (i.e. end of the test). + AutoDestroyChildLayerId auto_destroy1(kChildLayerId1); + auto child_view1 = std::make_shared( + kChildLayerId1, SkPoint::Make(1, 1), SkSize::Make(10, 10), + /*hit_testable=*/false); + root->Add(child_view1); + + // Child #3: PhysicalShapeLayer + auto physical_shape2 = std::make_shared( + /*color=*/SK_ColorCYAN, + /*shadow_color=*/SK_ColorBLACK, + /*elevation*/ 21.f, path, Clip::antiAlias); + root->Add(physical_shape2); + + // Grandchild (child of #3): PhysicalShapeLayer + auto physical_shape3 = std::make_shared( + /*color=*/SK_ColorCYAN, + /*shadow_color=*/SK_ColorBLACK, + /*elevation*/ 19.f, path, Clip::antiAlias); + physical_shape2->Add(physical_shape3); + + // Child #4: ChildSceneLayer + const zx_koid_t kChildLayerId2 = GetChildLayerId(); + auto [unused_view_token2, unused_view_holder_token2] = + scenic::ViewTokenPair::New(); + ViewHolder::Create(kChildLayerId2, test_context->task_runner, + std::move(unused_view_holder_token2), + /*bind_callback=*/[](scenic::ResourceId id) {}); + // Will destroy only when we go out of scope (i.e. end of the test). + AutoDestroyChildLayerId auto_destroy2(kChildLayerId2); + auto child_view2 = std::make_shared( + kChildLayerId2, SkPoint::Make(1, 1), SkSize::Make(10, 10), + /*hit_testable=*/false); + root->Add(child_view2); + + // Child #5: PhysicalShapeLayer + auto physical_shape4 = std::make_shared( + /*color=*/SK_ColorCYAN, + /*shadow_color=*/SK_ColorBLACK, + /*elevation*/ 17.f, path, Clip::antiAlias); + root->Add(physical_shape4); + + // Preroll. + root->Preroll(test_context->preroll_context.get(), SkMatrix()); + + // Create another frame to be the "real" root. Required because + // UpdateScene() traversal expects there to already be a top node. + SceneUpdateContext::Frame frame(*(test_context->scene_update_context), + SkRRect::MakeRect(SkRect::MakeWH(100, 100)), + SK_ColorTRANSPARENT, SK_AlphaOPAQUE, + "fuchsia test root"); + + // Submit the list of command we will expect Scenic to see. + // + // Some things we expect: + // + // The Scenic elevations of the PhysicalShapeLayers are monotically + // increasing, even though the elevations we gave them when creating them are + // decreasing. The two should not have any correlation; we're merely mirror + // the paint order using Scenic elevation. + // + // PhysicalShapeLayers created before/below a ChildView do not get their own + // node; PhysicalShapeLayers created afterward do. + // + // Nested PhysicalShapeLayers are collapsed. + + std::vector expected; + + // + // Test root. + // + expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/1)); + expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/2)); + expected.push_back(scenic::NewSetLabelCmd(/*id=*/1, "fuchsia test root")); + expected.push_back(scenic::NewSetTranslationCmd(/*id=*/1, {0, 0})); + expected.push_back(scenic::NewAddChildCmd(/*id=*/1, /*child_id=*/2)); + expected.push_back(scenic::NewSetOpacityCmd(/*id=*/2, kOneMinusEpsilon)); + + // + // Child #1: PhysicalShapeLayer + // + // Expect no new commands! Should be composited into base layer. + + // + // Child #2: ChildSceneLayer. + // + expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/3)); + expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/4)); + auto [view_token1, view_holder_token1] = scenic::ViewTokenPair::New(); + expected.push_back(scenic::NewCreateViewHolderCmd( + /*id=*/5, std::move(view_holder_token1), "")); + expected.push_back(scenic::NewAddChildCmd(/*id=*/4, /*child_id=*/3)); + expected.push_back(scenic::NewSetLabelCmd(/*id=*/4, "flutter::ViewHolder")); + expected.push_back(scenic::NewAddChildCmd(/*id=*/3, /*child_id=*/5)); + expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/4)); + expected.push_back(scenic::NewSetOpacityCmd(/*id=*/4, 1.f)); + expected.push_back(scenic::NewSetTranslationCmd(/*id=*/3, {1, 1, -0.1})); + expected.push_back(scenic::NewSetHitTestBehaviorCmd( + /*id=*/3, /*ignored*/ fuchsia::ui::gfx::HitTestBehavior::kSuppress)); + + // + // Child #3: PhysicalShapeLayer + // + expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/6)); + expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/6)); + expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/7)); + expected.push_back( + scenic::NewSetLabelCmd(/*id=*/6, "flutter::PhysicalShapeLayer")); + expected.push_back(scenic::NewSetTranslationCmd( + /*id=*/6, {0, 0, -kScenicZElevationBetweenLayers})); + expected.push_back(scenic::NewAddChildCmd(/*id=*/6, /*child_id=*/7)); + expected.push_back(scenic::NewSetOpacityCmd(/*id=*/7, kOneMinusEpsilon)); + expected.push_back(scenic::NewSetClipPlanesCmd(/*id=*/6, /*ignored*/ {})); + expected.push_back(scenic::NewCreateShapeNodeCmd(/*id=*/8)); + expected.push_back(scenic::NewCreateRectangleCmd( + /*id=*/9, /*width=*/10, /*height=*/10)); + expected.push_back(scenic::NewSetShapeCmd(/*id=*/8, /*shape_id=*/9)); + expected.push_back(scenic::NewSetTranslationCmd(/*id=*/8, {5, 5, 0})); + expected.push_back(scenic::NewCreateMaterialCmd(/*id=*/10)); + expected.push_back(scenic::NewSetMaterialCmd(/*id=*/8, /*material_id=*/10)); + expected.push_back(scenic::NewAddChildCmd(/*id=*/6, /*child_id=*/8)); + + expected.push_back(scenic::NewCreateImageCmd(/*id=*/11, 0, 0, {})); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/6)); + expected.push_back(scenic::NewSetColorCmd(/*id=*/10, /*r*/ 255, /*g*/ 255, + /*b*/ 255, /*a*/ 255)); + expected.push_back( + scenic::NewSetTextureCmd(/*material_id=*/10, /*texture_id=*/11)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/10)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/9)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/8)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/7)); + + // + // Grandchild (child of #3): PhysicalShapeLayer + // + // Expect no new commands! Should be composited into parent. + + // + // Child #4: ChildSceneLayer + // + expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/12)); + expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/13)); + auto [view_token2, view_holder_token2] = scenic::ViewTokenPair::New(); + expected.push_back(scenic::NewCreateViewHolderCmd( + /*id=*/14, std::move(view_holder_token2), "")); + expected.push_back(scenic::NewAddChildCmd(/*id=*/13, /*child_id=*/12)); + expected.push_back(scenic::NewSetLabelCmd(/*id=*/13, "flutter::ViewHolder")); + expected.push_back(scenic::NewAddChildCmd(/*id=*/12, /*child_id=*/14)); + expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/13)); + expected.push_back(scenic::NewSetOpacityCmd(/*id=*/13, 1.f)); + expected.push_back(scenic::NewSetTranslationCmd(/*id=*/12, {1, 1, -0.1})); + expected.push_back(scenic::NewSetHitTestBehaviorCmd( + /*id=*/12, /*ignored*/ fuchsia::ui::gfx::HitTestBehavior::kSuppress)); + + // + // Child #5: PhysicalShapeLayer + // + expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/15)); + expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/15)); + expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/16)); + expected.push_back( + scenic::NewSetLabelCmd(/*id=*/15, "flutter::PhysicalShapeLayer")); + expected.push_back(scenic::NewSetTranslationCmd( + /*id=*/15, {0, 0, -2 * kScenicZElevationBetweenLayers})); + expected.push_back(scenic::NewAddChildCmd(/*id=*/15, /*child_id=*/16)); + expected.push_back(scenic::NewSetOpacityCmd(/*id=*/16, kOneMinusEpsilon)); + expected.push_back(scenic::NewSetClipPlanesCmd(/*id=*/15, /*ignored*/ {})); + expected.push_back(scenic::NewCreateShapeNodeCmd(/*id=*/17)); + expected.push_back(scenic::NewCreateRectangleCmd( + /*id=*/18, /*width=*/10, /*height=*/10)); + expected.push_back(scenic::NewSetShapeCmd(/*id=*/17, /*shape_id=*/18)); + expected.push_back(scenic::NewSetTranslationCmd(/*id=*/17, {5, 5, 0})); + expected.push_back(scenic::NewCreateMaterialCmd(/*id=*/19)); + expected.push_back(scenic::NewSetMaterialCmd(/*id=*/17, /*material_id=*/19)); + expected.push_back(scenic::NewAddChildCmd(/*id=*/15, /*child_id=*/17)); + + expected.push_back(scenic::NewCreateImageCmd(/*id=*/20, 0, 0, {})); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/15)); + expected.push_back(scenic::NewSetColorCmd(/*id=*/19, /*r*/ 255, /*g*/ 255, + /*b*/ 255, /*a*/ 255)); + expected.push_back( + scenic::NewSetTextureCmd(/*material_id=*/19, /*texture_id=*/20)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/19)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/18)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/17)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/16)); + + test_context->mock_session.SetExpectedCommands(std::move(expected)); + + // Finally, UpdateScene(). The MockSession will check the emitted commands + // against the list above. + root->UpdateScene(*(test_context->scene_update_context)); + + // Run loop until idle, so that the Session receives and processes + // its method calls. + async_loop_run_until_idle( + async_loop_from_dispatcher(async_get_default_dispatcher())); + + // Ensure we saw enough commands. + EXPECT_EQ(72u, test_context->mock_session.num_enqueued_commands()); +} + +// Create a hierarchy with OpacityLayers, PhysicalShapeLayers and +// ChildSceneLayers, and inspect the commands sent to Scenic. +// +// We are interested in verifying that the opacity values of children are +// correct. +// +TEST_F(FuchsiaLayerTest, Opacity) { + auto test_context = InitTest(); + + // Root. + auto root = std::make_shared(); + SkPath path; + path.addRect(SkRect::MakeWH(10.f, 10.f)); + + // OpacityLayer #1 + auto opacity_layer1 = + std::make_shared(127, SkPoint::Make(0, 0)); + root->Add(opacity_layer1); + + // OpacityLayer #2 + auto opacity_layer2 = + std::make_shared(127, SkPoint::Make(0, 0)); + opacity_layer1->Add(opacity_layer2); + + // Child #1: ChildSceneLayer. + const zx_koid_t kChildLayerId1 = GetChildLayerId(); + auto [unused_view_token1, unused_view_holder_token1] = + scenic::ViewTokenPair::New(); + + ViewHolder::Create(kChildLayerId1, test_context->task_runner, + std::move(unused_view_holder_token1), + /*bind_callback=*/[](scenic::ResourceId id) {}); + // Will destroy only when we go out of scope (i.e. end of the test). + AutoDestroyChildLayerId auto_destroy1(kChildLayerId1); + auto child_view1 = std::make_shared( + kChildLayerId1, SkPoint::Make(1, 1), SkSize::Make(10, 10), + /*hit_testable=*/false); + opacity_layer2->Add(child_view1); + + // Child #2: PhysicalShapeLayer. + auto physical_shape1 = std::make_shared( + /*color=*/SK_ColorCYAN, + /*shadow_color=*/SK_ColorBLACK, + /*elevation*/ 23.f, path, Clip::antiAlias); + opacity_layer2->Add(physical_shape1); + + // Preroll. + root->Preroll(test_context->preroll_context.get(), SkMatrix()); + + // Create another frame to be the "real" root. Required because + // UpdateScene() traversal expects there to already be a top node. + SceneUpdateContext::Frame frame(*(test_context->scene_update_context), + SkRRect::MakeRect(SkRect::MakeWH(100, 100)), + SK_ColorTRANSPARENT, SK_AlphaOPAQUE, + "fuchsia test root"); + + // Submit the list of command we will expect Scenic to see. + // + // We are interested in verifying that the opacity values of children are + // correct. + + std::vector expected; + + // + // Test root. + // + expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/1)); + expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/2)); + expected.push_back(scenic::NewSetLabelCmd(/*id=*/1, "fuchsia test root")); + expected.push_back(scenic::NewSetTranslationCmd(/*id=*/1, {0, 0})); + expected.push_back(scenic::NewAddChildCmd(/*id=*/1, /*child_id=*/2)); + expected.push_back(scenic::NewSetOpacityCmd(/*id=*/2, kOneMinusEpsilon)); + + // + // OpacityLayer #1 + // + // Expect no new commands for this. + + // + // OpacityLayer #2 + // + // Expect no new commands for this. + + // + // Child #1: ChildSceneLayer. + // + expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/3)); + expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/4)); + auto [view_token1, view_holder_token1] = scenic::ViewTokenPair::New(); + expected.push_back(scenic::NewCreateViewHolderCmd( + /*id=*/5, std::move(view_holder_token1), "")); + expected.push_back(scenic::NewAddChildCmd(/*id=*/4, /*child_id=*/3)); + expected.push_back(scenic::NewSetLabelCmd(/*id=*/4, "flutter::ViewHolder")); + expected.push_back(scenic::NewAddChildCmd(/*id=*/3, /*child_id=*/5)); + expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/4)); + + // Check opacity value. Extra rounding required because we pass alpha as + // a uint/SkAlpha to SceneUpdateContext::Frame. + float opacity1 = kOneMinusEpsilon * (127 / 255.f) * (127 / 255.f); + opacity1 = SkScalarRoundToInt(opacity1 * 255) / 255.f; + expected.push_back(scenic::NewSetOpacityCmd(/*id=*/4, opacity1)); + expected.push_back(scenic::NewSetTranslationCmd(/*id=*/3, {1, 1, -0.1})); + expected.push_back(scenic::NewSetHitTestBehaviorCmd( + /*id=*/3, /*ignored*/ fuchsia::ui::gfx::HitTestBehavior::kSuppress)); + + // + // Child #2: PhysicalShapeLayer + // + expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/6)); + expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/6)); + expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/7)); + expected.push_back( + scenic::NewSetLabelCmd(/*id=*/6, "flutter::PhysicalShapeLayer")); + expected.push_back(scenic::NewSetTranslationCmd( + /*id=*/6, {0, 0, -kScenicZElevationBetweenLayers})); + expected.push_back(scenic::NewAddChildCmd(/*id=*/6, /*child_id=*/7)); + + // Check opacity value. Extra rounding required because we pass alpha as + // a uint/SkAlpha to SceneUpdateContext::Frame. + float opacity2 = kOneMinusEpsilon * (127 / 255.f) * (127 / 255.f); + opacity2 = SkScalarRoundToInt(opacity2 * 255) / 255.f; + expected.push_back(scenic::NewSetOpacityCmd(/*id=*/7, opacity2)); + expected.push_back(scenic::NewSetClipPlanesCmd(/*id=*/6, /*ignored*/ {})); + expected.push_back(scenic::NewCreateShapeNodeCmd(/*id=*/8)); + expected.push_back(scenic::NewCreateRectangleCmd( + /*id=*/9, /*width=*/10, /*height=*/10)); + expected.push_back(scenic::NewSetShapeCmd(/*id=*/8, /*shape_id=*/9)); + expected.push_back(scenic::NewSetTranslationCmd(/*id=*/8, {5, 5, 0})); + expected.push_back(scenic::NewCreateMaterialCmd(/*id=*/10)); + expected.push_back(scenic::NewSetMaterialCmd(/*id=*/8, + /*material_id=*/10)); + expected.push_back(scenic::NewAddChildCmd(/*id=*/6, + /*child_id=*/8)); + + expected.push_back(scenic::NewCreateImageCmd(/*id=*/11, 0, 0, {})); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/6)); + expected.push_back(scenic::NewSetColorCmd(/*id=*/10, /*r*/ 255, + /*g*/ 255, + /*b*/ 255, /*a*/ 63)); + expected.push_back( + scenic::NewSetTextureCmd(/*material_id=*/10, /*texture_id=*/11)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/10)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/9)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/8)); + expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/7)); + + test_context->mock_session.SetExpectedCommands(std::move(expected)); + + // Finally, UpdateScene(). The MockSession will check the emitted + // commands against the list above. + root->UpdateScene(*(test_context->scene_update_context)); + + // Run loop until idle, so that the Session receives and processes + // its method calls. + async_loop_run_until_idle( + async_loop_from_dispatcher(async_get_default_dispatcher())); + + // Ensure we saw enough commands. + EXPECT_EQ(39u, test_context->mock_session.num_enqueued_commands()); +} + +} // namespace testing +} // namespace flutter diff --git a/engine/src/flutter/flow/layers/layer.h b/engine/src/flutter/flow/layers/layer.h index 50e2b0f7e68..dfd022a350a 100644 --- a/engine/src/flutter/flow/layers/layer.h +++ b/engine/src/flutter/flow/layers/layer.h @@ -66,6 +66,11 @@ struct PrerollContext { float total_elevation = 0.0f; bool has_platform_view = false; bool is_opaque = true; +#if defined(OS_FUCHSIA) + // True if, during the traversal so far, we have seen a child_scene_layer. + // Informs whether a layer needs to be system composited. + bool child_scene_layer_exists_below = false; +#endif // defined(OS_FUCHSIA) }; // Represents a single composited layer. Created on the UI thread but then diff --git a/engine/src/flutter/flow/layers/layer_tree.cc b/engine/src/flutter/flow/layers/layer_tree.cc index 37c28e861bf..61560abbed5 100644 --- a/engine/src/flutter/flow/layers/layer_tree.cc +++ b/engine/src/flutter/flow/layers/layer_tree.cc @@ -83,7 +83,7 @@ void LayerTree::UpdateScene(SceneUpdateContext& context, context, SkRRect::MakeRect( SkRect::MakeWH(frame_size_.width(), frame_size_.height())), - SK_ColorTRANSPARENT, SK_AlphaOPAQUE); + SK_ColorTRANSPARENT, SK_AlphaOPAQUE, "flutter::LayerTree"); if (root_layer_->needs_system_composite()) { root_layer_->UpdateScene(context); } diff --git a/engine/src/flutter/flow/layers/opacity_layer.cc b/engine/src/flutter/flow/layers/opacity_layer.cc index e6ca90a66a4..7899c31784b 100644 --- a/engine/src/flutter/flow/layers/opacity_layer.cc +++ b/engine/src/flutter/flow/layers/opacity_layer.cc @@ -9,11 +9,6 @@ namespace flutter { -// The OpacityLayer has no real "elevation", but we want to avoid Z-fighting -// when using the system compositor. Choose a small but non-zero value for -// this. -constexpr float kOpacityElevationWhenUsingSystemCompositor = 0.01f; - OpacityLayer::OpacityLayer(SkAlpha alpha, const SkPoint& offset) : alpha_(alpha), offset_(offset) { // Ensure OpacityLayer has only one direct child. @@ -40,8 +35,6 @@ void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { SkMatrix child_matrix = matrix; child_matrix.postTranslate(offset_.fX, offset_.fY); - total_elevation_ = context->total_elevation; - context->total_elevation += kOpacityElevationWhenUsingSystemCompositor; context->is_opaque = parent_is_opaque && (alpha_ == SK_AlphaOPAQUE); context->mutators_stack.PushTransform( SkMatrix::MakeTrans(offset_.fX, offset_.fY)); @@ -52,17 +45,7 @@ void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) { context->mutators_stack.Pop(); context->mutators_stack.Pop(); context->is_opaque = parent_is_opaque; - context->total_elevation = total_elevation_; -#if defined(OS_FUCHSIA) - if (needs_system_composite()) { - // When using the system compositor, do not include the offset since we - // are rendering as a separate piece of geometry and the offset will be - // baked into that geometry's transform. - frameRRect_ = SkRRect::MakeRect(paint_bounds()); - set_paint_bounds(SkRect::MakeEmpty()); - } else -#endif { set_paint_bounds(paint_bounds().makeOffset(offset_.fX, offset_.fY)); if (!context->has_platform_view && context->raster_cache && @@ -123,36 +106,10 @@ void OpacityLayer::Paint(PaintContext& context) const { #if defined(OS_FUCHSIA) void OpacityLayer::UpdateScene(SceneUpdateContext& context) { - FML_DCHECK(needs_system_composite()); - TRACE_EVENT0("flutter", "OpacityLayer::UpdateScene"); - - ContainerLayer* container = GetChildContainer(); - FML_DCHECK(!container->layers().empty()); // OpacityLayer can't be a leaf. - - SceneUpdateContext::Transform transform( - context, SkMatrix::MakeTrans(offset_.fX, offset_.fY)); - - // Retained rendering: speedup by reusing a retained entity node if possible. - // When an entity node is reused, no paint layer is added to the frame so we - // won't call PhysicalShapeLayer::Paint. - LayerRasterCacheKey key(unique_id(), context.Matrix()); - if (context.HasRetainedNode(key)) { - TRACE_EVENT_INSTANT0("flutter", "retained layer cache hit"); - const scenic::EntityNode& retained_node = context.GetRetainedNode(key); - FML_DCHECK(context.top_entity()); - FML_DCHECK(retained_node.session() == context.session()); - context.top_entity()->embedder_node().AddChild(retained_node); - return; - } - - TRACE_EVENT_INSTANT0("flutter", "cache miss, creating"); - // If we can't find an existing retained surface, create one. - SceneUpdateContext::Frame frame( - context, frameRRect_, SK_ColorTRANSPARENT, alpha_, - kOpacityElevationWhenUsingSystemCompositor, total_elevation_, this); - frame.AddPaintLayer(container); - - UpdateSceneChildren(context); + float saved_alpha = context.alphaf(); + context.set_alphaf(context.alphaf() * (alpha_ / 255.f)); + ContainerLayer::UpdateScene(context); + context.set_alphaf(saved_alpha); } #endif // defined(OS_FUCHSIA) diff --git a/engine/src/flutter/flow/layers/opacity_layer.h b/engine/src/flutter/flow/layers/opacity_layer.h index efc5dd4f83e..658dd1a7b84 100644 --- a/engine/src/flutter/flow/layers/opacity_layer.h +++ b/engine/src/flutter/flow/layers/opacity_layer.h @@ -43,7 +43,6 @@ class OpacityLayer : public ContainerLayer { SkAlpha alpha_; SkPoint offset_; SkRRect frameRRect_; - float total_elevation_ = 0.0f; FML_DISALLOW_COPY_AND_ASSIGN(OpacityLayer); }; diff --git a/engine/src/flutter/flow/layers/physical_shape_layer.cc b/engine/src/flutter/flow/layers/physical_shape_layer.cc index c64049222a8..15bad4f5e1f 100644 --- a/engine/src/flutter/flow/layers/physical_shape_layer.cc +++ b/engine/src/flutter/flow/layers/physical_shape_layer.cc @@ -52,23 +52,31 @@ void PhysicalShapeLayer::Preroll(PrerollContext* context, context->total_elevation += elevation_; total_elevation_ = context->total_elevation; +#if defined(OS_FUCHSIA) + child_layer_exists_below_ = context->child_scene_layer_exists_below; + context->child_scene_layer_exists_below = false; +#endif + SkRect child_paint_bounds; PrerollChildren(context, matrix, &child_paint_bounds); + +#if defined(OS_FUCHSIA) + if (child_layer_exists_below_) { + set_needs_system_composite(true); + } + context->child_scene_layer_exists_below = + context->child_scene_layer_exists_below || child_layer_exists_below_; +#endif context->total_elevation -= elevation_; if (elevation_ == 0) { set_paint_bounds(path_.getBounds()); } else { -#if defined(OS_FUCHSIA) - // Let the system compositor draw all shadows for us. - set_needs_system_composite(true); -#else // We will draw the shadow in Paint(), so add some margin to the paint // bounds to leave space for the shadow. We fill this whole region and clip // children to it so we don't need to join the child paint bounds. set_paint_bounds(ComputeShadowBounds(path_.getBounds(), elevation_, context->frame_device_pixel_ratio)); -#endif // defined(OS_FUCHSIA) } } @@ -78,30 +86,52 @@ void PhysicalShapeLayer::UpdateScene(SceneUpdateContext& context) { FML_DCHECK(needs_system_composite()); TRACE_EVENT0("flutter", "PhysicalShapeLayer::UpdateScene"); - // Retained rendering: speedup by reusing a retained entity node if possible. - // When an entity node is reused, no paint layer is added to the frame so we - // won't call PhysicalShapeLayer::Paint. - LayerRasterCacheKey key(unique_id(), context.Matrix()); - if (context.HasRetainedNode(key)) { - TRACE_EVENT_INSTANT0("flutter", "retained layer cache hit"); - const scenic::EntityNode& retained_node = context.GetRetainedNode(key); - FML_DCHECK(context.top_entity()); - FML_DCHECK(retained_node.session() == context.session()); - context.top_entity()->entity_node().AddChild(retained_node); - return; - } + // If there is embedded Fuchsia content in the scene (a ChildSceneLayer), + // PhysicalShapeLayers that appear above the embedded content will be turned + // into their own Scenic layers. + if (child_layer_exists_below_) { + float global_scenic_elevation = + context.GetGlobalElevationForNextScenicLayer(); + float local_scenic_elevation = + global_scenic_elevation - context.scenic_elevation(); + float z_translation = -local_scenic_elevation; - TRACE_EVENT_INSTANT0("flutter", "cache miss, creating"); - // If we can't find an existing retained surface, create one. - SceneUpdateContext::Frame frame(context, frameRRect_, color_, SK_AlphaOPAQUE, - elevation_, total_elevation_, this); - for (auto& layer : layers()) { - if (layer->needs_painting()) { - frame.AddPaintLayer(layer.get()); + // Retained rendering: speedup by reusing a retained entity node if + // possible. When an entity node is reused, no paint layer is added to the + // frame so we won't call PhysicalShapeLayer::Paint. + LayerRasterCacheKey key(unique_id(), context.Matrix()); + if (context.HasRetainedNode(key)) { + TRACE_EVENT_INSTANT0("flutter", "retained layer cache hit"); + scenic::EntityNode* retained_node = context.GetRetainedNode(key); + FML_DCHECK(context.top_entity()); + FML_DCHECK(retained_node->session() == context.session()); + + // Re-adjust the elevation. + retained_node->SetTranslation(0.f, 0.f, z_translation); + + context.top_entity()->entity_node().AddChild(*retained_node); + return; } - } - UpdateSceneChildren(context); + TRACE_EVENT_INSTANT0("flutter", "cache miss, creating"); + // If we can't find an existing retained surface, create one. + SceneUpdateContext::Frame frame(context, frameRRect_, SK_ColorTRANSPARENT, + SkScalarRoundToInt(context.alphaf() * 255), + "flutter::PhysicalShapeLayer", + z_translation, this); + + frame.AddPaintLayer(this); + + // Node: UpdateSceneChildren needs to be called here so that |frame| is + // still in scope (and therefore alive) while UpdateSceneChildren is being + // called. + float scenic_elevation = context.scenic_elevation(); + context.set_scenic_elevation(scenic_elevation + local_scenic_elevation); + ContainerLayer::UpdateSceneChildren(context); + context.set_scenic_elevation(scenic_elevation); + } else { + ContainerLayer::UpdateSceneChildren(context); + } } #endif // defined(OS_FUCHSIA) @@ -110,7 +140,18 @@ void PhysicalShapeLayer::Paint(PaintContext& context) const { TRACE_EVENT0("flutter", "PhysicalShapeLayer::Paint"); FML_DCHECK(needs_painting()); +#if defined(OS_FUCHSIA) + // TODO(mikejurka,dworsham,liyl): Re-enable shadow drawing here. + // Shadows are not rendered for PhysicalShapeLayers that exist as separate + // system services; this is to maintain compatibility with the previous + // implementation and has the added benefit of requiring smaller textures, + // since extra space is not needed for the shadows. This behavior might change + // after clients adjust their usage of PhysicalShaperLayer to make elevation + // correlate to desired shadow size. + if (false && !child_layer_exists_below_ && elevation_ != 0) { +#else if (elevation_ != 0) { +#endif DrawShadow(context.leaf_nodes_canvas, path_, shadow_color_, elevation_, SkColorGetA(color_) != 0xff, context.frame_device_pixel_ratio); } diff --git a/engine/src/flutter/flow/layers/physical_shape_layer.h b/engine/src/flutter/flow/layers/physical_shape_layer.h index 40ff19cfac3..106327f47ec 100644 --- a/engine/src/flutter/flow/layers/physical_shape_layer.h +++ b/engine/src/flutter/flow/layers/physical_shape_layer.h @@ -42,6 +42,9 @@ class PhysicalShapeLayer : public ContainerLayer { float total_elevation() const { return total_elevation_; } private: +#if defined(OS_FUCHSIA) + bool child_layer_exists_below_ = false; +#endif SkColor color_; SkColor shadow_color_; float elevation_ = 0.0f; diff --git a/engine/src/flutter/flow/layers/physical_shape_layer_unittests.cc b/engine/src/flutter/flow/layers/physical_shape_layer_unittests.cc index d13116c9c44..df247e394f1 100644 --- a/engine/src/flutter/flow/layers/physical_shape_layer_unittests.cc +++ b/engine/src/flutter/flow/layers/physical_shape_layer_unittests.cc @@ -126,17 +126,11 @@ TEST_F(PhysicalShapeLayerTest, ElevationSimple) { layer->Preroll(preroll_context(), SkMatrix()); // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and // their shadows , so we do not do any painting there. -#if defined(OS_FUCHSIA) - EXPECT_EQ(layer->paint_bounds(), kEmptyRect); - EXPECT_FALSE(layer->needs_painting()); - EXPECT_TRUE(layer->needs_system_composite()); -#else EXPECT_EQ(layer->paint_bounds(), PhysicalShapeLayer::ComputeShadowBounds(layer_path.getBounds(), initial_elevation, 1.0f)); EXPECT_TRUE(layer->needs_painting()); EXPECT_FALSE(layer->needs_system_composite()); -#endif EXPECT_EQ(layer->total_elevation(), initial_elevation); // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and @@ -187,18 +181,12 @@ TEST_F(PhysicalShapeLayerTest, ElevationComplex) { // On Fuchsia, the system compositor handles all elevated // PhysicalShapeLayers and their shadows , so we do not do any painting // there. -#if defined(OS_FUCHSIA) - EXPECT_EQ(layers[i]->paint_bounds(), kEmptyRect); - EXPECT_FALSE(layers[i]->needs_painting()); - EXPECT_TRUE(layers[i]->needs_system_composite()); -#else EXPECT_EQ(layers[i]->paint_bounds(), (PhysicalShapeLayer::ComputeShadowBounds( layer_path.getBounds(), initial_elevations[i], 1.0f /* pixel_ratio */))); EXPECT_TRUE(layers[i]->needs_painting()); EXPECT_FALSE(layers[i]->needs_system_composite()); -#endif EXPECT_EQ(layers[i]->total_elevation(), total_elevations[i]); } diff --git a/engine/src/flutter/flow/layers/transform_layer.cc b/engine/src/flutter/flow/layers/transform_layer.cc index 5c7bc440732..dea9b8f18eb 100644 --- a/engine/src/flutter/flow/layers/transform_layer.cc +++ b/engine/src/flutter/flow/layers/transform_layer.cc @@ -56,8 +56,12 @@ void TransformLayer::UpdateScene(SceneUpdateContext& context) { TRACE_EVENT0("flutter", "TransformLayer::UpdateScene"); FML_DCHECK(needs_system_composite()); - SceneUpdateContext::Transform transform(context, transform_); - UpdateSceneChildren(context); + if (!transform_.isIdentity()) { + SceneUpdateContext::Transform transform(context, transform_); + UpdateSceneChildren(context); + } else { + UpdateSceneChildren(context); + } } #endif // defined(OS_FUCHSIA) diff --git a/engine/src/flutter/flow/scene_update_context.cc b/engine/src/flutter/flow/scene_update_context.cc index 59524bc9c6e..407751406bf 100644 --- a/engine/src/flutter/flow/scene_update_context.cc +++ b/engine/src/flutter/flow/scene_update_context.cc @@ -72,14 +72,9 @@ void SceneUpdateContext::CreateFrame(scenic::EntityNode entity_node, // and possibly for its texture. // TODO(SCN-137): Need to be able to express the radii as vectors. scenic::ShapeNode shape_node(session()); - scenic::RoundedRectangle shape( - session_, // session - rrect.width(), // width - rrect.height(), // height - rrect.radii(SkRRect::kUpperLeft_Corner).x(), // top_left_radius - rrect.radii(SkRRect::kUpperRight_Corner).x(), // top_right_radius - rrect.radii(SkRRect::kLowerRight_Corner).x(), // bottom_right_radius - rrect.radii(SkRRect::kLowerLeft_Corner).x() // bottom_left_radius + scenic::Rectangle shape(session_, // session + rrect.width(), // width + rrect.height() // height ); shape_node.SetShape(shape); shape_node.SetTranslation(shape_bounds.width() * 0.5f + shape_bounds.left(), @@ -222,6 +217,9 @@ SceneUpdateContext::ExecutePaintTasks(CompositorContext::ScopedFrame& frame) { surfaces_to_submit.emplace_back(std::move(task.surface)); } paint_tasks_.clear(); + alpha_ = 1.f; + topmost_global_scenic_elevation_ = kScenicZElevationBetweenLayers; + scenic_elevation_ = 0.f; return surfaces_to_submit; } @@ -244,19 +242,22 @@ SceneUpdateContext::Transform::Transform(SceneUpdateContext& context, : Entity(context), previous_scale_x_(context.top_scale_x_), previous_scale_y_(context.top_scale_y_) { + entity_node().SetLabel("flutter::Transform"); if (!transform.isIdentity()) { // TODO(SCN-192): The perspective and shear components in the matrix // are not handled correctly. MatrixDecomposition decomposition(transform); if (decomposition.IsValid()) { + // Don't allow clients to control the z dimension; we control that + // instead to make sure layers appear in proper order. entity_node().SetTranslation(decomposition.translation().x(), // decomposition.translation().y(), // - -decomposition.translation().z() // + 0.f // ); entity_node().SetScale(decomposition.scale().x(), // decomposition.scale().y(), // - decomposition.scale().z() // + 0.f // ); context.top_scale_x_ *= decomposition.scale().x(); context.top_scale_y_ *= decomposition.scale().y(); @@ -277,6 +278,7 @@ SceneUpdateContext::Transform::Transform(SceneUpdateContext& context, : Entity(context), previous_scale_x_(context.top_scale_x_), previous_scale_y_(context.top_scale_y_) { + entity_node().SetLabel("flutter::Transform"); if (scale_x != 1.f || scale_y != 1.f || scale_z != 1.f) { entity_node().SetScale(scale_x, scale_y, scale_z); context.top_scale_x_ *= scale_x; @@ -293,8 +295,8 @@ SceneUpdateContext::Frame::Frame(SceneUpdateContext& context, const SkRRect& rrect, SkColor color, SkAlpha opacity, - float local_elevation, - float world_elevation, + std::string label, + float z_translation, Layer* layer) : Entity(context), rrect_(rrect), @@ -303,23 +305,14 @@ SceneUpdateContext::Frame::Frame(SceneUpdateContext& context, opacity_node_(context.session()), paint_bounds_(SkRect::MakeEmpty()), layer_(layer) { - const float depth = context.frame_physical_depth(); - if (depth > -1 && world_elevation > depth) { - // TODO(mklim): Deal with bounds overflow more elegantly. We'd like to be - // able to have developers specify the behavior here to alternatives besides - // clamping, like normalization on some arbitrary curve. - - // Clamp the local z coordinate at our max bound. Take into account the - // parent z position here to fix clamping in cases where the child is - // overflowing because of its parents. - const float parent_elevation = world_elevation - local_elevation; - local_elevation = depth - parent_elevation; - } - if (local_elevation != 0.0) { - entity_node().SetTranslation(0.f, 0.f, -local_elevation); - } + entity_node().SetLabel(label); + entity_node().SetTranslation(0.f, 0.f, z_translation); entity_node().AddChild(opacity_node_); - opacity_node_.SetOpacity(opacity_ / 255.0f); + // Scenic currently lacks an API to enable rendering of alpha channel; alpha + // channels are only rendered if there is a OpacityNode higher in the tree + // with opacity != 1. For now, clamp to a infinitesimally smaller value than + // 1, which does not cause visual problems in practice. + opacity_node_.SetOpacity(std::min(kOneMinusEpsilon, opacity_ / 255.0f)); } SceneUpdateContext::Frame::~Frame() { @@ -348,6 +341,7 @@ void SceneUpdateContext::Frame::AddPaintLayer(Layer* layer) { SceneUpdateContext::Clip::Clip(SceneUpdateContext& context, const SkRect& shape_bounds) : Entity(context) { + entity_node().SetLabel("flutter::Clip"); SetEntityNodeClipPlanes(entity_node(), shape_bounds); } diff --git a/engine/src/flutter/flow/scene_update_context.h b/engine/src/flutter/flow/scene_update_context.h index 77b0925cc65..a4d1156cc60 100644 --- a/engine/src/flutter/flow/scene_update_context.h +++ b/engine/src/flutter/flow/scene_update_context.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_FLOW_SCENE_UPDATE_CONTEXT_H_ #define FLUTTER_FLOW_SCENE_UPDATE_CONTEXT_H_ +#include #include #include #include @@ -22,6 +23,15 @@ namespace flutter { class Layer; +// Scenic currently lacks an API to enable rendering of alpha channel; this only +// happens if there is a OpacityNode higher in the tree with opacity != 1. For +// now, clamp to a infinitesimally smaller value than 1, which does not cause +// visual problems in practice. +constexpr float kOneMinusEpsilon = 1 - FLT_EPSILON; + +// How much layers are separated in Scenic z elevation. +constexpr float kScenicZElevationBetweenLayers = 10.f; + class SceneUpdateContext { public: class SurfaceProducerSurface { @@ -59,7 +69,7 @@ class SceneUpdateContext { // Query a retained entity node (owned by a retained surface) for retained // rendering. virtual bool HasRetainedNode(const LayerRasterCacheKey& key) const = 0; - virtual const scenic::EntityNode& GetRetainedNode( + virtual scenic::EntityNode* GetRetainedNode( const LayerRasterCacheKey& key) = 0; virtual void SubmitSurface( @@ -105,8 +115,8 @@ class SceneUpdateContext { const SkRRect& rrect, SkColor color, SkAlpha opacity, - float local_elevation = 0.0f, - float parent_elevation = 0.0f, + std::string label, + float z_translation = 0.0f, Layer* layer = nullptr); virtual ~Frame(); @@ -175,10 +185,25 @@ class SceneUpdateContext { bool HasRetainedNode(const LayerRasterCacheKey& key) const { return surface_producer_->HasRetainedNode(key); } - const scenic::EntityNode& GetRetainedNode(const LayerRasterCacheKey& key) { + scenic::EntityNode* GetRetainedNode(const LayerRasterCacheKey& key) { return surface_producer_->GetRetainedNode(key); } + // The cumulative alpha value based on all the parent OpacityLayers. + void set_alphaf(float alpha) { alpha_ = alpha; } + float alphaf() { return alpha_; } + + // The global scenic elevation at a given point in the traversal. + float scenic_elevation() { return scenic_elevation_; } + + void set_scenic_elevation(float elevation) { scenic_elevation_ = elevation; } + + float GetGlobalElevationForNextScenicLayer() { + float elevation = topmost_global_scenic_elevation_; + topmost_global_scenic_elevation_ += kScenicZElevationBetweenLayers; + return elevation; + } + private: struct PaintTask { std::unique_ptr surface; @@ -236,6 +261,10 @@ class SceneUpdateContext { float frame_device_pixel_ratio_ = 1.0f; // Ratio between logical and physical pixels. + float alpha_ = 1.0f; + float scenic_elevation_ = 0.f; + float topmost_global_scenic_elevation_ = kScenicZElevationBetweenLayers; + std::vector paint_tasks_; FML_DISALLOW_COPY_AND_ASSIGN(SceneUpdateContext); diff --git a/engine/src/flutter/flow/view_holder.cc b/engine/src/flutter/flow/view_holder.cc index 7f8929d9337..66ce3672a84 100644 --- a/engine/src/flutter/flow/view_holder.cc +++ b/engine/src/flutter/flow/view_holder.cc @@ -102,13 +102,17 @@ ViewHolder::ViewHolder(fml::RefPtr ui_task_runner, void ViewHolder::UpdateScene(SceneUpdateContext& context, const SkPoint& offset, const SkSize& size, + SkAlpha opacity, bool hit_testable) { if (pending_view_holder_token_.value) { entity_node_ = std::make_unique(context.session()); + opacity_node_ = + std::make_unique(context.session()); view_holder_ = std::make_unique( context.session(), std::move(pending_view_holder_token_), "Flutter SceneHost"); - + opacity_node_->AddChild(*entity_node_); + opacity_node_->SetLabel("flutter::ViewHolder"); entity_node_->Attach(*view_holder_); ui_task_runner_->PostTask( [bind_callback = std::move(pending_bind_callback_), @@ -117,9 +121,11 @@ void ViewHolder::UpdateScene(SceneUpdateContext& context, }); } FML_DCHECK(entity_node_); + FML_DCHECK(opacity_node_); FML_DCHECK(view_holder_); - context.top_entity()->embedder_node().AddChild(*entity_node_); + context.top_entity()->embedder_node().AddChild(*opacity_node_); + opacity_node_->SetOpacity(opacity / 255.0f); entity_node_->SetTranslation(offset.x(), offset.y(), -0.1f); entity_node_->SetHitTestBehavior( hit_testable ? fuchsia::ui::gfx::HitTestBehavior::kDefault diff --git a/engine/src/flutter/flow/view_holder.h b/engine/src/flutter/flow/view_holder.h index 82d43eba826..10677b81576 100644 --- a/engine/src/flutter/flow/view_holder.h +++ b/engine/src/flutter/flow/view_holder.h @@ -57,12 +57,14 @@ class ViewHolder { void UpdateScene(SceneUpdateContext& context, const SkPoint& offset, const SkSize& size, + SkAlpha opacity, bool hit_testable); private: fml::RefPtr ui_task_runner_; std::unique_ptr entity_node_; + std::unique_ptr opacity_node_; std::unique_ptr view_holder_; fuchsia::ui::views::ViewHolderToken pending_view_holder_token_; diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface.h b/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface.h index 4c67c9d5538..2315e0bcee7 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface.h +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface.h @@ -133,10 +133,11 @@ class VulkanSurface final // For better safety in retained rendering, Flutter uses a retained // |EntityNode| associated with the retained surface instead of using the // retained surface directly. Hence Flutter can't modify the surface during - // retained rendering. - const scenic::EntityNode& GetRetainedNode() { + // retained rendering. However, the node itself is modifiable to be able + // to adjust its position. + scenic::EntityNode* GetRetainedNode() { used_in_retained_rendering_ = true; - return *retained_node_; + return retained_node_.get(); } // Check whether the retained surface (and its associated |EntityNode|) is diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface_pool.h b/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface_pool.h index 00b40437de6..23f85513562 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface_pool.h +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface_pool.h @@ -43,8 +43,7 @@ class VulkanSurfacePool final { return retained_surfaces_.find(key) != retained_surfaces_.end(); } // For |VulkanSurfaceProducer::GetRetainedNode|. - const scenic::EntityNode& GetRetainedNode( - const flutter::LayerRasterCacheKey& key) { + scenic::EntityNode* GetRetainedNode(const flutter::LayerRasterCacheKey& key) { FML_DCHECK(HasRetainedNode(key)); return retained_surfaces_[key].vk_surface->GetRetainedNode(); } diff --git a/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface_producer.h b/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface_producer.h index 77457bac32c..5ea1415cf53 100644 --- a/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface_producer.h +++ b/engine/src/flutter/shell/platform/fuchsia/flutter/vulkan_surface_producer.h @@ -49,7 +49,7 @@ class VulkanSurfaceProducer final } // |flutter::SceneUpdateContext::GetRetainedNode| - const scenic::EntityNode& GetRetainedNode( + scenic::EntityNode* GetRetainedNode( const flutter::LayerRasterCacheKey& key) override { return surface_pool_->GetRetainedNode(key); }