diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 7a06e1efa04bd..5f02f7592b679 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/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/flow/BUILD.gn b/flow/BUILD.gn index 14ceec4dadc5c..e25b5dc8957a3 100644 --- a/flow/BUILD.gn +++ b/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/flow/layers/child_scene_layer.cc b/flow/layers/child_scene_layer.cc index 4a5358053928f..4e533130c1d4b 100644 --- a/flow/layers/child_scene_layer.cc +++ b/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/flow/layers/fuchsia_layer_unittests.cc b/flow/layers/fuchsia_layer_unittests.cc new file mode 100644 index 0000000000000..2a859226ab5ed --- /dev/null +++ b/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/flow/layers/layer.h b/flow/layers/layer.h index 50e2b0f7e68cf..dfd022a350a0f 100644 --- a/flow/layers/layer.h +++ b/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/flow/layers/layer_tree.cc b/flow/layers/layer_tree.cc index 37c28e861bf8a..61560abbed5d4 100644 --- a/flow/layers/layer_tree.cc +++ b/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/flow/layers/opacity_layer.cc b/flow/layers/opacity_layer.cc index e6ca90a66a42f..7899c31784b89 100644 --- a/flow/layers/opacity_layer.cc +++ b/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/flow/layers/opacity_layer.h b/flow/layers/opacity_layer.h index efc5dd4f83e04..658dd1a7b8488 100644 --- a/flow/layers/opacity_layer.h +++ b/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/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc index c64049222a8e7..15bad4f5e1f6b 100644 --- a/flow/layers/physical_shape_layer.cc +++ b/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; + + // 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); - 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()); + 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/flow/layers/physical_shape_layer.h b/flow/layers/physical_shape_layer.h index 40ff19cfac348..106327f47ec9c 100644 --- a/flow/layers/physical_shape_layer.h +++ b/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/flow/layers/physical_shape_layer_unittests.cc b/flow/layers/physical_shape_layer_unittests.cc index d13116c9c44a0..df247e394f1f9 100644 --- a/flow/layers/physical_shape_layer_unittests.cc +++ b/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/flow/layers/transform_layer.cc b/flow/layers/transform_layer.cc index 5c7bc44073236..dea9b8f18ebd3 100644 --- a/flow/layers/transform_layer.cc +++ b/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/flow/scene_update_context.cc b/flow/scene_update_context.cc index 59524bc9c6e56..407751406bfd6 100644 --- a/flow/scene_update_context.cc +++ b/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/flow/scene_update_context.h b/flow/scene_update_context.h index 77b0925cc6503..a4d1156cc6021 100644 --- a/flow/scene_update_context.h +++ b/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/flow/view_holder.cc b/flow/view_holder.cc index 7f8929d933705..66ce3672a8431 100644 --- a/flow/view_holder.cc +++ b/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/flow/view_holder.h b/flow/view_holder.h index 82d43eba826d4..10677b8157680 100644 --- a/flow/view_holder.h +++ b/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/shell/platform/fuchsia/flutter/vulkan_surface.h b/shell/platform/fuchsia/flutter/vulkan_surface.h index 4c67c9d5538c2..2315e0bcee70e 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface.h +++ b/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/shell/platform/fuchsia/flutter/vulkan_surface_pool.h b/shell/platform/fuchsia/flutter/vulkan_surface_pool.h index 00b40437de612..23f8551356265 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface_pool.h +++ b/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/shell/platform/fuchsia/flutter/vulkan_surface_producer.h b/shell/platform/fuchsia/flutter/vulkan_surface_producer.h index 77457bac32cdd..5ea1415cf537f 100644 --- a/shell/platform/fuchsia/flutter/vulkan_surface_producer.h +++ b/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); }