| // 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_scenic/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 <fuchsia/ui/gfx/cpp/fidl.h> |
| #include <lib/async/cpp/task.h> |
| #include <zx/time.h> |
| |
| #include "lib/component/cpp/connect.h" |
| #include "lib/escher/util/image_utils.h" |
| |
| #include "lib/fxl/functional/make_copyable.h" |
| #include "lib/fxl/logging.h" |
| |
| #include "lib/ui/scenic/cpp/commands.h" |
| #include "lib/ui/scenic/cpp/host_memory.h" |
| |
| using namespace scenic; |
| |
| namespace hello_scenic { |
| |
| static constexpr uint64_t kBillion = 1000000000; |
| |
| App::App(async::Loop* loop) |
| : startup_context_(component::StartupContext::CreateFromStartupInfo()), |
| loop_(loop) { |
| // Connect to the SceneManager service. |
| scenic_ = startup_context_ |
| ->ConnectToEnvironmentService<fuchsia::ui::scenic::Scenic>(); |
| scenic_.set_error_handler([this] { |
| FXL_LOG(INFO) << "Lost connection to Scenic service."; |
| loop_->Quit(); |
| }); |
| scenic_->GetDisplayInfo([this](fuchsia::ui::gfx::DisplayInfo 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. |
| fuchsia::images::ImageInfo checkerboard_image_info; |
| 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 = fuchsia::images::PixelFormat::BGRA_8; |
| checkerboard_image_info.color_space = fuchsia::images::ColorSpace::SRGB; |
| checkerboard_image_info.tiling = fuchsia::images::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(fuchsia::ui::gfx::DisplayInfo display_info) { |
| FXL_LOG(INFO) << "Creating new Session"; |
| |
| // TODO: set up SessionListener. |
| session_ = std::make_unique<scenic::Session>(scenic_.get()); |
| session_->set_error_handler([this] { |
| FXL_LOG(INFO) << "Session terminated."; |
| loop_->Quit(); |
| }); |
| |
| // Wait kSessionDuration seconds, and close the session. |
| constexpr int kSessionDuration = 40; |
| async::PostDelayedTask(loop_->dispatcher(), |
| [this] { ReleaseSessionResources(); }, |
| zx::sec(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_->SetTransform(glm::value_ptr(eye), target, up); |
| camera_->SetProjection(glm::radians(15.f)); |
| } |
| |
| // Present |
| session_->Present( |
| next_presentation_time, [this](fuchsia::images::PresentationInfo 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_scenic |