blob: b1e7d2be05500f066ed0ea3daa425045d84caedb [file] [log] [blame]
// 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 <deque>
#include "gtest/gtest.h"
#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 "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"
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 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<void(void)>& on_writes_committed) override {}
scenic::Image* GetImage() override { return &image_; };
sk_sp<SkSurface> 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<SceneUpdateContext::SurfaceProducerSurface> ProduceSurface(
const SkISize& size,
const LayerRasterCacheKey& layer_key,
std::unique_ptr<scenic::EntityNode> entity_node) override {
return std::make_unique<MockSurfaceProducerSurface>(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<SceneUpdateContext::SurfaceProducerSurface>
surface) override {}
private:
scenic::Session* session_;
};
struct TestContext {
// Message loop.
fml::RefPtr<fml::MessageLoopFuchsia> loop;
fml::RefPtr<fml::TaskRunner> task_runner;
// Session.
MockSession mock_session;
fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener> listener_request;
std::unique_ptr<scenic::Session> session;
// SceneUpdateContext.
std::unique_ptr<MockSurfaceProducer> mock_surface_producer;
std::unique_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->session = std::make_unique<scenic::Session>(std::move(session_ptr));
// Init SceneUpdateContext.
context->mock_surface_producer =
std::make_unique<MockSurfaceProducer>(context->session.get());
context->scene_update_context = std::make_unique<SceneUpdateContext>(
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<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
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.
//
// 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->session->Flush();
// 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->session->Flush();
// 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