blob: d13e820d9c85fc16038ad63a09aa90bc2e7b92e7 [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 "src/ui/scenic/lib/allocation/allocator.h"
#include <lib/async/cpp/wait.h>
#include <lib/async/default.h>
#include <lib/fit/function.h>
#include <lib/syslog/cpp/macros.h>
#include <lib/trace/event.h>
#include <memory>
#include "src/lib/fsl/handles/object_info.h"
#include "src/ui/scenic/lib/allocation/buffer_collection_importer.h"
using allocation::BufferCollectionUsage;
using fuchsia::ui::composition::RegisterBufferCollectionError;
using fuchsia::ui::composition::RegisterBufferCollectionUsages;
namespace allocation {
namespace {
RegisterBufferCollectionUsages UsageToUsages(
fuchsia::ui::composition::RegisterBufferCollectionUsage usage) {
switch (usage) {
case fuchsia::ui::composition::RegisterBufferCollectionUsage::DEFAULT:
return fuchsia::ui::composition::RegisterBufferCollectionUsages::DEFAULT;
case fuchsia::ui::composition::RegisterBufferCollectionUsage::SCREENSHOT:
return fuchsia::ui::composition::RegisterBufferCollectionUsages::SCREENSHOT;
}
}
bool BufferCollectionTokenIsValid(
fuchsia::sysmem::AllocatorSyncPtr& sysmem_allocator,
const fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>& token) {
bool is_known = false;
const auto status = sysmem_allocator->ValidateBufferCollectionToken(
fsl::GetRelatedKoid(token.channel().get()), &is_known);
return status == ZX_OK && is_known;
}
// Creates a vector of |num_tokens| duplicates of BufferCollectionTokenSyncPtr.
// Returns an empty vector if creation failed.
std::vector<fuchsia::sysmem::BufferCollectionTokenSyncPtr> CreateVectorOfTokens(
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> token, const size_t num_tokens) {
FX_DCHECK(num_tokens > 0);
std::vector<fuchsia::sysmem::BufferCollectionTokenSyncPtr> tokens;
tokens.emplace_back(token.BindSync());
std::vector<fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken>> dup_tokens;
if (tokens.front()->DuplicateSync(std::vector<zx_rights_t>(num_tokens - 1, ZX_RIGHT_SAME_RIGHTS),
&dup_tokens) == ZX_OK) {
for (auto& token : dup_tokens) {
tokens.emplace_back(token.BindSync());
}
} else {
tokens.clear();
}
return tokens;
}
struct ParsedArgs {
zx_koid_t koid;
RegisterBufferCollectionUsages buffer_collection_usages;
fuchsia::ui::composition::BufferCollectionExportToken export_token;
fidl::InterfaceHandle<fuchsia::sysmem::BufferCollectionToken> buffer_collection_token;
};
// Parses the FIDL struct, validating the arguments. Logs an error and returns std::nullopt on
// failure.
std::optional<ParsedArgs> ParseArgs(fuchsia::ui::composition::RegisterBufferCollectionArgs args) {
// It's okay if there's no specified RegisterBufferCollectionUsage. In that case, assume it is
// DEFAULT.
if (!args.has_buffer_collection_token() || !args.has_export_token()) {
FX_LOGS(ERROR) << "RegisterBufferCollection called with missing arguments";
return std::nullopt;
}
if (!args.buffer_collection_token().is_valid()) {
FX_LOGS(ERROR) << "RegisterBufferCollection called with invalid buffer collection token";
return std::nullopt;
}
if (!args.export_token().value.is_valid()) {
FX_LOGS(ERROR) << "RegisterBufferCollection called with invalid export token";
return std::nullopt;
}
// Check if there is a valid peer.
if (fsl::GetRelatedKoid(args.export_token().value.get()) == ZX_KOID_INVALID) {
FX_LOGS(ERROR) << "RegisterBufferCollection called with no valid import tokens";
return std::nullopt;
}
if (args.has_usages() && args.usages().has_unknown_bits()) {
FX_LOGS(ERROR) << "Arguments contain unknown BufferCollectionUsage type";
return std::nullopt;
}
// Grab object koid to be used as unique_id.
const GlobalBufferCollectionId koid = fsl::GetKoid(args.export_token().value.get());
FX_DCHECK(koid != ZX_KOID_INVALID);
// If no usages are set we default to DEFAULT. Otherwise the newer "usages" value takes precedence
// over the deprecated "usage" variant.
RegisterBufferCollectionUsages buffer_collection_usages = RegisterBufferCollectionUsages::DEFAULT;
if (args.has_usages()) {
buffer_collection_usages = args.usages();
} else if (args.has_usage()) {
buffer_collection_usages = UsageToUsages(args.usage());
}
return ParsedArgs{
.koid = koid,
.buffer_collection_usages = buffer_collection_usages,
.export_token = std::move(*args.mutable_export_token()),
.buffer_collection_token = std::move(*args.mutable_buffer_collection_token()),
};
}
} // namespace
Allocator::Allocator(sys::ComponentContext* app_context,
const std::vector<std::shared_ptr<BufferCollectionImporter>>&
default_buffer_collection_importers,
const std::vector<std::shared_ptr<BufferCollectionImporter>>&
screenshot_buffer_collection_importers,
fuchsia::sysmem::AllocatorSyncPtr sysmem_allocator)
: dispatcher_(async_get_default_dispatcher()),
default_buffer_collection_importers_(default_buffer_collection_importers),
screenshot_buffer_collection_importers_(screenshot_buffer_collection_importers),
sysmem_allocator_(std::move(sysmem_allocator)),
weak_factory_(this) {
FX_DCHECK(app_context);
app_context->outgoing()->AddPublicService(bindings_.GetHandler(this));
}
Allocator::~Allocator() {
FX_DCHECK(dispatcher_ == async_get_default_dispatcher());
// Allocator outlives |*_buffer_collection_importers_| instances, because we hold shared_ptrs. It
// is safe to release all remaining buffer collections because there should be no more usage.
while (!buffer_collections_.empty()) {
ReleaseBufferCollection(buffer_collections_.begin()->first);
}
}
void Allocator::RegisterBufferCollection(
fuchsia::ui::composition::RegisterBufferCollectionArgs args,
RegisterBufferCollectionCallback callback) {
TRACE_DURATION("gfx", "allocation::Allocator::RegisterBufferCollection");
FX_DCHECK(dispatcher_ == async_get_default_dispatcher());
auto parsed_args = ParseArgs(std::move(args));
if (!parsed_args) {
callback(fpromise::error(RegisterBufferCollectionError::BAD_OPERATION));
return;
}
auto& [koid, buffer_collection_usages, export_token, buffer_collection_token] =
parsed_args.value();
// Check if this export token has already been used.
if (buffer_collections_.find(koid) != buffer_collections_.end()) {
FX_LOGS(ERROR) << "RegisterBufferCollection called with pre-registered export token";
callback(fpromise::error(RegisterBufferCollectionError::BAD_OPERATION));
return;
}
if (!BufferCollectionTokenIsValid(sysmem_allocator_, buffer_collection_token)) {
FX_LOGS(ERROR) << "RegisterBufferCollection called with a buffer collection token where "
"ValidateBufferCollectionToken() failed";
callback(fpromise::error(RegisterBufferCollectionError::BAD_OPERATION));
return;
}
const auto importers = GetImporters(buffer_collection_usages);
// Create a token for each of the buffer collection importers.
auto tokens = CreateVectorOfTokens(std::move(buffer_collection_token), importers.size());
if (tokens.empty()) {
FX_LOGS(ERROR) << "RegisterBufferCollection called with a buffer collection token where "
"Duplicate() failed";
callback(fpromise::error(RegisterBufferCollectionError::BAD_OPERATION));
return;
}
// Loop over each of the importers and provide each of them with a token from the vector we
// created above.
for (uint32_t i = 0; i < importers.size(); i++) {
bool import_successful = false;
{
auto& [importer, usage] = importers.at(i);
import_successful = importer.ImportBufferCollection(
koid, sysmem_allocator_.get(), std::move(tokens[i]), usage, std::nullopt);
}
if (!import_successful) {
// If any importers failed then clean up the ones that didn't before returning.
for (uint32_t j = 0; j < i; j++) {
auto& [importer, usage] = importers.at(j);
importer.ReleaseBufferCollection(koid, usage);
}
FX_LOGS(ERROR) << "Failed to import the buffer collection to the BufferCollectionimporter.";
callback(fpromise::error(RegisterBufferCollectionError::BAD_OPERATION));
return;
}
}
buffer_collections_[koid] = buffer_collection_usages;
// Use a self-referencing async::WaitOnce to deregister buffer collections when all
// BufferCollectionImportTokens are used, i.e. peers of eventpair are closed. Note that the
// ownership of |export_token| is also passed, so that GetRelatedKoid() calls return valid koid.
auto wait = std::make_shared<async::WaitOnce>(export_token.value.get(), ZX_EVENTPAIR_PEER_CLOSED);
const zx_status_t status =
wait->Begin(async_get_default_dispatcher(),
[keepalive_wait = wait, keepalive_export_token = std::move(export_token.value),
weak_ptr = weak_factory_.GetWeakPtr(),
koid = koid](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;
// Because Flatland::CreateImage() holds an import token, this
// is guaranteed to be called after all images are created, so
// it is safe to release buffer collection.
weak_ptr->ReleaseBufferCollection(koid);
});
FX_DCHECK(status == ZX_OK);
callback(fpromise::ok());
}
std::vector<std::pair<BufferCollectionImporter&, BufferCollectionUsage>> Allocator::GetImporters(
const RegisterBufferCollectionUsages usages) const {
std::vector<std::pair<BufferCollectionImporter&, BufferCollectionUsage>> importers;
if (usages & RegisterBufferCollectionUsages::DEFAULT) {
for (const auto& importer : default_buffer_collection_importers_) {
importers.emplace_back(*importer, BufferCollectionUsage::kClientImage);
}
}
if (usages & RegisterBufferCollectionUsages::SCREENSHOT) {
for (const auto& importer : screenshot_buffer_collection_importers_) {
importers.emplace_back(*importer, BufferCollectionUsage::kRenderTarget);
}
}
return importers;
}
void Allocator::ReleaseBufferCollection(GlobalBufferCollectionId collection_id) {
TRACE_DURATION("gfx", "allocation::Allocator::ReleaseBufferCollection");
FX_DCHECK(dispatcher_ == async_get_default_dispatcher());
const auto usages = buffer_collections_.at(collection_id);
buffer_collections_.erase(collection_id);
for (auto& [importer, usage] : GetImporters(usages)) {
importer.ReleaseBufferCollection(collection_id, usage);
}
}
} // namespace allocation