| // 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. |
| |
| #include "demo_view.h" |
| |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/ui/scenic/cpp/commands.h> |
| #include <lib/ui/scenic/cpp/resources.h> |
| #include <lib/zx/time.h> |
| #include <sys/types.h> |
| #include <zircon/errors.h> |
| |
| #include <queue> |
| #include <random> |
| |
| #include <fbl/unique_fd.h> |
| |
| #include "src/camera/lib/stream_utils/image_io_util.h" |
| #include "src/lib/syslog/cpp/logger.h" |
| #include "src/ui/lib/glm_workaround/glm_workaround.h" |
| |
| #include <glm/gtc/type_ptr.hpp> |
| #include <glm/gtx/quaternion.hpp> |
| |
| namespace camera { |
| |
| static constexpr uint32_t kChaosMaxSleepMsec = 2000; |
| static constexpr 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: |
| FX_LOGS(ERROR) << "sysmem pixel format (" << static_cast<uint32_t>(type) |
| << ") has no corresponding fuchsia::images::PixelFormat"; |
| return static_cast<fuchsia::images::PixelFormat>(-1); |
| } |
| } |
| |
| DemoView::DemoView(scenic::ViewContext context, async::Loop* loop, bool chaos, bool image_io) |
| : BaseView(std::move(context), "Camera Demo"), |
| loop_(loop), |
| chaos_(chaos), |
| chaos_dist_(kChaosMaxSleepMsec, static_cast<float>(kChaosMeanSleepMsec) / kChaosMaxSleepMsec), |
| text_node_(session()), |
| image_io_(image_io), |
| trace_provider_(loop->dispatcher()) {} |
| |
| DemoView::~DemoView() { |
| // Manually delete Wait instances before their corresponding events to avoid a failed assert. |
| // TODO(36367): revisit async::Wait object lifetime requirements |
| for (auto& ipp : image_pipe_properties_) { |
| for (auto& waiter : ipp.waiters) { |
| waiter.second.first = nullptr; |
| } |
| } |
| }; |
| |
| std::unique_ptr<DemoView> DemoView::Create(scenic::ViewContext context, async::Loop* loop, |
| bool chaos, bool image_io) { |
| auto view = std::make_unique<DemoView>(std::move(context), loop, chaos, image_io); |
| |
| view->stream_provider_ = StreamProvider::Create(StreamProvider::Source::MANAGER); |
| if (!view->stream_provider_) { |
| FX_LOGS(ERROR) << "Failed to get MANAGER stream provider"; |
| return nullptr; |
| } |
| |
| view->total_width_ = 0.0f; |
| for (uint32_t index = 0;; ++index) { |
| fuchsia::camera2::StreamPtr stream; |
| auto result = |
| view->stream_provider_->ConnectToStream(stream.NewRequest(loop->dispatcher()), index); |
| if (result.is_error()) { |
| if (result.error() == ZX_ERR_OUT_OF_RANGE) { |
| break; |
| } |
| FX_PLOGS(ERROR, result.error()) << "Failed to connect to stream"; |
| return nullptr; |
| } |
| auto& ipp = view->image_pipe_properties_.emplace_back(view->session()); |
| ipp.stream = std::move(stream); |
| auto [format, buffers, should_rotate] = result.take_value(); |
| ipp.should_rotate = should_rotate; |
| ipp.stream.set_error_handler([loop](zx_status_t status) { |
| FX_PLOGS(ERROR, status) << "Stream disconnected"; |
| loop->Quit(); |
| }); |
| ipp.stream.events().OnFrameAvailable = |
| [index, view = view.get()](fuchsia::camera2::FrameAvailableInfo info) { |
| view->OnFrameAvailable(index, std::move(info)); |
| }; |
| |
| std::stringstream ss; |
| ss << "/demo/" << index; |
| ipp.image_io_util = camera::ImageIOUtil::Create(&buffers, ss.str()); |
| if (ipp.image_io_util) { |
| FX_LOGS(ERROR) << "Failed to create ImageIOUtil"; |
| } else { |
| FX_LOGS(INFO) << "ImageIOUtil Created!"; |
| } |
| |
| uint32_t image_pipe_id = view->session()->AllocResourceId(); |
| view->session()->Enqueue(scenic::NewCreateImagePipeCmd( |
| image_pipe_id, ipp.image_pipe.NewRequest(loop->dispatcher()))); |
| 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); |
| ipp.shape_width = should_rotate ? format.coded_height : format.coded_width; |
| ipp.shape_height = should_rotate ? format.coded_width : format.coded_height; |
| view->total_width_ += ipp.shape_width; |
| view->max_height_ = std::max(view->max_height_, ipp.shape_height); |
| ipp.node.SetShape(shape); |
| ipp.node.SetMaterial(material); |
| view->root_node().AddChild(ipp.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. |
| ipp.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); |
| ipp.image_ids[i] = image_id; |
| } |
| |
| ipp.stream->Start(); |
| } |
| |
| view->root_node().AddChild(view->text_node_); |
| view->InvalidateScene(); |
| |
| return view; |
| } |
| |
| void DemoView::OnSceneInvalidated(fuchsia::images::PresentationInfo presentation_info) { |
| if (!has_logical_size() || !has_metrics()) { |
| return; |
| } |
| |
| uint32_t rotated_count = 0; |
| |
| // Determine a uniform scale factor that will allow all nodes to be visible. |
| constexpr float kPadding = 8.0f; |
| const float content_width = total_width_ + kPadding * (image_pipe_properties_.size() - 1); |
| const float content_height = max_height_; |
| float scale_x = logical_size().x / content_width; |
| float scale_y = logical_size().y / content_height; |
| float scale = std::min(scale_x, scale_y); |
| |
| float offset_x = logical_size().x * 0.5f - content_width * scale * 0.5f; |
| for (auto& ipp : image_pipe_properties_) { |
| if (ipp.should_rotate) { |
| ++rotated_count; |
| auto rotation = glm::angleAxis(glm::half_pi<float>(), glm::vec3(0, 0, 1)); |
| ipp.node.SetRotation(rotation.x, rotation.y, rotation.z, rotation.w); |
| } |
| ipp.node.SetScale(scale, scale, 1.0f); |
| ipp.node.SetTranslation(offset_x + ipp.shape_width * scale * 0.5f, logical_size().y * 0.5f, |
| -1.0f); |
| offset_x += (ipp.shape_width + kPadding) * scale; |
| } |
| text_node_.SetTranslation(logical_size().x * 0.5f + metrics().scale_x * 0.5f, |
| logical_size().y * 0.02f, -1.1f); |
| |
| std::stringstream ss; |
| if (rotated_count > 0) { |
| ss << " (" << rotated_count << " of " << image_pipe_properties_.size() |
| << " nodes rotated by Scenic)"; |
| } |
| text_node_.SetText(stream_provider_->GetName() + ss.str()); |
| text_node_.SetScale(1.0f / metrics().scale_x, 1.0f / metrics().scale_y, 1.0f / metrics().scale_z); |
| } |
| |
| void DemoView::OnInputEvent(fuchsia::ui::input::InputEvent event) { |
| if (event.is_pointer() && event.pointer().phase == fuchsia::ui::input::PointerEventPhase::UP) { |
| loop_->Quit(); |
| } |
| } |
| |
| void DemoView::OnScenicError(std::string error) { FX_LOGS(ERROR) << "Scenic Error " << error; } |
| |
| void DemoView::OnFrameAvailable(uint32_t index, fuchsia::camera2::FrameAvailableInfo info) { |
| auto async_id = TRACE_NONCE(); |
| TRACE_ASYNC_BEGIN("camera", "FrameHeld", async_id, "buffer_id", info.buffer_id); |
| SleepIfChaos(); |
| auto& ipp = image_pipe_properties_[index]; |
| if (!has_logical_size()) { |
| ipp.stream->ReleaseFrame(info.buffer_id); |
| TRACE_ASYNC_END("camera", "FrameHeld", async_id, "buffer_id", info.buffer_id); |
| return; |
| } |
| if (info.frame_status != fuchsia::camera2::FrameStatus::OK) { |
| FX_LOGS(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) { |
| FX_PLOGS(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) { |
| FX_PLOGS(ERROR, status) << "Failed to duplicate fence"; |
| loop_->Quit(); |
| return; |
| } |
| |
| // If flag is enabled, write frame to disk once. |
| if (ipp.image_io_util && image_io_) { |
| status = ipp.image_io_util->WriteImageData(info.buffer_id); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to write to disk"; |
| } |
| FX_LOGS(INFO) << "Image written to disk"; |
| image_io_ = false; |
| } |
| |
| uint64_t now_ns = zx_clock_get_monotonic(); |
| ipp.image_pipe->PresentImage( |
| ipp.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, async_id, buffer_id = info.buffer_id, &ipp](async_dispatcher_t* dispatcher, |
| async::Wait* wait, zx_status_t status, |
| const zx_packet_signal_t* signal) { |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Wait failed"; |
| loop_->Quit(); |
| return; |
| } |
| SleepIfChaos(); |
| ipp.stream->ReleaseFrame(buffer_id); |
| TRACE_ASYNC_END("camera", "FrameHeld", async_id, "buffer_id", buffer_id); |
| auto it = ipp.waiters.find(buffer_id); |
| ZX_ASSERT(it != ipp.waiters.end()); |
| it->second.first = nullptr; |
| ipp.waiters.erase(it); |
| }); |
| auto it = ipp.waiters.emplace( |
| std::make_pair(info.buffer_id, std::make_pair(std::move(waiter), std::move(release_fence)))); |
| status = it.first->second.first->Begin(loop_->dispatcher()); |
| if (status != ZX_OK) { |
| FX_PLOGS(ERROR, status) << "Failed to start waiter"; |
| loop_->Quit(); |
| return; |
| } |
| |
| SleepIfChaos(); |
| } |
| |
| void DemoView::SleepIfChaos() { |
| if (!chaos_) { |
| return; |
| } |
| zx_nanosleep(zx_deadline_after(ZX_MSEC(chaos_dist_(chaos_gen_)))); |
| } |
| |
| } // namespace camera |