| // 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 <fuchsia/ui/scenic/cpp/fidl.h> |
| #include <fuchsia/ui/scenic/cpp/fidl_test_base.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async/default.h> |
| #include <lib/fidl/cpp/optional.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <lib/ui/scenic/cpp/commands.h> |
| #include <lib/ui/scenic/cpp/id.h> |
| #include <lib/ui/scenic/cpp/view_token_pair.h> |
| |
| #include <deque> |
| |
| #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/layers/transform_layer.h" |
| #include "flutter/flow/view_holder.h" |
| #include "flutter/fml/platform/fuchsia/message_loop_fuchsia.h" |
| #include "flutter/fml/task_runner.h" |
| #include "gtest/gtest.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 QuaternionValueToString( |
| fuchsia::ui::gfx::QuaternionValue value) { |
| return "{" + std::to_string(value.value.x) + ", " + |
| std::to_string(value.value.y) + ", " + |
| std::to_string(value.value.z) + ", " + |
| std::to_string(value.value.w) + "}"; |
| } |
| |
| 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_scale().value); |
| case fuchsia::ui::gfx::Command::Tag::kSetRotation: |
| return "SetRotation id: " + std::to_string(cmd.set_rotation().id) + |
| " value: " + QuaternionValueToString(cmd.set_rotation().value); |
| 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<fuchsia::ui::scenic::Command> 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<fuchsia::ui::gfx::Command> gfx_cmds) { |
| std::deque<fuchsia::ui::scenic::Command> 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<fuchsia::ui::scenic::Command> expected_; |
| size_t num_enqueued_commands_ = 0; |
| fidl::Binding<fuchsia::ui::scenic::Session> binding_; |
| fuchsia::ui::scenic::SessionListenerPtr listener_; |
| }; |
| |
| class MockSessionWrapper : public flutter::SessionWrapper { |
| public: |
| MockSessionWrapper(fuchsia::ui::scenic::SessionPtr session_ptr) |
| : session_(std::move(session_ptr)) {} |
| ~MockSessionWrapper() override = default; |
| |
| scenic::Session* get() override { return &session_; } |
| void Present() override { session_.Flush(); } |
| |
| private: |
| scenic::Session session_; |
| }; |
| |
| struct TestContext { |
| // Message loop. |
| fml::RefPtr<fml::MessageLoopFuchsia> loop; |
| fml::RefPtr<fml::TaskRunner> task_runner; |
| |
| // Session. |
| fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener> listener_request; |
| MockSession mock_session; |
| std::unique_ptr<MockSessionWrapper> mock_session_wrapper; |
| |
| // SceneUpdateContext. |
| std::shared_ptr<SceneUpdateContext> scene_update_context; |
| |
| // PrerollContext. |
| MutatorsStack unused_stack; |
| const Stopwatch unused_stopwatch; |
| TextureRegistry unused_texture_registry; |
| std::unique_ptr<PrerollContext> preroll_context; |
| }; |
| |
| std::unique_ptr<TestContext> InitTest() { |
| std::unique_ptr<TestContext> context = std::make_unique<TestContext>(); |
| |
| // Init message loop. |
| context->loop = fml::MakeRefCounted<fml::MessageLoopFuchsia>(); |
| context->task_runner = fml::MakeRefCounted<fml::TaskRunner>(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->mock_session_wrapper = |
| std::make_unique<MockSessionWrapper>(std::move(session_ptr)); |
| |
| // Init SceneUpdateContext. |
| context->scene_update_context = std::make_shared<SceneUpdateContext>( |
| "fuchsia_layer_unittest", fuchsia::ui::views::ViewToken(), |
| scenic::ViewRefPair::New(), *(context->mock_session_wrapper)); |
| |
| // Init PrerollContext. |
| context->preroll_context = std::unique_ptr<PrerollContext>(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 |
| 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. |
| // |
| // This test has been temporarily disabled: fxb/52028 |
| TEST_F(FuchsiaLayerTest, DISABLED_PhysicalShapeLayersAndChildSceneLayers) { |
| auto test_context = InitTest(); |
| |
| // Root. |
| auto root = std::make_shared<ContainerLayer>(); |
| SkPath path; |
| path.addRect(SkRect::MakeWH(10.f, 10.f)); |
| |
| // Child #1: PhysicalShapeLayer. |
| auto physical_shape1 = std::make_shared<PhysicalShapeLayer>( |
| /*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<ChildSceneLayer>( |
| 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<PhysicalShapeLayer>( |
| /*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<PhysicalShapeLayer>( |
| /*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<ChildSceneLayer>( |
| 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<PhysicalShapeLayer>( |
| /*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<fuchsia::ui::gfx::Command> 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); |
| |
| test_context->mock_session_wrapper->Present(); |
| |
| // 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, TransformLayer, PhysicalShapeLayers |
| // and ChildSceneLayers, and inspect the commands sent to Scenic. |
| // |
| // We are interested in verifying that the opacity values of children are |
| // correct, and the transform values as well. |
| // |
| // This test has been temporarily disabled: fxb/52028 |
| TEST_F(FuchsiaLayerTest, DISABLED_OpacityAndTransformLayer) { |
| auto test_context = InitTest(); |
| |
| // Root. |
| auto root = std::make_shared<ContainerLayer>(); |
| SkPath path; |
| path.addRect(SkRect::MakeWH(10.f, 10.f)); |
| |
| // OpacityLayer #1 |
| auto opacity_layer1 = |
| std::make_shared<OpacityLayer>(127, SkPoint::Make(0, 0)); |
| root->Add(opacity_layer1); |
| |
| // OpacityLayer #2 |
| auto opacity_layer2 = |
| std::make_shared<OpacityLayer>(127, SkPoint::Make(0, 0)); |
| opacity_layer1->Add(opacity_layer2); |
| |
| // TransformLayer |
| SkMatrix translate_and_scale; |
| translate_and_scale.setScaleTranslate(1.1f, 1.1f, 2.f, 2.f); |
| auto transform_layer = std::make_shared<TransformLayer>(translate_and_scale); |
| opacity_layer2->Add(transform_layer); |
| |
| // TransformLayer 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<ChildSceneLayer>( |
| kChildLayerId1, SkPoint::Make(1, 1), SkSize::Make(10, 10), |
| /*hit_testable=*/false); |
| transform_layer->Add(child_view1); |
| |
| // TransformLayer Child #2: PhysicalShapeLayer. |
| auto physical_shape1 = std::make_shared<PhysicalShapeLayer>( |
| /*color=*/SK_ColorCYAN, |
| /*shadow_color=*/SK_ColorBLACK, |
| /*elevation*/ 23.f, path, Clip::antiAlias); |
| transform_layer->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<fuchsia::ui::gfx::Command> 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, 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. |
| |
| // |
| // TransformLayer |
| // |
| // |
| expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/3)); |
| expected.push_back(scenic::NewAddChildCmd(/*id=*/2, /*child_id=*/3)); |
| expected.push_back(scenic::NewSetLabelCmd(/*id=*/3, "flutter::Transform")); |
| expected.push_back(scenic::NewSetTranslationCmd(/*id=*/3, {2.f, 2.f, 0.f})); |
| expected.push_back(scenic::NewSetScaleCmd(/*id=*/3, {1.1f, 1.1f, 1.f})); |
| expected.push_back(scenic::NewSetRotationCmd(/*id=*/3, {0.f, 0.f, 0.f, 1.f})); |
| |
| // |
| // TransformLayer Child #1: ChildSceneLayer. |
| // |
| expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/4)); |
| expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/5)); |
| auto [view_token1, view_holder_token1] = scenic::ViewTokenPair::New(); |
| expected.push_back(scenic::NewCreateViewHolderCmd( |
| /*id=*/6, std::move(view_holder_token1), "")); |
| expected.push_back(scenic::NewAddChildCmd(/*id=*/5, /*child_id=*/4)); |
| expected.push_back(scenic::NewSetLabelCmd(/*id=*/5, "flutter::ViewHolder")); |
| expected.push_back(scenic::NewAddChildCmd(/*id=*/4, /*child_id=*/6)); |
| expected.push_back(scenic::NewAddChildCmd(/*id=*/3, /*child_id=*/5)); |
| |
| // 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=*/5, opacity1)); |
| expected.push_back(scenic::NewSetTranslationCmd(/*id=*/4, {1, 1, -0.1})); |
| expected.push_back(scenic::NewSetHitTestBehaviorCmd( |
| /*id=*/4, /*ignored*/ fuchsia::ui::gfx::HitTestBehavior::kSuppress)); |
| |
| // |
| // TransformLayer Child #2: PhysicalShapeLayer |
| // |
| expected.push_back(scenic::NewCreateEntityNodeCmd(/*id=*/7)); |
| expected.push_back(scenic::NewAddChildCmd(/*id=*/3, /*child_id=*/7)); |
| expected.push_back(scenic::NewCreateOpacityNodeCmdHACK(/*id=*/8)); |
| expected.push_back( |
| scenic::NewSetLabelCmd(/*id=*/7, "flutter::PhysicalShapeLayer")); |
| expected.push_back(scenic::NewSetTranslationCmd( |
| /*id=*/7, {0, 0, -kScenicZElevationBetweenLayers})); |
| expected.push_back(scenic::NewAddChildCmd(/*id=*/7, /*child_id=*/8)); |
| |
| // 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=*/8, opacity2)); |
| expected.push_back(scenic::NewSetClipPlanesCmd(/*id=*/7, /*ignored*/ {})); |
| expected.push_back(scenic::NewCreateShapeNodeCmd(/*id=*/9)); |
| expected.push_back(scenic::NewCreateRectangleCmd( |
| /*id=*/10, /*width=*/10, /*height=*/10)); |
| expected.push_back(scenic::NewSetShapeCmd(/*id=*/9, /*shape_id=*/10)); |
| expected.push_back(scenic::NewSetTranslationCmd(/*id=*/9, {5, 5, 0})); |
| expected.push_back(scenic::NewCreateMaterialCmd(/*id=*/11)); |
| expected.push_back(scenic::NewSetMaterialCmd(/*id=*/9, |
| /*material_id=*/11)); |
| expected.push_back(scenic::NewAddChildCmd(/*id=*/7, |
| /*child_id=*/9)); |
| |
| expected.push_back(scenic::NewCreateImageCmd(/*id=*/12, 0, 0, {})); |
| expected.push_back(scenic::NewReleaseResourceCmd(/*id=*/7)); |
| expected.push_back(scenic::NewSetColorCmd(/*id=*/11, /*r*/ 255, |
| /*g*/ 255, |
| /*b*/ 255, /*a*/ 63)); |
| expected.push_back( |
| scenic::NewSetTextureCmd(/*material_id=*/11, /*texture_id=*/12)); |
| expected.push_back(scenic::NewReleaseResourceCmd(/*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=*/3)); |
| |
| 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); |
| |
| test_context->mock_session_wrapper->Present(); |
| |
| // 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(46u, test_context->mock_session.num_enqueued_commands()); |
| } |
| |
| } // namespace testing |
| } // namespace flutter |