| // Copyright 2019 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. |
| |
| /* |
| fx shell tiles_ctl add fuchsia-pkg://fuchsia.com/camera_demo#meta/camera_demo.cmx |
| */ |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/ui/base_view/cpp/base_view.h> |
| #include <lib/ui/base_view/cpp/view_provider_component.h> |
| #include <lib/ui/scenic/cpp/commands.h> |
| #include <lib/ui/scenic/cpp/resources.h> |
| #include <lib/zx/time.h> |
| #include <stream_provider.h> |
| #include <sys/types.h> |
| #include <text_node.h> |
| |
| #include <queue> |
| #include <random> |
| |
| #include <fbl/unique_fd.h> |
| #include <src/lib/fxl/command_line.h> |
| #include <src/lib/fxl/log_settings_command_line.h> |
| #include <src/lib/fxl/logging.h> |
| #include <src/lib/fxl/macros.h> |
| |
| #include "src/lib/component/cpp/startup_context.h" |
| #include "src/ui/lib/glm_workaround/glm_workaround.h" |
| |
| #include <glm/gtc/type_ptr.hpp> |
| #include <glm/gtx/quaternion.hpp> |
| |
| static const uint32_t kChaosMaxSleepMsec = 2000; |
| static const uint32_t kChaosMeanSleepMsec = 50; |
| |
| static fuchsia::images::PixelFormat convertFormat(fuchsia::sysmem::PixelFormatType type) { |
| switch (type) { |
| case fuchsia::sysmem::PixelFormatType::BGRA32: |
| return fuchsia::images::PixelFormat::BGRA_8; |
| case fuchsia::sysmem::PixelFormatType::YUY2: |
| return fuchsia::images::PixelFormat::YUY2; |
| case fuchsia::sysmem::PixelFormatType::NV12: |
| return fuchsia::images::PixelFormat::NV12; |
| case fuchsia::sysmem::PixelFormatType::YV12: |
| return fuchsia::images::PixelFormat::YV12; |
| default: |
| FXL_LOG(ERROR) << "sysmem pixel format (" << static_cast<uint32_t>(type) |
| << ") has no corresponding fuchsia::images::PixelFormat"; |
| return static_cast<fuchsia::images::PixelFormat>(-1); |
| } |
| } |
| |
| // Draws a scenic scene containing a single rectangle with an image pipe material, |
| // constructed with buffers populated by a stream provider. |
| class DemoView : public scenic::BaseView, public fuchsia::camera2::Stream::EventSender_ { |
| public: |
| explicit DemoView(scenic::ViewContext context, async::Loop* loop, bool chaos) |
| : BaseView(std::move(context), "Camera Demo"), |
| loop_(loop), |
| chaos_(chaos), |
| chaos_dist_(kChaosMaxSleepMsec, |
| static_cast<float>(kChaosMeanSleepMsec) / kChaosMaxSleepMsec), |
| node_(session()), |
| text_node_(session()) {} |
| |
| ~DemoView() override { |
| // Manually delete Wait instances before their corresponding events to avoid a failed assert. |
| // TODO(36367): revisit async::Wait object lifetime requirements |
| while (waiters_.size() > 0) { |
| waiters_.front().first = nullptr; |
| waiters_.pop(); |
| } |
| }; |
| |
| static std::unique_ptr<DemoView> Create(scenic::ViewContext context, async::Loop* loop, |
| bool chaos) { |
| auto view = std::make_unique<DemoView>(std::move(context), loop, chaos); |
| |
| view->stream_provider_ = StreamProvider::Create(StreamProvider::Source::CONTROLLER); |
| if (!view->stream_provider_) { |
| FXL_LOG(ERROR) << "Failed to get CONTROLLER stream provider"; |
| return nullptr; |
| } |
| |
| fuchsia::sysmem::ImageFormat_2 format; |
| fuchsia::sysmem::BufferCollectionInfo_2 buffers; |
| view->stream_ = view->stream_provider_->ConnectToStream(view.get(), &format, &buffers, |
| &view->should_rotate_); |
| if (!view->stream_) { |
| FXL_LOG(ERROR) << "Failed to connect to stream"; |
| return nullptr; |
| } |
| |
| uint32_t image_pipe_id = view->session()->AllocResourceId(); |
| view->session()->Enqueue( |
| scenic::NewCreateImagePipeCmd(image_pipe_id, view->image_pipe_.NewRequest())); |
| scenic::Material material(view->session()); |
| material.SetTexture(image_pipe_id); |
| view->session()->ReleaseResource(image_pipe_id); |
| scenic::Rectangle shape(view->session(), format.coded_width, format.coded_height); |
| view->shape_width_ = format.coded_width; |
| view->shape_height_ = format.coded_height; |
| view->node_.SetShape(shape); |
| view->node_.SetMaterial(material); |
| view->root_node().AddChild(view->node_); |
| view->root_node().AddChild(view->text_node_); |
| |
| fuchsia::images::ImageInfo image_info{}; |
| image_info.width = format.coded_width; |
| image_info.height = format.coded_height; |
| image_info.stride = format.bytes_per_row; |
| image_info.pixel_format = convertFormat(format.pixel_format.type); |
| for (uint32_t i = 0; i < buffers.buffer_count; ++i) { |
| uint32_t image_id = i + 1; // Scenic doesn't allow clients to use image ID 0. |
| view->image_pipe_->AddImage(image_id, image_info, std::move(buffers.buffers[i].vmo), 0, |
| buffers.settings.buffer_settings.size_bytes, |
| fuchsia::images::MemoryType::HOST_MEMORY); |
| view->image_ids_[i] = image_id; |
| } |
| |
| view->stream_->Start(); |
| |
| view->InvalidateScene(); |
| |
| return view; |
| } |
| |
| private: |
| // |scenic::BaseView| |
| void OnSceneInvalidated(fuchsia::images::PresentationInfo presentation_info) override { |
| if (!has_logical_size() || !has_metrics()) |
| return; |
| if (should_rotate_) { |
| auto rotation = glm::angleAxis(glm::half_pi<float>(), glm::vec3(0, 0, 1)); |
| node_.SetRotation(rotation.x, rotation.y, rotation.z, rotation.w); |
| } |
| node_.SetTranslation(logical_size().x * 0.5f, logical_size().y * 0.5f, -1.0f); |
| const float shape_vertical_size = should_rotate_ ? shape_width_ : shape_height_; |
| const float scale = logical_size().y / shape_vertical_size; // Fit vertically. |
| node_.SetScale(scale, scale, 1.0f); |
| text_node_.SetText(stream_provider_->GetName() + |
| (should_rotate_ ? " (Rotated by Scenic)" : "")); |
| text_node_.SetTranslation(logical_size().x * 0.5f + metrics().scale_x * 0.5f, |
| logical_size().y * 0.02f, -1.1f); |
| text_node_.SetScale(1.0f / metrics().scale_x, 1.0f / metrics().scale_y, |
| 1.0f / metrics().scale_z); |
| } |
| |
| void OnInputEvent(fuchsia::ui::input::InputEvent event) override { |
| if (event.is_pointer() && event.pointer().phase == fuchsia::ui::input::PointerEventPhase::UP) { |
| loop_->Quit(); |
| } |
| } |
| |
| // |scenic::SessionListener| |
| void OnScenicError(std::string error) override { FXL_LOG(ERROR) << "Scenic Error " << error; } |
| |
| // |fuchsia::camera2::Stream_EventSender| |
| void OnFrameAvailable(fuchsia::camera2::FrameAvailableInfo info) override { |
| SleepIfChaos(); |
| if (!has_logical_size()) { |
| stream_->ReleaseFrame(info.buffer_id); |
| return; |
| } |
| if (info.frame_status != fuchsia::camera2::FrameStatus::OK) { |
| FXL_LOG(ERROR) << "Received OnFrameAvailable with error event"; |
| return; |
| } |
| |
| zx::event release_fence; |
| zx_status_t status = zx::event::create(0, &release_fence); |
| if (status != ZX_OK) { |
| FXL_PLOG(ERROR, status) << "Failed to create fence"; |
| loop_->Quit(); |
| return; |
| } |
| std::vector<zx::event> acquire_fences; |
| std::vector<zx::event> release_fences(1); |
| status = release_fence.duplicate(ZX_RIGHT_SAME_RIGHTS, &release_fences[0]); |
| if (status != ZX_OK) { |
| FXL_PLOG(ERROR, status) << "Failed to duplicate fence"; |
| loop_->Quit(); |
| return; |
| } |
| |
| uint64_t now_ns = zx_clock_get_monotonic(); |
| image_pipe_->PresentImage( |
| image_ids_[info.buffer_id], now_ns, std::move(acquire_fences), std::move(release_fences), |
| [this](fuchsia::images::PresentationInfo presentation_info) { SleepIfChaos(); }); |
| |
| auto waiter = std::make_unique<async::Wait>( |
| release_fence.get(), ZX_EVENT_SIGNALED, 0, |
| [this, buffer_id = info.buffer_id](async_dispatcher_t* dispatcher, async::Wait* wait, |
| zx_status_t status, const zx_packet_signal_t* signal) { |
| if (status != ZX_OK) { |
| FXL_PLOG(ERROR, status) << "Wait failed"; |
| loop_->Quit(); |
| return; |
| } |
| SleepIfChaos(); |
| stream_->ReleaseFrame(buffer_id); |
| }); |
| waiters_.emplace(std::move(waiter), std::move(release_fence)); |
| status = waiters_.back().first->Begin(loop_->dispatcher()); |
| if (status != ZX_OK) { |
| FXL_PLOG(ERROR, status) << "Failed to start waiter"; |
| loop_->Quit(); |
| return; |
| } |
| |
| // Remove any signaled waiters. |
| zx_signals_t signals{}; |
| while (waiters_.size() > 0 && zx_object_wait_one(waiters_.front().second.get(), |
| ZX_EVENT_SIGNALED, 0, &signals) == ZX_OK) { |
| waiters_.pop(); |
| } |
| |
| SleepIfChaos(); |
| } |
| |
| void SleepIfChaos() { |
| if (!chaos_) { |
| return; |
| } |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(chaos_dist_(chaos_gen_)))); |
| } |
| |
| async::Loop* loop_; |
| bool chaos_; |
| std::mt19937 chaos_gen_; |
| std::binomial_distribution<uint32_t> chaos_dist_; |
| std::unique_ptr<fuchsia::camera2::Stream> stream_; |
| scenic::ShapeNode node_; |
| TextNode text_node_; |
| fuchsia::images::ImagePipePtr image_pipe_; |
| std::map<uint32_t, uint32_t> image_ids_; |
| float shape_width_; |
| float shape_height_; |
| bool should_rotate_; |
| std::queue<std::pair<std::unique_ptr<async::Wait>, zx::event>> waiters_; |
| std::unique_ptr<StreamProvider> stream_provider_; |
| }; |
| |
| int main(int argc, const char** argv) { |
| async::Loop loop(&kAsyncLoopConfigAttachToThread); |
| // Chaos mode adds random delays between frame acquisition, presentation, and release. |
| bool chaos = false; |
| if (argc > 1 && std::string(argv[1]) == "--chaos") { |
| std::cout << "Chaos mode enabled!" << std::endl; |
| chaos = true; |
| } |
| scenic::ViewProviderComponent component( |
| [&loop, chaos](scenic::ViewContext context) { |
| return DemoView::Create(std::move(context), &loop, chaos); |
| }, |
| &loop); |
| FXL_LOG(INFO) << argv[0] << " initialized successfully - entering loop"; |
| zx_status_t status = loop.Run(); |
| if (status != ZX_ERR_CANCELED) { |
| FXL_LOG(WARNING) << "Main thread terminated abnormally"; |
| return status == ZX_OK ? -1 : status; |
| } |
| return 0; |
| } |