blob: 88bbc9812a18952beafcd77ea24713ec9c76a779 [file] [log] [blame]
// 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 "lib/media/test/frame_sink_view.h"
#include <lib/async/cpp/wait.h>
#include <lib/media/codec_impl/fourcc.h>
#include <lib/media/test/frame_sink.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <iomanip>
#include <memory>
namespace {
constexpr uint32_t kShapeWidth = 640;
constexpr uint32_t kShapeHeight = 480;
constexpr float kDisplayHeight = 50;
constexpr float kInitialWindowXPos = 320;
constexpr float kInitialWindowYPos = 240;
// Context for a frame's async lifetime. When the presentation wait is done,
// the WaitHandler deletes this.
//
// As with async::WaitMethod, this relies on all this code running on the
// dispatcher's single thread.
class Frame {
public:
Frame(async_dispatcher_t* dispatcher, ::zx::eventpair& release_eventpair, fit::closure on_done)
: wait_(this), on_done_(std::move(on_done)) {
zx_status_t status = release_eventpair.duplicate(ZX_RIGHT_SAME_RIGHTS, &release_eventpair_);
if (status != ZX_OK) {
FX_PLOGS(FATAL, status) << "::zx::event::duplicate() failed";
FX_NOTREACHED();
}
wait_.set_object(release_eventpair_.get());
// TODO(dustingreen): We should make it so an eventpair A B can have A wait
// for B to be signalled without B's holder caring that it's an eventpair
// instead of an event.
//
// TODO(dustingreen): Have ImagePipe accept eventpair as well as event, or
// instead of event. Maybe have it signal the peer instead of signalling
// its end, or maybe signal both ends.
//
// For now we use the other end getting closed instead of the other end
// being signalled, so we can more easily move on if Scenic dies.
wait_.set_trigger(ZX_EVENTPAIR_PEER_CLOSED);
status = wait_.Begin(dispatcher);
if (status != ZX_OK) {
FX_PLOGS(FATAL, status) << "Begin() failed";
FX_NOTREACHED();
}
}
~Frame() {
// on_done_ was already run prior to ~Frame, and set to nullptr.
FX_DCHECK(!on_done_);
}
// Normally this runs when the remote end of the eventpair is closed. In
// addition, when the dispatcher shuts down, if this Frame is still waiting,
// this callback runs and is passed status ZX_ERR_CANCELED.
void WaitHandler(async_dispatcher_t* dispatcher, async::WaitBase* wait, zx_status_t status,
const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
if (status == ZX_ERR_CANCELED) {
// Probably normal if this is not happening until dispatcher shutdown.
//
// TODO(dustingreen): Mute this output if |dispatcher| is shutting down.
FX_LOGS(INFO) << "WaitHandler() sees ZX_ERR_CANCELED (normal if shutting down)";
} else {
FX_PLOGS(INFO, status) << "WaitHandler() sees failure";
}
}
// Regardless of status or signal, this frame is done. The callee here
// knows nothing about |Packet|, so cannot delete |this|.
on_done_();
on_done_ = nullptr;
// If the handler is called it means the handler needs to delete the frame.
// This applies even if the status is ZX_ERR_CANCELED because that'll only
// happen if the dispatcher is shutting down, not if a canceller manually
// cancels (which we don't do currently anyway).
delete this;
}
::zx::eventpair release_eventpair_;
async::WaitMethod<Frame, &Frame::WaitHandler> wait_;
fit::closure on_done_;
};
} // namespace
std::unique_ptr<FrameSinkView> FrameSinkView::Create(scenic::ViewContext context, FrameSink* parent,
async::Loop* main_loop) {
return std::unique_ptr<FrameSinkView>(new FrameSinkView(std::move(context), parent, main_loop));
}
FrameSinkView::~FrameSinkView() { parent_->RemoveFrameSinkView(this); }
void FrameSinkView::PutFrame(uint32_t image_id, zx_time_t present_time, const zx::vmo& vmo,
uint64_t vmo_offset,
const fuchsia::media::VideoUncompressedFormat& video_format,
fit::closure on_done) {
FX_DCHECK((image_id != FrameSink::kBlankFrameImageId) || !on_done);
fuchsia::images::PixelFormat pixel_format = fuchsia::images::PixelFormat::BGRA_8;
uint32_t fourcc = video_format.fourcc;
switch (fourcc) {
case make_fourcc('N', 'V', '1', '2'):
pixel_format = fuchsia::images::PixelFormat::NV12;
break;
case make_fourcc('B', 'G', 'R', 'A'):
pixel_format = fuchsia::images::PixelFormat::BGRA_8;
break;
case make_fourcc('Y', 'V', '1', '2'):
pixel_format = fuchsia::images::PixelFormat::YV12;
break;
default:
FX_CHECK(false) << "fourcc conversion not implemented - fourcc: " << fourcc_to_string(fourcc)
<< " in hex: 0x" << std::hex << std::setw(8) << fourcc;
FX_NOTREACHED();
pixel_format = fuchsia::images::PixelFormat::BGRA_8;
break;
}
fuchsia::images::ImageInfo image_info{
.width = video_format.primary_width_pixels,
.height = video_format.primary_height_pixels,
.stride = video_format.primary_line_stride_bytes,
.pixel_format = pixel_format,
};
FX_VLOGS(3) << "#### image_id: " << image_id << " width: " << image_info.width
<< " height: " << image_info.height << " stride: " << image_info.stride;
::zx::vmo image_vmo;
zx_status_t status = vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &image_vmo);
if (status != ZX_OK) {
FX_PLOGS(FATAL, status) << "vmo.duplicate() failed";
FX_NOTREACHED();
}
size_t image_vmo_size;
status = image_vmo.get_size(&image_vmo_size);
if (status != ZX_OK) {
FX_PLOGS(FATAL, status) << "vmo.get_size() failed";
FX_NOTREACHED();
}
image_pipe_->AddImage(image_id, image_info, std::move(image_vmo), vmo_offset, image_vmo_size,
fuchsia::images::MemoryType::HOST_MEMORY);
::zx::eventpair release_frame_client;
::zx::eventpair release_frame_server;
status = ::zx::eventpair::create(0, &release_frame_client, &release_frame_server);
if (status != ZX_OK) {
FX_PLOGS(FATAL, status) << "::zx::eventpair::create() failed";
FX_NOTREACHED();
}
// TODO(dustingreen): Stop doing this, or rather, do use eventpair(s) but stop
// needing to force-cast them to "event" like this, by changing ImagePipe
// interface to take eventpair instead. Specifically, a release fence that's
// just an event hampers detection when Scenic dies, since Scenic dying
// doesn't set the release fence. What we're doing here for now is forcing an
// eventpair to be treated as an event, and then relying on the eventpair
// being closed by Scenic (a short while after it's signalled by Scenic, but
// we don't notice the signalling because by then we've closed the handle
// under release_frame_server). The alternative of just using an event and
// keeping the event handle locally and trying to notice Scenic dying via
// other means seems to have some issues around not necessarily knowing that
// everything under Scenic is really done with the frame yet. The alternative
// of cloning the VMO per frame and noticing when all the clones are gone is
// perhaps workable, but doesn't seem likely to be as efficient as using
// eventpair, and it might not work for all types of VMOs (at least at the
// moment).
::zx::event release_frame_server_hack(release_frame_server.release());
// There is no expectation that ~frame would be run from this method, but
// avoid just having a raw pointer since that may be considered harmful-ish.
//
// If ~frame were to run, we rely on ~frame being able to cancel the frame's
// wait without the frame's handler running yet, which is among the ways in
// which we're relying on running on the dispatcher's single thread here.
auto on_done_wrapper = [this, image_id, on_done = std::move(on_done)] {
if (image_id == FrameSink::kBlankFrameImageId) {
// The image_pipe_ may already be gone, so don't touch ImagePipe. There
// is no on_done callback, so no worries re. not running it.
FX_DCHECK(!on_done);
return;
}
// According to ImagePipe .fidl, this RemoveImage() doesn't impact the
// present queue or the currently-displayed image. However, it might
// help avoid some complaining in the log from Scenic, for now, if we
// wait this long, instead of removing earlier.
image_pipe_->RemoveImage(image_id);
on_done();
};
auto frame = std::make_unique<Frame>(main_loop_->dispatcher(), release_frame_client,
std::move(on_done_wrapper));
// TODO(dustingreen): When release_frame_server_hack is gone, change these to
// use "auto". For now, we'll leave the types explicit to make the temp hack
// as obvious as possible.
::std::vector<::zx::event> acquire_fences;
::std::vector<::zx::event> release_fences;
release_fences.push_back(std::move(release_frame_server_hack));
// For the moment we just display every frame asap. This will hopefully tend
// to show frames faster than real-time, and provide some general impression
// of how fast they're decoding - it's fairly useless for determining the max
// presentation frame rate - that's not the point here.
image_pipe_->PresentImage(
image_id, present_time, std::move(acquire_fences), std::move(release_fences),
[image_id](fuchsia::images::PresentationInfo presentation_info) {
FX_VLOGS(3) << "PresentImageCallback() called - presentation_time: "
<< presentation_info.presentation_time
<< " presenation_interval: " << presentation_info.presentation_interval
<< " image_id: " << image_id;
});
// The frame will self-delete when its wait is done, so don't ~frame here.
(void)frame.release();
}
FrameSinkView::FrameSinkView(scenic::ViewContext context, FrameSink* parent, async::Loop* main_loop)
: BaseView(std::move(context), "FrameSinkView"),
parent_(parent),
main_loop_(main_loop),
node_(session()) {
FX_VLOGS(3) << "Creating View";
// Create an ImagePipe and use it.
uint32_t image_pipe_id = session()->AllocResourceId();
session()->Enqueue(scenic::NewCreateImagePipeCmd(
image_pipe_id, image_pipe_.NewRequest(main_loop_->dispatcher())));
// Create a material that has our image pipe mapped onto it:
scenic::Material material(session());
material.SetTexture(image_pipe_id);
session()->ReleaseResource(image_pipe_id);
// Create a rectangle shape to display the YUV on.
scenic::Rectangle shape(session(), kShapeWidth, kShapeHeight);
node_.SetShape(shape);
node_.SetMaterial(material);
root_node().AddChild(node_);
// Translation of 0, 0 is the middle of the screen
node_.SetTranslation(kInitialWindowXPos, kInitialWindowYPos, -kDisplayHeight);
InvalidateScene();
parent_->AddFrameSinkView(this);
}
void FrameSinkView::OnSceneInvalidated(fuchsia::images::PresentationInfo presentation_info) {
if (!has_logical_size()) {
return;
}
float width = logical_size().x;
float height = logical_size().y;
scenic::Rectangle shape(session(), width, height);
node_.SetShape(shape);
float half_width = width * 0.5f;
float half_height = height * 0.5f;
node_.SetTranslation(half_width, half_height, -kDisplayHeight);
}