| // 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 "src/ui/examples/lab/pose_buffer_presenter/app.h" | |
| // This header is intentionally out of order because it contains a workaround | |
| // for both glm and zircon defining countof(), and must be included before | |
| // the glm headers to work. | |
| #include <fuchsia/ui/gfx/cpp/fidl.h> | |
| #include <lib/async-loop/cpp/loop.h> | |
| #include <lib/async-loop/default.h> | |
| #include <lib/async/cpp/task.h> | |
| #include <lib/fdio/directory.h> | |
| #include <lib/fdio/fd.h> | |
| #include <lib/fdio/fdio.h> | |
| #include <lib/sys/cpp/service_directory.h> | |
| #include <lib/zx/clock.h> | |
| // clang-format off | |
| #include "src/ui/lib/glm_workaround/glm_workaround.h" | |
| // clang-format on | |
| #include <glm/gtc/type_ptr.hpp> | |
| #include <glm/gtx/quaternion.hpp> | |
| #include <glm/gtx/string_cast.hpp> | |
| #include <iostream> | |
| #include "lib/ui/scenic/cpp/commands.h" | |
| #include "src/lib/ui/scenic/cpp/host_memory.h" | |
| #include "src/lib/ui/scenic/cpp/mesh_utils.h" | |
| #include <lib/syslog/cpp/macros.h> | |
| #include "src/ui/lib/escher/geometry/types.h" | |
| #include "src/ui/lib/escher/hmd/pose_buffer.h" | |
| #include "src/ui/lib/escher/util/image_utils.h" | |
| #define DEBUG_BOX 0 | |
| using namespace scenic; | |
| namespace pose_buffer_presenter { | |
| namespace { | |
| constexpr float kSecondsPerNanosecond = .000'000'001f; | |
| static constexpr float kEdgeLength = 0.125f; | |
| static const float kVertexBufferData[] = { | |
| -1.0f, -1.0f, -1.0f, // 0 | |
| -1.0f, -1.0f, 1.0f, // 1 | |
| -1.0f, 1.0f, -1.0f, // 2 | |
| -1.0f, 1.0f, 1.0f, // 3 | |
| 1.0f, -1.0f, -1.0f, // 4 | |
| 1.0f, -1.0f, 1.0f, // 5 | |
| 1.0f, 1.0f, -1.0f, // 6 | |
| 1.0f, 1.0f, 1.0f, // 7 | |
| }; | |
| static const uint32_t kIndexBufferData[] = { | |
| 5, 6, 7, 6, 5, 4, // +X | |
| 0, 1, 2, 3, 2, 1, // -X | |
| 2, 3, 6, 7, 6, 3, // +Y | |
| 1, 4, 5, 4, 1, 0, // -Y | |
| 3, 5, 7, 5, 3, 1, // +Z | |
| 0, 2, 4, 6, 4, 2, // -Z | |
| }; | |
| } // namespace | |
| App::App(async::Loop* loop) | |
| : component_context_(sys::ComponentContext::CreateAndServeOutgoingDirectory()), loop_(loop) { | |
| // Connect to the Scenic service. | |
| 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. Status: " << status; | |
| loop_->Quit(); | |
| }); | |
| scenic_->GetDisplayInfo( | |
| [this](fuchsia::ui::gfx::DisplayInfo display_info) { Init(std::move(display_info)); }); | |
| } | |
| 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<StereoCamera>(scene); | |
| // Produces the Identity View Matrix | |
| static const glm::vec3 eye(0, 0, 0); | |
| static const glm::vec3 look_at(0, 0, -1); | |
| static const glm::vec3 up(0, 1, 0); | |
| camera_->SetTransform({eye.x, eye.y, eye.z}, {look_at.x, look_at.y, look_at.z}, | |
| {up.x, up.y, up.z}); | |
| float fovy = glm::radians(30.f); | |
| float f = 1.0f / tan(0.5f * fovy); | |
| float aspect_ratio = (display_width * 0.5f) / display_height; | |
| float near = 0.1f; | |
| float far = 10; | |
| // Use (display_width * 0.5f) / display_height because the stereo camera uses | |
| // half of the display for each eye, so the aspect ratio for each eye has 1/2 | |
| // the width:height ratio of the display. | |
| // clang-format off | |
| glm::mat4 projection( f / aspect_ratio, 0.0f, 0.0f, 0.0f, | |
| 0.0f, -f, 0.0f, 0.0f, | |
| 0.0f, 0.0f, far / (near - far), -1.0f, | |
| 0.0f, 0.0f, (near * far) / (near - far), 0.0f); | |
| // clang-format on | |
| std::array<float, 16> projection_arr; | |
| const float* projection_ptr = glm::value_ptr(projection); | |
| std::copy(projection_ptr, projection_ptr + 16, std::begin(projection_arr)); | |
| camera_->SetStereoProjection(projection_arr, projection_arr); | |
| compositor_->SetLayerStack(layer_stack); | |
| layer_stack.AddLayer(layer); | |
| layer.SetSize(display_width, display_height); | |
| layer.SetRenderer(renderer); | |
| renderer.SetCamera(camera_->id()); | |
| renderer.SetShadowTechnique(fuchsia::ui::gfx::ShadowTechnique::UNSHADOWED); | |
| // 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()); | |
| cube_node_ = std::make_unique<ShapeNode>(session); | |
| Material cube_material(session); | |
| cube_material.SetColor(0xf5, 0x00, 0x57, 0xff); // Pink A400 | |
| cube_node_->SetMaterial(cube_material); | |
| std::vector<float> vertices(std::begin(kVertexBufferData), std::end(kVertexBufferData)); | |
| std::vector<uint32_t> indices(std::begin(kIndexBufferData), std::end(kIndexBufferData)); | |
| auto cube_shape = scenic_util::NewMeshWithVertices(session, vertices, indices); | |
| cube_node_->SetShape(*cube_shape); | |
| // Raw vertex data has an edge length of 2, so we must scale by half of | |
| // kEdgeLength to end up with a cube whose edge length is kEdgeLength long. | |
| float scale_factor = 0.5 * kEdgeLength; | |
| cube_node_->SetScale(scale_factor, scale_factor, scale_factor); | |
| cube_node_->SetTranslation(0, 4.0 * kEdgeLength, 0); | |
| root_node.AddChild(*cube_node_); | |
| // Adds a colored box arround the camera to help debug orientation problems | |
| #if DEBUG_BOX | |
| float pane_width = 10; | |
| scenic::Rectangle pane_shape(session, pane_width, pane_width); | |
| static const int num_panes = 6; | |
| glm::vec4 colors[num_panes] = { | |
| glm::vec4(255, 0, 0, 255), // RED | |
| glm::vec4(0, 255, 255, 255), // CYAN | |
| glm::vec4(0, 255, 0, 255), // GREEN | |
| glm::vec4(255, 0, 255, 255), // MAGENTA | |
| glm::vec4(0, 0, 255, 255), // BLUE | |
| glm::vec4(255, 255, 0, 255), // YELLOW | |
| }; | |
| static const float pane_offset = pane_width / 2; | |
| glm::vec3 translations[num_panes] = { | |
| glm::vec3(0, 0, pane_offset), // Above Camera | |
| glm::vec3(0, 0, -pane_offset), // Below Camera | |
| glm::vec3(pane_offset, 0, 0), // Right of camera | |
| glm::vec3(-pane_offset, 0, 0), // Left of Camera | |
| glm::vec3(0, pane_offset, 0), // In front of camera. | |
| glm::vec3(0, -pane_offset, 0), // Behind camera. | |
| }; | |
| float pi = glm::pi<float>(); | |
| glm::quat orientations[num_panes] = { | |
| glm::quat(), // identity quaternion | |
| glm::angleAxis(pi, glm::vec3(1, 0, 0)), | |
| glm::angleAxis(pi / 2, glm::vec3(0, 1, 0)), | |
| glm::angleAxis(-pi / 2, glm::vec3(0, 1, 0)), | |
| glm::angleAxis(-pi / 2, glm::vec3(1, 0, 0)), | |
| glm::angleAxis(pi / 2, glm::vec3(1, 0, 0)), | |
| }; | |
| for (int i = 0; i < num_panes; i++) { | |
| glm::vec4 color = colors[i]; | |
| glm::vec3 translation = translations[i]; | |
| glm::quat orientation = orientations[i]; | |
| scenic::Material pane_material(session); | |
| pane_material.SetColor(color.r, color.g, color.b, color.a); | |
| scenic::ShapeNode pane_shape_node(session); | |
| pane_shape_node.SetShape(pane_shape); | |
| pane_shape_node.SetMaterial(pane_material); | |
| pane_shape_node.SetTranslation(translation.x, translation.y, translation.z); | |
| pane_shape_node.SetRotation(orientation.x, orientation.y, orientation.z, orientation.w); | |
| root_node.AddChild(pane_shape_node); | |
| } | |
| #endif | |
| } | |
| void App::StartPoseBufferProvider() { | |
| FX_LOGS(INFO) << "Launching PoseBufferProvider"; | |
| fuchsia::sys::LaunchInfo launch_info; | |
| launch_info.url = | |
| "fuchsia-pkg://fuchsia.com/pose_buffer_provider#meta/" | |
| "pose_buffer_provider.cmx"; | |
| auto services = sys::ServiceDirectory::CreateWithRequest(&launch_info.directory_request); | |
| fuchsia::sys::LauncherSyncPtr launcher; | |
| component_context_->svc()->Connect(launcher.NewRequest()); | |
| launcher->CreateComponent(std::move(launch_info), controller_.NewRequest()); | |
| controller_.set_error_handler([](zx_status_t status) { | |
| FX_LOGS(ERROR) << "Lost connection to controller_. Status: " << status; | |
| }); | |
| services->Connect(provider_.NewRequest()); | |
| provider_.set_error_handler([](zx_status_t status) { | |
| FX_LOGS(ERROR) << "Lost connection to PoseBufferProvider service. Status: " << status; | |
| }); | |
| } | |
| void App::ConfigurePoseBuffer() { | |
| auto session = session_.get(); | |
| uint64_t vmo_size = PAGE_SIZE; | |
| zx::vmo vmo; | |
| zx_status_t status; | |
| status = zx::vmo::create(vmo_size, 0u, &pose_buffer_vmo_); | |
| FX_DCHECK(status == ZX_OK); | |
| status = pose_buffer_vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo); | |
| FX_DCHECK(status == ZX_OK); | |
| zx_time_t base_time = zx::clock::get_monotonic().get(); | |
| // Normally the time interval is the period of time between each entry in the | |
| // pose buffer. In this example we only use one entry so the time interval is | |
| // pretty meaningless. Set to 1 for simplicity (see fxbug.dev/327). | |
| zx_time_t time_interval = 1; | |
| uint32_t num_entries = 1; | |
| Memory mem(session, std::move(vmo), vmo_size, fuchsia::images::MemoryType::VK_DEVICE_MEMORY); | |
| Buffer pose_buffer(mem, 0, vmo_size); | |
| camera_->SetPoseBuffer(pose_buffer, num_entries, base_time, time_interval); | |
| status = pose_buffer_vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo); | |
| FX_DCHECK(status == ZX_OK); | |
| provider_->SetPoseBuffer(std::move(vmo), num_entries, base_time, time_interval); | |
| } | |
| void App::Init(fuchsia::ui::gfx::DisplayInfo display_info) { | |
| StartPoseBufferProvider(); | |
| FX_LOGS(INFO) << "Creating new Session"; | |
| // TODO: set up SessionListener. | |
| session_ = std::make_unique<Session>(scenic_.get()); | |
| session_->set_error_handler([this](zx_status_t status) { | |
| FX_LOGS(INFO) << "Session terminated. Status: " << status; | |
| loop_->Quit(); | |
| }); | |
| // 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); | |
| ConfigurePoseBuffer(); | |
| start_time_ = zx_clock_get_monotonic(); | |
| Update(start_time_); | |
| } | |
| void App::Update(uint64_t next_presentation_time) { | |
| float secs = | |
| static_cast<float>(static_cast<double>(zx_clock_get_monotonic()) * kSecondsPerNanosecond); | |
| glm::quat quaternion = glm::angleAxis(secs / 2.0f, glm::normalize(glm::vec3(0, 1, 0))); | |
| cube_node_->SetRotation(quaternion.x, quaternion.y, quaternion.z, quaternion.w); | |
| // 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(); | |
| session_.reset(); | |
| } | |
| } // namespace pose_buffer_presenter |