| // 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 "garnet/examples/ui/hello_scene_manager/app.h" |
| |
| #if defined(countof) |
| // Workaround for compiler error due to Zircon defining countof() as a macro. |
| // Redefines countof() using GLM_COUNTOF(), which currently provides a more |
| // sophisticated implementation anyway. |
| #undef countof |
| #include <glm/glm.hpp> |
| #define countof(X) GLM_COUNTOF(X) |
| #else |
| // No workaround required. |
| #include <glm/glm.hpp> |
| #endif |
| |
| #include <glm/gtc/type_ptr.hpp> |
| #include <glm/gtx/quaternion.hpp> |
| |
| #include "lib/app/cpp/connect.h" |
| #include "lib/escher/util/image_utils.h" |
| |
| #include "lib/fxl/functional/make_copyable.h" |
| #include "lib/fxl/logging.h" |
| |
| #include "garnet/bin/ui/scene_manager/tests/util.h" |
| #include "lib/ui/scenic/client/host_memory.h" |
| #include "lib/ui/scenic/fidl/ops.fidl.h" |
| #include "lib/ui/scenic/fidl/scene_manager.fidl.h" |
| #include "lib/ui/scenic/fidl/session.fidl.h" |
| #include "lib/ui/scenic/fidl_helpers.h" |
| #include "lib/ui/scenic/types.h" |
| |
| using namespace mozart; |
| using namespace scenic_lib; |
| |
| namespace hello_scene_manager { |
| |
| static constexpr uint64_t kBillion = 1000000000; |
| |
| App::App() |
| : application_context_(app::ApplicationContext::CreateFromStartupInfo()), |
| loop_(fsl::MessageLoop::GetCurrent()) { |
| // Connect to the SceneManager service. |
| scene_manager_ = |
| application_context_->ConnectToEnvironmentService<scenic::SceneManager>(); |
| scene_manager_.set_error_handler([this] { |
| FXL_LOG(INFO) << "Lost connection to SceneManager service."; |
| loop_->QuitNow(); |
| }); |
| scene_manager_->GetDisplayInfo([this](scenic::DisplayInfoPtr display_info) { |
| Init(std::move(display_info)); |
| }); |
| } |
| |
| void App::InitCheckerboardMaterial(Material* uninitialized_material) { |
| // Generate a checkerboard material. This is a multi-step process: |
| // - generate pixels for the material. |
| // - create a VMO that contains these pixels. |
| // - duplicate the VMO handle and use it to create a Session Memory obj. |
| // - use the Memory obj to create an Image obj. |
| // - use the Image obj as a Material's texture. |
| size_t checkerboard_width = 8; |
| size_t checkerboard_height = 8; |
| size_t checkerboard_pixels_size; |
| auto checkerboard_pixels = escher::image_utils::NewGradientPixels( |
| checkerboard_width, checkerboard_height, &checkerboard_pixels_size); |
| |
| HostMemory checkerboard_memory(session_.get(), checkerboard_pixels_size); |
| memcpy(checkerboard_memory.data_ptr(), checkerboard_pixels.get(), |
| checkerboard_pixels_size); |
| |
| // Create an Image to wrap the checkerboard. |
| auto checkerboard_image_info = scenic::ImageInfo::New(); |
| checkerboard_image_info->width = checkerboard_width; |
| checkerboard_image_info->height = checkerboard_height; |
| const size_t kBytesPerPixel = 4u; |
| checkerboard_image_info->stride = checkerboard_width * kBytesPerPixel; |
| checkerboard_image_info->pixel_format = |
| scenic::ImageInfo::PixelFormat::BGRA_8; |
| checkerboard_image_info->color_space = scenic::ImageInfo::ColorSpace::SRGB; |
| checkerboard_image_info->tiling = scenic::ImageInfo::Tiling::LINEAR; |
| |
| HostImage checkerboard_image(checkerboard_memory, 0, |
| std::move(checkerboard_image_info)); |
| |
| uninitialized_material->SetTexture(checkerboard_image.id()); |
| } |
| |
| void App::CreateExampleScene(float display_width, float display_height) { |
| auto session = session_.get(); |
| |
| // The top-level nesting for drawing anything is compositor -> layer-stack |
| // -> layer. Layer content can come from an image, or by rendering a scene. |
| // In this case, we do the latter, so we nest layer -> renderer -> camera -> |
| // scene. |
| compositor_ = std::make_unique<DisplayCompositor>(session); |
| LayerStack layer_stack(session); |
| Layer layer(session); |
| Renderer renderer(session); |
| Scene scene(session); |
| camera_ = std::make_unique<Camera>(scene); |
| |
| compositor_->SetLayerStack(layer_stack); |
| layer_stack.AddLayer(layer); |
| layer.SetSize(display_width, display_height); |
| layer.SetRenderer(renderer); |
| renderer.SetCamera(camera_->id()); |
| |
| // Set up lights. |
| AmbientLight ambient_light(session); |
| DirectionalLight directional_light(session); |
| scene.AddLight(ambient_light); |
| scene.AddLight(directional_light); |
| ambient_light.SetColor(0.3f, 0.3f, 0.3f); |
| directional_light.SetColor(0.7f, 0.7f, 0.7f); |
| directional_light.SetDirection(1.f, 1.f, -2.f); |
| |
| // Create an EntityNode to serve as the scene root. |
| EntityNode root_node(session); |
| scene.AddChild(root_node.id()); |
| |
| static constexpr float kPaneMargin = 100.f; |
| static const float pane_width = (display_width - 3 * kPaneMargin) / 2.f; |
| static const float pane_height = display_height - 2 * kPaneMargin; |
| |
| // The root node will enclose two "panes", each with a rounded-rect part |
| // that acts as a background clipper. |
| RoundedRectangle pane_shape(session, pane_width, pane_height, 20, 20, 80, 10); |
| Material pane_material(session); |
| pane_material.SetColor(120, 120, 255, 255); |
| |
| EntityNode pane_node_1(session); |
| ShapeNode pane_bg_1(session); |
| pane_bg_1.SetShape(pane_shape); |
| pane_bg_1.SetMaterial(pane_material); |
| pane_node_1.AddPart(pane_bg_1); |
| pane_node_1.SetTranslation(kPaneMargin + pane_width * 0.5, |
| kPaneMargin + pane_height * 0.5, 20); |
| pane_node_1.SetClip(0, true); |
| root_node.AddChild(pane_node_1); |
| |
| EntityNode pane_node_2(session); |
| ShapeNode pane_bg_2(session); |
| pane_bg_2.SetShape(pane_shape); |
| pane_bg_2.SetMaterial(pane_material); |
| pane_node_2.AddPart(pane_bg_2); |
| pane_node_2.SetTranslation(kPaneMargin * 2 + pane_width * 1.5, |
| kPaneMargin + pane_height * 0.5, 20); |
| pane_node_2.SetClip(0, true); |
| root_node.AddChild(pane_node_2); |
| |
| // Create a Material with the checkerboard image. This will be used for |
| // the objects in each pane. |
| Material checkerboard_material(session); |
| InitCheckerboardMaterial(&checkerboard_material); |
| checkerboard_material.SetColor(255, 100, 100, 255); |
| |
| Material green_material(session); |
| green_material.SetColor(50, 150, 50, 255); |
| |
| // The first pane will contain an animated rounded-rect. |
| rrect_node_ = std::make_unique<ShapeNode>(session); |
| rrect_node_->SetMaterial(checkerboard_material); |
| rrect_node_->SetShape(RoundedRectangle(session, 200, 300, 20, 20, 80, 10)); |
| pane_node_1.AddChild(rrect_node_->id()); |
| |
| // The second pane will contain two large circles that are clipped by a pair |
| // of smaller animated circles. |
| EntityNode pane_2_contents(session); |
| |
| Circle clipper_circle(session, 200); |
| clipper_1_ = std::make_unique<ShapeNode>(session); |
| clipper_2_ = std::make_unique<ShapeNode>(session); |
| clipper_1_->SetShape(clipper_circle); |
| clipper_2_->SetShape(clipper_circle); |
| |
| Circle clippee_circle(session, 400); |
| ShapeNode clippee1(session); |
| clippee1.SetShape(clippee_circle); |
| clippee1.SetMaterial(green_material); |
| clippee1.SetTranslation(0, 400, 0); |
| ShapeNode clippee2(session); |
| clippee2.SetShape(clippee_circle); |
| clippee2.SetMaterial(checkerboard_material); |
| clippee2.SetTranslation(0, -400, 0); |
| |
| pane_2_contents.AddPart(clipper_1_->id()); |
| pane_2_contents.AddPart(clipper_2_->id()); |
| pane_2_contents.AddChild(clippee1); |
| pane_2_contents.AddChild(clippee2); |
| pane_2_contents.SetClip(0, true); |
| |
| pane_node_2.AddChild(pane_2_contents); |
| pane_2_contents.SetTranslation(0, 0, 10); |
| } |
| |
| void App::Init(scenic::DisplayInfoPtr display_info) { |
| FXL_LOG(INFO) << "Creating new Session"; |
| |
| // TODO: set up SessionListener. |
| session_ = std::make_unique<scenic_lib::Session>(scene_manager_.get()); |
| session_->set_error_handler([this] { |
| FXL_LOG(INFO) << "Session terminated."; |
| loop_->QuitNow(); |
| }); |
| |
| // Wait kSessionDuration seconds, and close the session. |
| constexpr int kSessionDuration = 40; |
| loop_->task_runner()->PostDelayedTask( |
| [this] { ReleaseSessionResources(); }, |
| fxl::TimeDelta::FromSeconds(kSessionDuration)); |
| |
| // Set up initial scene. |
| const float display_width = static_cast<float>(display_info->width_in_px); |
| const float display_height = static_cast<float>(display_info->height_in_px); |
| CreateExampleScene(display_width, display_height); |
| |
| start_time_ = zx_clock_get(ZX_CLOCK_MONOTONIC); |
| camera_anim_start_time_ = start_time_; |
| Update(start_time_); |
| } |
| |
| void App::Update(uint64_t next_presentation_time) { |
| // Translate / rotate the rounded rect. |
| { |
| double secs = |
| static_cast<double>(next_presentation_time - start_time_) / kBillion; |
| |
| rrect_node_->SetTranslation(sin(secs * 0.8) * 500.f, |
| sin(secs * 0.6) * 570.f, 10.f); |
| |
| auto quaternion = |
| glm::angleAxis(static_cast<float>(secs / 2.0), glm::vec3(0, 0, 1)); |
| rrect_node_->SetRotation(quaternion.x, quaternion.y, quaternion.z, |
| quaternion.w); |
| } |
| |
| // Translate the clip-circles. |
| { |
| double secs = |
| static_cast<double>(next_presentation_time - start_time_) / kBillion; |
| |
| float offset1 = sin(secs * 0.8) * 300.f; |
| float offset2 = cos(secs * 0.8) * 300.f; |
| |
| clipper_1_->SetTranslation(offset1, offset2 * 3, -5); |
| clipper_2_->SetTranslation(offset2, offset1 * 2, -4); |
| } |
| |
| // Move the camera. |
| { |
| double secs = |
| static_cast<double>(next_presentation_time - camera_anim_start_time_) / |
| kBillion; |
| const double kCameraModeDuration = 5.0; |
| float param = secs / kCameraModeDuration; |
| if (param > 1.0) { |
| param = 0.0; |
| camera_anim_returning_ = !camera_anim_returning_; |
| camera_anim_start_time_ = next_presentation_time; |
| } |
| if (camera_anim_returning_) { |
| param = 1.0 - param; |
| } |
| |
| // Animate the eye position. |
| glm::vec3 eye_start(1080, 720, 6000); |
| glm::vec3 eye_end(0, 10000, 7000); |
| glm::vec3 eye = |
| glm::mix(eye_start, eye_end, glm::smoothstep(0.f, 1.f, param)); |
| |
| // Always look at the middle of the stage. |
| float target[3] = {1080, 720, 0}; |
| float up[3] = {0, 1, 0}; |
| |
| camera_->SetProjection(glm::value_ptr(eye), target, up, glm::radians(15.f)); |
| } |
| |
| // Present |
| session_->Present( |
| next_presentation_time, [this](scenic::PresentationInfoPtr info) { |
| Update(info->presentation_time + info->presentation_interval); |
| }); |
| } |
| |
| void App::ReleaseSessionResources() { |
| FXL_LOG(INFO) << "Closing session."; |
| |
| compositor_.reset(); |
| camera_.reset(); |
| clipper_2_.reset(); |
| clipper_1_.reset(); |
| rrect_node_.reset(); |
| |
| session_.reset(); |
| } |
| |
| } // namespace hello_scene_manager |