blob: 137bd9c122dc2324bc34dc4f851a8b98aa6a9952 [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.
/*
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;
}