blob: f900d58e97984bb972c7218860cf56748912f943 [file] [log] [blame]
// 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