blob: c46ee42bc645db5b560943870e48006f1467c6c6 [file] [log] [blame] [edit]
// Copyright 2022 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 "screen_capture2.h"
#include <lib/async/cpp/wait.h>
#include <lib/async/default.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <optional>
#include <utility>
#include "src/lib/fsl/handles/object_info.h"
using fuchsia::ui::composition::internal::FrameInfo;
using fuchsia::ui::composition::internal::ScreenCaptureConfig;
using fuchsia::ui::composition::internal::ScreenCaptureError;
using std::vector;
namespace screen_capture2 {
ScreenCapture::ScreenCapture(std::shared_ptr<screen_capture::ScreenCaptureBufferCollectionImporter>
screen_capture_buffer_collection_importer,
std::shared_ptr<flatland::Renderer> renderer,
GetRenderables get_renderables)
: screen_capture_buffer_collection_importer_(screen_capture_buffer_collection_importer),
renderer_(renderer),
get_renderables_(std::move(get_renderables)),
weak_factory_(this) {}
ScreenCapture::~ScreenCapture() { ClearImages(); }
void ScreenCapture::Configure(ScreenCaptureConfig args, ConfigureCallback callback) {
if (!args.has_image_size()) {
FX_LOGS(WARNING) << "ScreenCapture::Configure: Missing image size";
callback(fpromise::error(ScreenCaptureError::MISSING_ARGS));
return;
}
if (!args.has_import_token()) {
FX_LOGS(WARNING) << "ScreenCapture::Configure: Missing import token";
callback(fpromise::error(ScreenCaptureError::MISSING_ARGS));
return;
}
if (!args.image_size().width || !args.image_size().height) {
FX_LOGS(WARNING) << "ScreenCapture::Configure: Invalid arguments.";
callback(fpromise::error(ScreenCaptureError::INVALID_ARGS));
return;
}
auto import_token = args.mutable_import_token();
const zx_koid_t global_collection_id = fsl::GetRelatedKoid(import_token->value.get());
if (global_collection_id == ZX_KOID_INVALID) {
FX_LOGS(WARNING) << "ScreenCapture::Configure: Event pair ID must be valid.";
callback(fpromise::error(ScreenCaptureError::INVALID_ARGS));
return;
}
std::optional<BufferCount> buffer_count_opt =
screen_capture_buffer_collection_importer_->GetBufferCollectionBufferCount(
global_collection_id);
if (!buffer_count_opt) {
FX_LOGS(WARNING) << "ScreenCapture::Configure: Failed to get BufferCount.";
callback(fpromise::error(ScreenCaptureError::INVALID_ARGS));
return;
}
BufferCount buffer_count = buffer_count_opt.value();
if (buffer_count < 0) {
FX_LOGS(WARNING) << "ScreenCapture::Configure: There must be at least 0 buffers.";
callback(fpromise::error(ScreenCaptureError::INVALID_ARGS));
return;
}
// Release any existing buffers and reset |image_ids_| and |available_buffers_|.
ClearImages();
// Create the associated metadata. Note that clients are responsible for ensuring reasonable
// parameters.
allocation::ImageMetadata metadata;
metadata.collection_id = global_collection_id;
metadata.width = args.image_size().width;
metadata.height = args.image_size().height;
// For each buffer in the collection, add the image to the importer.
for (uint32_t i = 0; i < buffer_count; i++) {
metadata.identifier = allocation::GenerateUniqueImageId();
metadata.vmo_index = i;
auto result = screen_capture_buffer_collection_importer_->ImportBufferImage(
metadata, allocation::BufferCollectionUsage::kRenderTarget);
if (!result) {
ClearImages();
FX_LOGS(WARNING) << "ScreenCapture::Configure: Failed to import BufferImage at index " << i;
callback(fpromise::error(ScreenCaptureError::INVALID_ARGS));
return;
}
image_ids_[i] = metadata;
available_buffers_.push_front(i);
}
client_received_last_frame_ = false;
render_frame_in_progress_ = false;
current_callback_ = std::nullopt;
callback(fpromise::ok());
}
void ScreenCapture::GetNextFrame(ScreenCapture::GetNextFrameCallback callback) {
TRACE_DURATION("gfx", "GetNextFrame");
if (current_callback_ != std::nullopt) {
FX_LOGS(WARNING) << "ScreenCapture::GetNextFrame: GetNextFrame already in progress. Wait for "
"it to return before calling again.";
callback(fpromise::error(ScreenCaptureError::BAD_HANGING_GET));
return;
}
current_callback_ = std::move(callback);
if (!client_received_last_frame_ && !available_buffers_.empty()) {
MaybeRenderFrame();
}
}
void ScreenCapture::MaybeRenderFrame() {
TRACE_DURATION("gfx", "MaybeRenderFrame");
if (render_frame_in_progress_) {
return;
}
render_frame_in_progress_ = true;
if (current_callback_ == std::nullopt) {
client_received_last_frame_ = false;
render_frame_in_progress_ = false;
return;
}
if (available_buffers_.empty()) {
FX_LOGS(WARNING) << "ScreenCapture::MaybeRenderFrame: Should ensure there are available "
"buffers before call.";
client_received_last_frame_ = false;
render_frame_in_progress_ = false;
return;
}
const uint32_t buffer_index = available_buffers_.front();
available_buffers_.pop_front();
// Get renderables from the engine.
auto renderables = get_renderables_();
const auto& rects = renderables.first;
const auto& image_metadatas = renderables.second;
const auto& metadata = image_ids_[buffer_index];
zx::event release_fence;
zx_status_t status = zx::event::create(0, &release_fence);
FX_DCHECK(status == ZX_OK);
// Set up the |async::Wait| for call to Render() to signal release_fence. Note that we are
// passing ownership of the callback here.
auto wait = std::make_shared<async::WaitOnce>(release_fence.get(), ZX_EVENT_SIGNALED);
status = wait->Begin(async_get_default_dispatcher(),
[copy_ref = wait, weak_ptr = weak_factory_.GetWeakPtr(), buffer_index](
async_dispatcher_t*, async::WaitOnce*, zx_status_t status,
const zx_packet_signal_t* signal) mutable {
FX_DCHECK(status == ZX_OK || status == ZX_ERR_CANCELED);
if (!weak_ptr) {
return;
}
weak_ptr->HandleRender(buffer_index, signal->timestamp);
});
FX_DCHECK(status == ZX_OK);
// TODO(https://fxbug.dev/42174813): Clean up current_release_fences_ once bug is fixed.
FX_DCHECK(current_release_fences_.empty());
current_release_fences_.push_back(std::move(release_fence));
// Render content into user-provided buffer, which will signal the release_fence.
renderer_->Render(metadata, rects, image_metadatas, {.release_fences = current_release_fences_});
}
void ScreenCapture::HandleRender(uint32_t buffer_index, uint64_t timestamp) {
TRACE_DURATION("gfx", "HandleRender");
zx::eventpair buffer_release_client_token;
zx::eventpair buffer_release_server_token;
zx::eventpair::create(0, &buffer_release_server_token, &buffer_release_client_token);
// Set up |async::Wait| for when client releases buffer.
auto wait = std::make_shared<async::WaitOnce>(buffer_release_server_token.get(),
ZX_EVENTPAIR_PEER_CLOSED | ZX_EVENTPAIR_SIGNALED);
auto status = wait->Begin(async_get_default_dispatcher(),
[copy_ref = wait, weak_ptr = weak_factory_.GetWeakPtr(), buffer_index](
async_dispatcher_t*, async::WaitOnce*, zx_status_t status,
const zx_packet_signal_t*) mutable {
FX_DCHECK(status == ZX_OK || status == ZX_ERR_CANCELED);
if (!weak_ptr) {
return;
}
weak_ptr->HandleBufferRelease(buffer_index);
});
FX_DCHECK(status == ZX_OK);
buffer_server_tokens_[buffer_index] = std::move(buffer_release_server_token);
FrameInfo frame_info;
frame_info.set_buffer_index(buffer_index);
frame_info.set_buffer_release_token(std::move(buffer_release_client_token));
frame_info.set_capture_timestamp(timestamp);
GetNextFrameCallback callback = std::move(current_callback_.value());
callback(fpromise::ok(std::move(frame_info)));
current_release_fences_.clear();
current_callback_ = std::nullopt;
client_received_last_frame_ = true;
render_frame_in_progress_ = false;
}
void ScreenCapture::HandleBufferRelease(uint32_t buffer_index) {
TRACE_DURATION("gfx", "HandleBufferRelease", "buffer_index", buffer_index);
buffer_server_tokens_.erase(buffer_index);
if (available_buffers_.empty() && (current_callback_ != std::nullopt)) {
available_buffers_.push_front(buffer_index);
MaybeRenderFrame();
return;
}
available_buffers_.push_front(buffer_index);
}
void ScreenCapture::ClearImages() {
for (auto& image_id : image_ids_) {
auto identifier = image_id.second.identifier;
screen_capture_buffer_collection_importer_->ReleaseBufferImage(identifier);
}
image_ids_.clear();
available_buffers_.clear();
}
} // namespace screen_capture2