blob: 1a2fc86b20abf9ca6202fe44a05d8bac8f790a70 [file] [log] [blame]
// Copyright 2021 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_capture.h"
#include <lib/fpromise/result.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/syscalls.h>
#include <utility>
#include "src/lib/fsl/handles/object_info.h"
#include "src/ui/scenic/lib/flatland/engine/engine.h"
#include "src/ui/scenic/lib/flatland/renderer/renderer.h"
using fuchsia::ui::composition::FrameInfo;
using fuchsia::ui::composition::ScreenCaptureConfig;
using fuchsia::ui::composition::ScreenCaptureError;
using std::vector;
namespace screen_capture {
ScreenCapture::ScreenCapture(
fidl::InterfaceRequest<fuchsia::ui::composition::ScreenCapture> request,
const vector<std::shared_ptr<allocation::BufferCollectionImporter>>&
buffer_collection_importers,
std::shared_ptr<flatland::Renderer> renderer, GetRenderables get_renderables)
: binding_(this, std::move(request)),
buffer_collection_importers_(buffer_collection_importers),
renderer_(std::move(renderer)),
get_renderables_(std::move(get_renderables)) {}
ScreenCapture::~ScreenCapture() { ClearImages(); }
void ScreenCapture::Configure(ScreenCaptureConfig args, ConfigureCallback callback) {
// Check for missing args.
if (!args.has_import_token() || !args.has_size() || !args.size().width || !args.size().height ||
!args.has_buffer_count()) {
FX_LOGS(WARNING) << "ScreenCapture::Configure: Missing arguments.";
callback(fpromise::error(ScreenCaptureError::MISSING_ARGS));
return;
}
// Check for invalid args.
if (args.buffer_count() < 1) {
FX_LOGS(WARNING) << "ScreenCapture::Configure: There must be at least one buffer.";
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());
// Event pair ID must be valid.
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;
}
// 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.size().width;
metadata.height = args.size().height;
stream_rotation_ =
args.has_rotation() ? args.rotation() : fuchsia::ui::composition::Rotation::CW_0_DEGREES;
// For each buffer in the collection, add the image to our importers.
for (uint32_t i = 0; i < args.buffer_count(); i++) {
metadata.identifier = allocation::GenerateUniqueImageId();
metadata.vmo_index = i;
for (uint32_t j = 0; j < buffer_collection_importers_.size(); j++) {
auto& importer = buffer_collection_importers_[j];
auto result = importer->ImportBufferImage(metadata);
if (!result) {
// If this importer fails, we need to release the image from all of the importers that it
// passed on. Luckily we can do this right here instead of waiting for a fence since we know
// this image isn't being used by anything yet.
for (uint32_t k = 0; k < j; k++) {
buffer_collection_importers_[k]->ReleaseBufferImage(metadata.identifier);
}
FX_LOGS(WARNING) << "ScreenCapture::Configure: Failed to import BufferImage.";
callback(fpromise::error(ScreenCaptureError::BAD_OPERATION));
return;
}
}
image_ids_[i] = metadata;
available_buffers_.push_back(i);
}
// Everything was successful!
callback(fpromise::ok());
}
void ScreenCapture::GetNextFrame(fuchsia::ui::composition::GetNextFrameArgs args,
ScreenCapture::GetNextFrameCallback callback) {
// Check that we have an available buffer that we can render.
if (available_buffers_.empty()) {
if (image_ids_.empty()) {
FX_LOGS(ERROR)
<< "ScreenCapture::GetNextFrame: No buffers configured. Was Configure called previously?";
callback(fpromise::error(ScreenCaptureError::BAD_OPERATION));
return;
}
FX_LOGS(WARNING) << "ScreenCapture::GetNextFrame: No buffers available.";
callback(fpromise::error(ScreenCaptureError::BUFFER_FULL));
return;
}
if (!args.has_event()) {
FX_LOGS(WARNING) << "ScreenCapture::GetNextFrame: Missing arguments.";
callback(fpromise::error(ScreenCaptureError::MISSING_ARGS));
return;
}
// Get renderables from the engine.
// TODO(fxbug.dev/97057): Ensure this does not happen more than once in the same vsync.
auto renderables = get_renderables_();
const auto& rects = renderables.first;
const auto& image_metadatas = renderables.second;
uint32_t buffer_id = available_buffers_.front();
const auto& metadata = image_ids_[buffer_id];
auto image_width = metadata.width;
auto image_height = metadata.height;
const auto& rotated_rects = RotateRenderables(rects, stream_rotation_, image_width, image_height);
std::vector<zx::event> events;
events.emplace_back(args.mutable_event()->release());
// Render content into user-provided buffer, which will signal the user-provided event.
renderer_->Render(metadata, rotated_rects, image_metadatas, events);
FrameInfo frame_info;
frame_info.set_buffer_id(buffer_id);
available_buffers_.pop_front();
callback(fpromise::ok(std::move(frame_info)));
}
void ScreenCapture::ReleaseFrame(uint32_t buffer_id, ReleaseFrameCallback callback) {
// Check that the buffer index is in range.
if (image_ids_.find(buffer_id) == image_ids_.end()) {
FX_LOGS(WARNING) << "ScreenCapture::ReleaseFrame: Buffer ID does not exist.";
callback(fpromise::error(ScreenCaptureError::INVALID_ARGS));
return;
}
// Check that the buffer index is not already available.
if (find(available_buffers_.begin(), available_buffers_.end(), buffer_id) !=
available_buffers_.end()) {
FX_LOGS(WARNING) << "ScreenCapture::ReleaseFrame: Buffer ID already available.";
callback(fpromise::error(ScreenCaptureError::INVALID_ARGS));
return;
}
available_buffers_.push_back(buffer_id);
callback(fpromise::ok());
}
void ScreenCapture::ClearImages() {
for (auto& image_id : image_ids_) {
auto identifier = image_id.second.identifier;
for (auto& buffer_collection_importer : buffer_collection_importers_) {
buffer_collection_importer->ReleaseBufferImage(identifier);
}
}
image_ids_.clear();
available_buffers_.clear();
}
std::vector<Rectangle2D> ScreenCapture::RotateRenderables(
const std::vector<Rectangle2D>& rects, fuchsia::ui::composition::Rotation rotation,
uint32_t image_width, uint32_t image_height) {
if (rotation == fuchsia::ui::composition::Rotation::CW_0_DEGREES)
return rects;
std::vector<Rectangle2D> final_rects;
for (const auto& rect : rects) {
auto origin = rect.origin;
auto extent = rect.extent;
auto uvs = rect.clockwise_uvs;
// (x,y) is the origin pre-rotation. (0,0) is the top-left of the image.
auto x = origin[0];
auto y = origin[1];
// (w, h) is the width and height of the rectangle pre-rotation.
auto w = extent[0];
auto h = extent[1];
// Account for rotation of the rectangle itself.
std::array<vec2, 4> new_uv_coords;
// Account for translation of the rectangle in the bounds of the canvas.
vec2 new_origin;
// Account for the new extent.
vec2 new_extent;
switch (rotation) {
case fuchsia::ui::composition::Rotation::CW_90_DEGREES:
new_uv_coords = {uvs[3], uvs[0], uvs[1], uvs[2]};
new_origin = {static_cast<float>(image_width) - y - h, x};
new_extent = {h, w};
break;
case fuchsia::ui::composition::Rotation::CW_180_DEGREES:
new_uv_coords = {uvs[2], uvs[3], uvs[0], uvs[1]};
new_origin = {static_cast<float>(image_width) - x - w,
static_cast<float>(image_height) - y - h};
new_extent = {w, h};
break;
case fuchsia::ui::composition::Rotation::CW_270_DEGREES:
new_uv_coords = {uvs[1], uvs[2], uvs[3], uvs[0]};
new_origin = {y, static_cast<float>(image_height) - x - w};
new_extent = {h, w};
break;
default:
FX_DCHECK(false);
break;
}
final_rects.emplace_back(new_origin, new_extent, new_uv_coords);
}
return final_rects;
}
} // namespace screen_capture