| // 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 |