|  | // 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 "src/ui/examples/lab/scenic_dev_app/app.h" | 
|  |  | 
|  | // clang-format off | 
|  | #include "src/ui/lib/glm_workaround/glm_workaround.h" | 
|  | // clang-format on | 
|  |  | 
|  | #include <fuchsia/ui/gfx/cpp/fidl.h> | 
|  | #include <glm/gtc/type_ptr.hpp> | 
|  | #include <glm/gtx/quaternion.hpp> | 
|  | #include <lib/async/cpp/task.h> | 
|  |  | 
|  | #include <lib/ui/scenic/cpp/commands.h> | 
|  | #include <src/lib/ui/scenic/cpp/host_memory.h> | 
|  | #include <lib/zx/time.h> | 
|  | #include <lib/syslog/cpp/macros.h> | 
|  | #include <string> | 
|  |  | 
|  | #include "src/ui/lib/escher/util/image_utils.h" | 
|  |  | 
|  | using namespace scenic; | 
|  |  | 
|  | namespace scenic_dev_app { | 
|  |  | 
|  | static constexpr uint64_t kBillion = 1000000000; | 
|  |  | 
|  | fuchsia::ui::gfx::ShadowTechnique GetShadowTechniqueFromCommandLine( | 
|  | const fxl::CommandLine& command_line) { | 
|  | using fuchsia::ui::gfx::ShadowTechnique; | 
|  |  | 
|  | std::string shadow_type; | 
|  | if (command_line.GetOptionValue("shadow_type", &shadow_type)) { | 
|  | if (shadow_type == "UNSHADOWED") { | 
|  | return ShadowTechnique::UNSHADOWED; | 
|  | } else if (shadow_type == "SCREEN_SPACE") { | 
|  | return ShadowTechnique::SCREEN_SPACE; | 
|  | } else if (shadow_type == "SHADOW_MAP") { | 
|  | return ShadowTechnique::SHADOW_MAP; | 
|  | } else if (shadow_type == "MOMENT_SHADOW_MAP") { | 
|  | return ShadowTechnique::MOMENT_SHADOW_MAP; | 
|  | } else if (shadow_type == "STENCIL_SHADOW_VOLUME") { | 
|  | return ShadowTechnique::STENCIL_SHADOW_VOLUME; | 
|  | } else { | 
|  | FX_LOGS(INFO) << "Unknown shadow type: " << shadow_type | 
|  | << ".  Valid choices are: UNSHADOWED, SCREEN_SPACE, " | 
|  | "SHADOW_MAP, MOMENT_SHADOW_MAP, STENCIL_SHADOW_VOLUME."; | 
|  | } | 
|  | } | 
|  | return ShadowTechnique::UNSHADOWED; | 
|  | } | 
|  |  | 
|  | App::App(async::Loop* loop, const fxl::CommandLine& command_line) | 
|  | : component_context_(sys::ComponentContext::CreateAndServeOutgoingDirectory()), | 
|  | loop_(loop), | 
|  | shadow_technique_(GetShadowTechniqueFromCommandLine(command_line)) { | 
|  | scenic_ = component_context_->svc()->Connect<fuchsia::ui::scenic::Scenic>(); | 
|  | scenic_.set_error_handler([this](zx_status_t status) { | 
|  | FX_LOGS(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); | 
|  |  | 
|  | scenic_util::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; | 
|  |  | 
|  | scenic_util::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()); | 
|  | fuchsia::ui::gfx::RendererParam param; | 
|  | param.set_shadow_technique(shadow_technique_); | 
|  | renderer.SetParam(std::move(param)); | 
|  |  | 
|  | if (shadow_technique_ == fuchsia::ui::gfx::ShadowTechnique::STENCIL_SHADOW_VOLUME) { | 
|  | AmbientLight ambient_light(session); | 
|  | PointLight point_light1(session); | 
|  | PointLight point_light2(session); | 
|  |  | 
|  | scene.AddLight(ambient_light); | 
|  | scene.AddLight(point_light1); | 
|  | scene.AddLight(point_light2); | 
|  |  | 
|  | // Specify colors for the three lights.  The first two values are tweakable, | 
|  | // and are used to generate the colors of the two point lights.  The color | 
|  | // of the three lights sum to (1.0, 1.0, 1.0).  kPointLightColorDiff causes | 
|  | // the two point lights to differ in color from each other. | 
|  | const glm::vec3 kAmbientLightColor(0.4f, 0.4f, 0.4f); | 
|  | const glm::vec3 kPointLightColorDiff(0.05f, -0.1f, 0.f); | 
|  | const glm::vec3 kPointLightAverageColor = 0.5f * (glm::vec3(1, 1, 1) - kAmbientLightColor); | 
|  | const glm::vec3 kPointLight1Color = kPointLightAverageColor + kPointLightColorDiff; | 
|  | const glm::vec3 kPointLight2Color = kPointLightAverageColor - kPointLightColorDiff; | 
|  |  | 
|  | ambient_light.SetColor({kAmbientLightColor[0], kAmbientLightColor[1], kAmbientLightColor[2]}); | 
|  | point_light1.SetColor({kPointLight1Color[0], kPointLight1Color[1], kPointLight1Color[2]}); | 
|  | point_light2.SetColor({kPointLight2Color[0], kPointLight2Color[1], kPointLight2Color[2]}); | 
|  | point_light1.SetPosition(0.3f * display_width, 0.3f * display_height, -1000.f); | 
|  | point_light2.SetPosition(display_width, 0.2f * display_height, -1000.f); | 
|  | point_light1.SetFalloff(0.f); | 
|  | point_light2.SetFalloff(0.f); | 
|  | } else { | 
|  | 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.AddChild(pane_bg_1); | 
|  | pane_node_1.SetTranslation(kPaneMargin + pane_width * 0.5, kPaneMargin + pane_height * 0.5, -20); | 
|  | 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.AddChild(pane_bg_2); | 
|  | pane_node_2.SetTranslation(kPaneMargin * 2 + pane_width * 1.5, kPaneMargin + pane_height * 0.5, | 
|  | -20); | 
|  | 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. | 
|  | pane_2_contents_ = std::make_unique<EntityNode>(session); | 
|  |  | 
|  | Circle clippee_circle(session, 400); | 
|  | ShapeNode clippee1(session); | 
|  | clippee1.SetShape(clippee_circle); | 
|  | clippee1.SetMaterial(green_material); | 
|  | clippee1.SetTranslation(0, 300, 0); | 
|  | ShapeNode clippee2(session); | 
|  | clippee2.SetShape(clippee_circle); | 
|  | clippee2.SetMaterial(checkerboard_material); | 
|  | clippee2.SetTranslation(0, -300, -100); | 
|  |  | 
|  | pane_2_contents_->AddChild(clippee1); | 
|  | pane_2_contents_->AddChild(clippee2); | 
|  |  | 
|  | pane_node_2.AddChild(*pane_2_contents_.get()); | 
|  | pane_2_contents_->SetTranslation(0, 0, -100); | 
|  | } | 
|  |  | 
|  | void App::Init(fuchsia::ui::gfx::DisplayInfo display_info) { | 
|  | FX_LOGS(INFO) << "Creating new Session"; | 
|  |  | 
|  | // TODO: set up SessionListener. | 
|  | session_ = std::make_unique<Session>(scenic_.get()); | 
|  | session_->SetDebugName("Scenic Standalone"); | 
|  | session_->set_error_handler([this](zx_status_t status) { | 
|  | FX_LOGS(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_monotonic(); | 
|  | camera_anim_start_time_ = start_time_; | 
|  | Update(start_time_); | 
|  | } | 
|  |  | 
|  | void App::Update(uint64_t next_presentation_time) { | 
|  | { | 
|  | double secs = static_cast<double>(next_presentation_time - start_time_) / kBillion; | 
|  |  | 
|  | // Translate / rotate the rounded rect. | 
|  | rrect_node_->SetTranslation(sin(secs * 0.8) * 500.f, sin(secs * 0.6) * 570.f, -200.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); | 
|  |  | 
|  | // Set a moving clip plane, clipping only the two circles. | 
|  | fuchsia::ui::gfx::Plane3 clip_plane; | 
|  | clip_plane.dir.x = sin(secs * 0.5); | 
|  | clip_plane.dir.y = cos(secs * 0.5); | 
|  | clip_plane.dir.z = 0.f; | 
|  | clip_plane.dist = -200.f; | 
|  | pane_2_contents_->SetClipPlanes({clip_plane}); | 
|  | } | 
|  |  | 
|  | // 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. | 
|  | std::array<float, 3> target = {1080, 720, 0}; | 
|  | std::array<float, 3> up = {0, -1, 0}; | 
|  |  | 
|  | camera_->SetTransform({eye.x, eye.y, eye.z}, 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() { | 
|  | FX_LOGS(INFO) << "Closing session."; | 
|  |  | 
|  | compositor_.reset(); | 
|  | camera_.reset(); | 
|  | clipper_2_.reset(); | 
|  | clipper_1_.reset(); | 
|  | rrect_node_.reset(); | 
|  | pane_2_contents_.reset(); | 
|  |  | 
|  | session_.reset(); | 
|  | } | 
|  |  | 
|  | }  // namespace scenic_dev_app |