blob: c1e02ccc86796a1df944355c8bd980edcb44497b [file] [log] [blame]
// Copyright 2018 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 <fuchsia/ui/gfx/cpp/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <fuchsia/ui/scenic/cpp/fidl.h>
#include <fuchsia/ui/views/cpp/fidl.h>
#include <lib/fostr/fidl/fuchsia/ui/gfx/formatting.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <memory>
#include <string>
#include <vector>
#include "garnet/lib/ui/gfx/displays/display_manager.h"
#include "garnet/lib/ui/gfx/engine/engine.h"
#include "garnet/lib/ui/gfx/engine/hit.h"
#include "garnet/lib/ui/gfx/engine/hit_tester.h"
#include "garnet/lib/ui/gfx/resources/compositor/compositor.h"
#include "garnet/lib/ui/gfx/resources/compositor/layer_stack.h"
#include "garnet/lib/ui/gfx/tests/mocks.h"
#include "garnet/lib/ui/scenic/event_reporter.h"
#include "garnet/lib/ui/scenic/util/error_reporter.h"
#include "garnet/lib/ui/scenic/util/print_event.h"
#include "gtest/gtest.h"
#include "src/ui/lib/escher/forward_declarations.h"
#include "lib/fostr/fidl/fuchsia/ui/scenic/formatting.h"
#include "lib/gtest/test_loop_fixture.h"
#include "lib/ui/input/cpp/formatting.h"
#include "lib/ui/scenic/cpp/commands.h"
#include "lib/ui/scenic/cpp/view_token_pair.h"
#include "src/lib/fxl/logging.h"
// The test setup here is sufficiently different from hittest_unittest.cc to
// merit its own file. We access the global hit test through the compositor,
// instead of through a session.
namespace scenic_impl {
namespace gfx {
namespace test {
// Session wrapper that references a common Engine.
//
// This class calls Session::TearDown directly and so avoids pulling in
// SessionHandler and SessionManager; these make a call to TearDown that's
// triggered by Engine::RenderFrame, and we don't need that here.
class CustomSession {
public:
CustomSession(SessionId id, SessionContext session_context) {
session_ = std::make_unique<SessionForTest>(id, std::move(session_context));
}
~CustomSession() {}
void Apply(::fuchsia::ui::gfx::Command command) {
CommandContext empty_command_context(nullptr);
bool result =
session_->ApplyCommand(&empty_command_context, std::move(command));
ASSERT_TRUE(result) << "Failed to apply: " << command; // Fail fast.
}
private:
std::unique_ptr<SessionForTest> session_;
};
// Loop fixture provides dispatcher for Engine's EventTimestamper.
using MultiSessionHitTestTest = ::gtest::TestLoopFixture;
// A comprehensive test that sets up three independent sessions, with
// View/ViewHolder pairs, and checks if global hit testing has access to
// hittable nodes across all sessions.
TEST_F(MultiSessionHitTestTest, GlobalHits) {
DisplayManager display_manager;
display_manager.SetDefaultDisplayForTests(std::make_unique<Display>(
/*id*/ 0, /*px-width*/ 9, /*px-height*/ 9));
sys::testing::ComponentContextProvider context_provider_;
std::unique_ptr<Engine> engine = std::make_unique<EngineForTest>(
context_provider_.context(), &display_manager,
/*release fence signaller*/ nullptr);
// Create our tokens for View/ViewHolder creation.
auto [view_token_1, view_holder_token_1] = scenic::ViewTokenPair::New();
auto [view_token_2, view_holder_token_2] = scenic::ViewTokenPair::New();
// Root session sets up the scene and two view holders.
CustomSession s_r(0, engine->session_context());
{
const uint32_t kCompositorId = 1001;
const uint32_t kLayerStackId = 1002;
const uint32_t kLayerId = 1003;
s_r.Apply(scenic::NewCreateCompositorCmd(kCompositorId));
s_r.Apply(scenic::NewCreateLayerStackCmd(kLayerStackId));
s_r.Apply(scenic::NewSetLayerStackCmd(kCompositorId, kLayerStackId));
s_r.Apply(scenic::NewCreateLayerCmd(kLayerId));
s_r.Apply(scenic::NewSetSizeCmd(
kLayerId, (float[2]){/*px-width*/ 9, /*px-height*/ 9}));
s_r.Apply(scenic::NewAddLayerCmd(kLayerStackId, kLayerId));
const uint32_t kSceneId = 1004; // Hit
const uint32_t kCameraId = 1005;
const uint32_t kRendererId = 1006;
s_r.Apply(scenic::NewCreateSceneCmd(kSceneId));
s_r.Apply(scenic::NewCreateCameraCmd(kCameraId, kSceneId));
s_r.Apply(scenic::NewCreateRendererCmd(kRendererId));
s_r.Apply(scenic::NewSetCameraCmd(kRendererId, kCameraId));
s_r.Apply(scenic::NewSetRendererCmd(kLayerId, kRendererId));
// TODO(SCN-885) - Adjust hit count; an EntityNode shouldn't be hit.
const uint32_t kRootNodeId = 1007; // Hit
s_r.Apply(scenic::NewCreateEntityNodeCmd(kRootNodeId));
const uint32_t kViewHolder1Id = 1008; // Hit
s_r.Apply(scenic::NewAddChildCmd(kSceneId, kRootNodeId));
s_r.Apply(scenic::NewCreateViewHolderCmd(
kViewHolder1Id, std::move(view_holder_token_1), "viewholder_1"));
s_r.Apply(scenic::NewAddChildCmd(kRootNodeId, kViewHolder1Id));
const uint32_t kViewHolder2Id = 1009; // Hit
s_r.Apply(scenic::NewCreateViewHolderCmd(
kViewHolder2Id, std::move(view_holder_token_2), "viewholder_2"));
s_r.Apply(scenic::NewAddChildCmd(kRootNodeId, kViewHolder2Id));
}
// Two sessions (s_1 and s_2) create an overlapping and hittable surface.
CustomSession s_1(1, engine->session_context());
{
const uint32_t kViewId = 2001; // Hit
s_1.Apply(
scenic::NewCreateViewCmd(kViewId, std::move(view_token_1), "view_1"));
const uint32_t kRootNodeId = 2002; // Hit
s_1.Apply(scenic::NewCreateEntityNodeCmd(kRootNodeId));
s_1.Apply(scenic::NewAddChildCmd(kViewId, kRootNodeId));
const uint32_t kChildId = 2003; // Hit
s_1.Apply(scenic::NewCreateShapeNodeCmd(kChildId));
s_1.Apply(scenic::NewAddChildCmd(kRootNodeId, kChildId));
s_1.Apply(scenic::NewSetTranslationCmd(kChildId,
(float[3]){4.f, 4.f, /*z*/ -2.f}));
const uint32_t kShapeId = 2004;
s_1.Apply(scenic::NewCreateRectangleCmd(kShapeId, /*px-width*/ 9.f,
/*px-height*/ 9.f));
s_1.Apply(scenic::NewSetShapeCmd(kChildId, kShapeId));
}
CustomSession s_2(2, engine->session_context());
{
const uint32_t kViewId = 3001; // Hit
s_2.Apply(
scenic::NewCreateViewCmd(kViewId, std::move(view_token_2), "view_2"));
const uint32_t kRootNodeId = 3002; // Hit
s_2.Apply(scenic::NewCreateEntityNodeCmd(kRootNodeId));
s_2.Apply(scenic::NewAddChildCmd(kViewId, kRootNodeId));
const uint32_t kChildId = 3003; // Hit
s_2.Apply(scenic::NewCreateShapeNodeCmd(kChildId));
s_2.Apply(scenic::NewAddChildCmd(kRootNodeId, kChildId));
s_2.Apply(scenic::NewSetTranslationCmd(kChildId,
(float[3]){4.f, 4.f, /*z*/ -3.f}));
const uint32_t kShapeId = 3004;
s_2.Apply(scenic::NewCreateRectangleCmd(kShapeId, /*px-width*/ 9.f,
/*px-height*/ 9.f));
s_2.Apply(scenic::NewSetShapeCmd(kChildId, kShapeId));
}
#if 0
FXL_LOG(INFO) << engine->DumpScenes(); // Handy debugging.
#endif
std::vector<Hit> hits;
{
// Models input subsystem's access to Engine internals.
// For simplicity, we use the first (and only) compositor and layer stack.
const CompositorWeakPtr& compositor =
engine->scene_graph()->first_compositor();
ASSERT_TRUE(compositor);
LayerStackPtr layer_stack = compositor->layer_stack();
ASSERT_NE(layer_stack.get(), nullptr);
escher::ray4 ray;
ray.origin = escher::vec4(4.f, 4.f, 1.f, 1.f);
ray.direction = escher::vec4(0.f, 0.f, -1.f, 0.f);
GlobalHitTester hit_tester;
hits = layer_stack->HitTest(ray, &hit_tester);
}
// All that for this!
EXPECT_EQ(hits.size(), 10u) << "Should see ten hits across three sessions.";
}
} // namespace test
} // namespace gfx
} // namespace scenic_impl