blob: 4b6d44ea884b1a5c0cb8c48540cdfe821ac800ed [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 <lib/sys/cpp/testing/component_context_provider.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <lib/ui/scenic/cpp/view_ref_pair.h>
#include <lib/ui/scenic/cpp/view_token_pair.h>
#include <lib/zx/eventpair.h>
#include "src/ui/scenic/lib/gfx/resources/compositor/compositor.h"
#include "src/ui/scenic/lib/gfx/tests/session_test.h"
namespace scenic_impl {
namespace gfx {
namespace test {
class SceneGraphTest : public SessionTest {
public:
SceneGraphTest() = default;
void TearDown() override {
SessionTest::TearDown();
scene_graph_.reset();
}
SessionContext CreateSessionContext() override {
SessionContext session_context = SessionTest::CreateSessionContext();
FXL_DCHECK(!view_linker_);
FXL_DCHECK(!scene_graph_);
view_linker_ = std::make_unique<ViewLinker>();
scene_graph_ = std::make_unique<SceneGraph>(context_provider_.context());
session_context.view_linker = view_linker_.get();
session_context.scene_graph = scene_graph_->GetWeakPtr();
return session_context;
}
CommandContext CreateCommandContext() {
return CommandContext(/*uploader=*/nullptr, /*sysmem=*/nullptr,
/*display_manager=*/nullptr, scene_graph_->GetWeakPtr());
}
SceneGraph* scene_graph() const { return scene_graph_.get(); }
private:
sys::testing::ComponentContextProvider context_provider_;
std::unique_ptr<SceneGraph> scene_graph_;
std::unique_ptr<ViewLinker> view_linker_;
};
fit::function<std::optional<glm::mat4>()> NoGlobalTransform() {
return [] { return std::nullopt; };
}
fit::function<void(ViewHolderPtr)> DummyAddAnnotation() {
// Annotation is not used by these tests.
return [](auto) {};
}
bool ContainsCompositor(const std::vector<CompositorWeakPtr>& compositors, Compositor* compositor) {
auto it =
std::find_if(compositors.begin(), compositors.end(),
[compositor](const CompositorWeakPtr& c) { return c.get() == compositor; });
return it != compositors.end();
};
TEST_F(SceneGraphTest, CompositorsGetAddedAndRemoved) {
sys::testing::ComponentContextProvider context_provider;
SceneGraph scene_graph(context_provider.context());
ASSERT_EQ(0u, scene_graph.compositors().size());
{
CompositorPtr c1 = Compositor::New(session(), session()->id(), 1, scene_graph.GetWeakPtr());
ASSERT_EQ(1u, scene_graph.compositors().size());
ASSERT_TRUE(ContainsCompositor(scene_graph.compositors(), c1.get()));
ASSERT_EQ(scene_graph.first_compositor().get(), c1.get());
{
CompositorPtr c2 = Compositor::New(session(), session()->id(), 2, scene_graph.GetWeakPtr());
ASSERT_EQ(2u, scene_graph.compositors().size());
ASSERT_TRUE(ContainsCompositor(scene_graph.compositors(), c1.get()));
ASSERT_TRUE(ContainsCompositor(scene_graph.compositors(), c2.get()));
ASSERT_EQ(scene_graph.first_compositor().get(), c1.get());
}
ASSERT_EQ(1u, scene_graph.compositors().size());
ASSERT_TRUE(ContainsCompositor(scene_graph.compositors(), c1.get()));
ASSERT_EQ(scene_graph.first_compositor().get(), c1.get());
}
}
TEST_F(SceneGraphTest, LookupCompositor) {
sys::testing::ComponentContextProvider context_provider;
SceneGraph scene_graph(context_provider.context());
CompositorPtr c1 = Compositor::New(session(), session()->id(), 1, scene_graph.GetWeakPtr());
auto c1_weak = scene_graph.GetCompositor(c1->global_id());
ASSERT_EQ(c1.get(), c1_weak.get());
}
TEST_F(SceneGraphTest, FirstCompositorIsStable) {
sys::testing::ComponentContextProvider context_provider;
SceneGraph scene_graph(context_provider.context());
CompositorPtr c1 = Compositor::New(session(), session()->id(), 1, scene_graph.GetWeakPtr());
ASSERT_EQ(scene_graph.first_compositor().get(), c1.get());
{
CompositorPtr c2 = Compositor::New(session(), session()->id(), 2, scene_graph.GetWeakPtr());
ASSERT_EQ(scene_graph.first_compositor().get(), c1.get());
CompositorPtr c3 = Compositor::New(session(), session()->id(), 3, scene_graph.GetWeakPtr());
ASSERT_EQ(scene_graph.first_compositor().get(), c1.get());
{
CompositorPtr c4 = Compositor::New(session(), session()->id(), 4, scene_graph.GetWeakPtr());
ASSERT_EQ(scene_graph.first_compositor().get(), c1.get());
}
ASSERT_EQ(scene_graph.first_compositor().get(), c1.get());
c1 = nullptr;
// First compositor follows order of creation.
ASSERT_EQ(2u, scene_graph.compositors().size());
ASSERT_EQ(scene_graph.first_compositor().get(), c2.get());
}
}
TEST_F(SceneGraphTest, RequestFocusChange) {
// Construct ViewTree with 2 ViewRefs in a parent-child relationship.
sys::testing::ComponentContextProvider context_provider;
SceneGraph scene_graph(context_provider.context());
auto parent_view_pair = scenic::ViewRefPair::New();
const zx_koid_t parent_koid = ExtractKoid(parent_view_pair.view_ref);
auto child_view_pair = scenic::ViewRefPair::New();
const zx_koid_t child_koid = ExtractKoid(child_view_pair.view_ref);
{
ViewTreeUpdates updates;
updates.push_back(ViewTreeNewRefNode{.view_ref = std::move(parent_view_pair.view_ref),
.may_receive_focus = [] { return true; },
.global_transform = NoGlobalTransform(),
.add_annotation_view_holder = DummyAddAnnotation(),
.session_id = 1u});
updates.push_back(ViewTreeNewAttachNode{.koid = 1111u});
updates.push_back(ViewTreeNewRefNode{.view_ref = std::move(child_view_pair.view_ref),
.may_receive_focus = [] { return true; },
.global_transform = NoGlobalTransform(),
.add_annotation_view_holder = DummyAddAnnotation(),
.session_id = 2u});
updates.push_back(ViewTreeMakeGlobalRoot{.koid = parent_koid});
updates.push_back(ViewTreeConnectToParent{.child = child_koid, .parent = 1111u});
updates.push_back(ViewTreeConnectToParent{.child = 1111u, .parent = parent_koid});
scene_graph.StageViewTreeUpdates(std::move(updates));
scene_graph.ProcessViewTreeUpdates();
}
ASSERT_EQ(scene_graph.view_tree().focus_chain().size(), 1u);
EXPECT_EQ(scene_graph.view_tree().focus_chain()[0], parent_koid);
auto status = scene_graph.RequestFocusChange(parent_koid, child_koid);
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kAccept);
ASSERT_EQ(scene_graph.view_tree().focus_chain().size(), 2u);
EXPECT_EQ(scene_graph.view_tree().focus_chain()[0], parent_koid);
EXPECT_EQ(scene_graph.view_tree().focus_chain()[1], child_koid);
}
TEST_F(SceneGraphTest, RequestFocusChangeButMayNotReceiveFocus) {
// Construct ViewTree with 2 ViewRefs in a parent-child relationship.
sys::testing::ComponentContextProvider context_provider;
SceneGraph scene_graph(context_provider.context());
auto parent_view_pair = scenic::ViewRefPair::New();
const zx_koid_t parent_koid = ExtractKoid(parent_view_pair.view_ref);
auto child_view_pair = scenic::ViewRefPair::New();
const zx_koid_t child_koid = ExtractKoid(child_view_pair.view_ref);
{
ViewTreeUpdates updates;
updates.push_back(ViewTreeNewRefNode{.view_ref = std::move(parent_view_pair.view_ref),
.may_receive_focus = [] { return true; },
.global_transform = NoGlobalTransform(),
.add_annotation_view_holder = DummyAddAnnotation(),
.session_id = 1u});
updates.push_back(ViewTreeNewAttachNode{.koid = 1111u});
updates.push_back(ViewTreeNewRefNode{.view_ref = std::move(child_view_pair.view_ref),
.may_receive_focus = [] { return false; }, // Different!
.global_transform = NoGlobalTransform(),
.add_annotation_view_holder = DummyAddAnnotation(),
.session_id = 2u});
updates.push_back(ViewTreeMakeGlobalRoot{.koid = parent_koid});
updates.push_back(ViewTreeConnectToParent{.child = child_koid, .parent = 1111u});
updates.push_back(ViewTreeConnectToParent{.child = 1111u, .parent = parent_koid});
scene_graph.StageViewTreeUpdates(std::move(updates));
scene_graph.ProcessViewTreeUpdates();
}
ASSERT_EQ(scene_graph.view_tree().focus_chain().size(), 1u);
EXPECT_EQ(scene_graph.view_tree().focus_chain()[0], parent_koid);
auto status = scene_graph.RequestFocusChange(parent_koid, child_koid);
EXPECT_EQ(status, ViewTree::FocusChangeStatus::kErrorRequestCannotReceiveFocus);
ASSERT_EQ(scene_graph.view_tree().focus_chain().size(), 1u);
EXPECT_EQ(scene_graph.view_tree().focus_chain()[0], parent_koid);
}
class SceneGraphViewLookupTest : public SceneGraphTest {
public:
enum : uint32_t {
kCompositorId = 20001,
kLayerStackId,
kLayerId,
kSceneId,
kCameraId,
kRendererId,
kEntityNodeId,
kViewHolder1Id,
kView1Id,
kViewHolder2Id,
kView2Id,
};
SceneGraphViewLookupTest() = default;
void SetUp() override {
SceneGraphTest::SetUp();
SetUpScene();
}
private:
void SetUpScene() {
// Create the following Resource Graph:
//
// Compositor --> LayerStack --> Layer --> Renderer --> Camera --> Scene
// |
// v
// EntityNode
Apply(scenic::NewCreateCompositorCmd(kCompositorId));
Apply(scenic::NewCreateLayerStackCmd(kLayerStackId));
Apply(scenic::NewSetLayerStackCmd(kCompositorId, kLayerStackId));
Apply(scenic::NewCreateLayerCmd(kLayerId));
Apply(scenic::NewSetSizeCmd(kLayerId, {1024, 768}));
Apply(scenic::NewAddLayerCmd(kLayerStackId, kLayerId));
Apply(scenic::NewCreateSceneCmd(kSceneId));
Apply(scenic::NewCreateCameraCmd(kCameraId, kSceneId));
Apply(scenic::NewCreateRendererCmd(kRendererId));
Apply(scenic::NewSetCameraCmd(kRendererId, kCameraId));
Apply(scenic::NewSetRendererCmd(kLayerId, kRendererId));
Apply(scenic::NewCreateEntityNodeCmd(kEntityNodeId));
Apply(scenic::NewAddChildCmd(kSceneId, kEntityNodeId));
}
};
TEST_F(SceneGraphViewLookupTest, SuccessfulLookup) {
// Consider the following Resource Graph:
//
// Scene
// |
// EntityNode
// /------------------|----------------\
// | |
// v v
// ViewHolder1 ViewHolder2
// .` | .` |
// .` v .` v
// View1 ==> ViewNode1 View2 ==> ViewNode2
//
// We should be able to locate View2 from |view2_ref|.
//
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view2_token, view_holder2_token] = scenic::ViewTokenPair::New();
auto [view2_ctrl_ref, view2_ref] = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view2_ref_for_creation;
view2_ref.Clone(&view2_ref_for_creation);
// Create Views.
auto session_view1 = CreateSession();
auto session_view2 = CreateSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(&cmds,
scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), "view 1"));
session_view2->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView2Id, std::move(view2_token), std::move(view2_ctrl_ref),
std::move(view2_ref_for_creation), "view 2"));
cmds.Flush();
// Create other nodes.
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
Apply(scenic::NewCreateViewHolderCmd(kViewHolder2Id, std::move(view_holder2_token), "holder 2"));
// Attach ViewHolder1 and ViewHolder2 to scene.
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder2Id));
// Lookup View2 in session_view2's ResourceMap to verify that it is created.
ViewPtr view2_ptr = session_view2->resources()->FindResource<View>(kView2Id);
EXPECT_TRUE(view2_ptr);
// Lookup View2 using View2's ViewRef.
ViewPtr view_found = scene_graph()->LookupViewByViewRef(std::move(view2_ref));
EXPECT_TRUE(view_found == view2_ptr);
}
TEST_F(SceneGraphViewLookupTest, LookupInvalidViewRef) {
// Consider the following Resource Graph:
//
// Scene
// |
// EntityNode
// |
// v
// ViewHolder1
// .` |
// .` v
// View1 ==> ViewNode1
//
// If we provide an invalid ViewRef to LookupViewByViewRef() function, we
// should get nullptr.
//
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view1_ctrl_ref, view1_ref] = scenic::ViewRefPair::New();
// Create Views.
auto session_view1 = CreateSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), std::move(view1_ctrl_ref),
std::move(view1_ref), "view 1"));
cmds.Flush();
// Create other nodes.
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
// Attach ViewHolder1 to scene.
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
// Lookup View using an invalid ViewRef object. Should return nullptr.
fuchsia::ui::views::ViewRef view_ref_invalid;
ViewPtr view_found = scene_graph()->LookupViewByViewRef(std::move(view_ref_invalid));
EXPECT_FALSE(view_found);
}
TEST_F(SceneGraphViewLookupTest, CannotFindUnattachedView) {
// Consider the following Resource Graph:
//
// Scene
// |
// EntityNode
// |
// v
// ViewHolder1 ViewHolder2
// .` | .` |
// .` v .` v
// View1 ==> ViewNode1 View2 ==> ViewNode2
//
// We can locate View2 but we should not be able to find it in the Scene
// Graph because it is not attached to any Scene.
//
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view2_token, view_holder2_token] = scenic::ViewTokenPair::New();
auto [view1_ctrl_ref, view1_ref] = scenic::ViewRefPair::New();
auto [view2_ctrl_ref, view2_ref] = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view2_ref_for_creation;
view2_ref.Clone(&view2_ref_for_creation);
// Create Views.
auto session_view1 = CreateSession();
auto session_view2 = CreateSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(&cmds,
scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), "view 1"));
session_view2->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView2Id, std::move(view2_token), std::move(view2_ctrl_ref),
std::move(view2_ref_for_creation), "view 2"));
cmds.Flush();
// Create other nodes.
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
Apply(scenic::NewCreateViewHolderCmd(kViewHolder2Id, std::move(view_holder2_token), "holder 2"));
// Here we attach only ViewHolder1 to scene.
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
// Lookup View2 in session_view2's ResourceMap to verify that it is created.
ViewPtr view2_ptr = session_view2->resources()->FindResource<View>(kView2Id);
EXPECT_TRUE(view2_ptr);
// Lookup View2 using View2's ViewRef; we should not find it.
ViewPtr view_found = scene_graph()->LookupViewByViewRef(std::move(view2_ref));
EXPECT_FALSE(view_found);
}
TEST_F(SceneGraphViewLookupTest, CannotFindDestroyedView) {
// Consider the following Resource Graph:
//
// Scene
// |
// EntityNode
// |
// v
// ViewHolder1
// .` |
// .` v
// View1 ==> ViewNode1
//
// We first create these resources and then we destroy the View. Then
// we should not be able to find it in the Scene Graph.
//
auto [view1_token, view_holder1_token] = scenic::ViewTokenPair::New();
auto [view1_ctrl_ref, view1_ref] = scenic::ViewRefPair::New();
fuchsia::ui::views::ViewRef view1_ref_for_creation;
view1_ref.Clone(&view1_ref_for_creation);
// Create Views.
auto session_view1 = CreateSession();
CommandContext cmds = CreateCommandContext();
session_view1->ApplyCommand(
&cmds, scenic::NewCreateViewCmd(kView1Id, std::move(view1_token), std::move(view1_ctrl_ref),
std::move(view1_ref_for_creation), "view 1"));
cmds.Flush();
// Create other nodes.
Apply(scenic::NewCreateViewHolderCmd(kViewHolder1Id, std::move(view_holder1_token), "holder 1"));
// Here we attach only ViewHolder1 to scene.
Apply(scenic::NewAddChildCmd(kEntityNodeId, kViewHolder1Id));
{
// Lookup View1 in session_view1's ResourceMap to verify that it is created.
ViewPtr view1_ptr = session_view1->resources()->FindResource<View>(kView1Id);
EXPECT_TRUE(view1_ptr);
// Verify that we can now lookup View1 using View1's ViewRef.
fuchsia::ui::views::ViewRef view1_ref_for_lookup;
view1_ref.Clone(&view1_ref_for_lookup);
ViewPtr view_found = scene_graph()->LookupViewByViewRef(std::move(view1_ref_for_lookup));
EXPECT_TRUE(view_found);
}
// --------------------------------------------------------------------------
// Destroy the View.
session_view1->ApplyCommand(&cmds, scenic::NewReleaseResourceCmd(kView1Id));
cmds.Flush();
{
// Lookup View1 in session_view1's ResourceMap to verify that it is destroyed.
ViewPtr view1_ptr = session_view1->resources()->FindResource<View>(kView1Id);
EXPECT_FALSE(view1_ptr);
// Verify that now we cannot lookup View1 using View1's ViewRef since it is
// already destroyed.
ViewPtr view_found = scene_graph()->LookupViewByViewRef(std::move(view1_ref));
EXPECT_FALSE(view_found);
}
}
} // namespace test
} // namespace gfx
} // namespace scenic_impl