blob: 9ee30ed3966f30a789073fbdf876598a91ce9eed [file] [log] [blame]
// 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 "src/embedder/software_surface.h"
#include <fuchsia/sysmem/cpp/fidl.h>
#include <lib/async/default.h>
#include <lib/syslog/global.h>
#include <lib/ui/scenic/cpp/commands.h>
#include <zircon/rights.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <cmath>
#include "src/embedder/logging.h"
namespace embedder {
namespace {
uint32_t BytesPerRow(const fuchsia::sysmem::SingleBufferSettings& settings,
uint32_t bytes_per_pixel, uint32_t image_width) {
const uint32_t bytes_per_row_divisor = settings.image_format_constraints.bytes_per_row_divisor;
const uint32_t min_bytes_per_row = settings.image_format_constraints.min_bytes_per_row;
const uint32_t unrounded_bytes_per_row =
std::max(image_width * bytes_per_pixel, min_bytes_per_row);
const uint32_t roundup_bytes = unrounded_bytes_per_row % bytes_per_row_divisor;
return unrounded_bytes_per_row + roundup_bytes;
}
} // namespace
SoftwareSurface::SoftwareSurface(fuchsia::sysmem::AllocatorSyncPtr& sysmem_allocator,
fuchsia::ui::composition::AllocatorPtr& flatland_allocator,
const Size size)
: wait_for_read_finished_(this) {
if (!flatland_allocator.is_bound()) {
FX_LOG(FATAL, kLogTag,
"fuchsia.ui.composition.Allocator must be bound before creating "
"SoftwareSurface");
return;
}
if (!AcquireSurface(sysmem_allocator, flatland_allocator, size)) {
FX_LOG(ERROR, kLogTag, "Could not create render surface.");
return;
}
if (!CreateFences()) {
FX_LOG(ERROR, kLogTag, "Could not create signal fences.");
return;
}
wait_for_read_finished_.set_object(release_event_.get());
wait_for_read_finished_.set_trigger(ZX_EVENT_SIGNALED);
Reset();
valid_ = true;
}
SoftwareSurface::~SoftwareSurface() {
release_image_callback_();
wait_for_read_finished_.Cancel();
wait_for_read_finished_.set_object(ZX_HANDLE_INVALID);
}
bool SoftwareSurface::CreateFences() {
if (zx::event::create(0, &acquire_event_) != ZX_OK) {
FX_LOG(ERROR, kLogTag, "Failed to create acquire event.");
return false;
}
if (zx::event::create(0, &release_event_) != ZX_OK) {
FX_LOG(ERROR, kLogTag, "Failed to create release event.");
return false;
}
return true;
}
bool SoftwareSurface::AcquireSurface(fuchsia::sysmem::AllocatorSyncPtr& sysmem_allocator,
fuchsia::ui::composition::AllocatorPtr& flatland_allocator,
const Size size) {
// Verify args.
// TODO(akbiggs): Fix race that's making this fail randomly.
const uint32_t width = size.width;
const uint32_t height = size.height;
if (width <= 0 || height <= 0) {
FX_LOG(ERROR, kLogTag, "Failed to allocate surface, size is empty.");
return false;
}
// Allocate a "local" sysmem token to represent Flutter's handle to the
// sysmem buffer.
fuchsia::sysmem::BufferCollectionTokenSyncPtr local_token;
zx_status_t status = sysmem_allocator->AllocateSharedCollection(local_token.NewRequest());
if (status != ZX_OK) {
FX_LOGF(ERROR, kLogTag, "Failed to allocate collection: %s", zx_status_get_string(status));
return false;
}
// Create a single duplicate of the token and sync it; the single
// duplicate token represents Flatland's handle to the sysmem buffer
// collection.
std::vector<fuchsia::sysmem::BufferCollectionTokenHandle> duplicate_tokens;
status =
local_token->DuplicateSync(std::vector<zx_rights_t>{ZX_RIGHT_SAME_RIGHTS}, &duplicate_tokens);
if (status != ZX_OK) {
FX_LOGF(ERROR, kLogTag, "Failed to duplicate collection token: %s",
zx_status_get_string(status));
return false;
}
if (duplicate_tokens.size() != 1u) {
FX_LOG(ERROR, kLogTag,
"Failed to duplicate collection token: Incorrect number of "
"tokens returned");
return false;
}
auto buffer_collection_token = std::move(duplicate_tokens[0]);
// Create a token pair (export and import token) for Flatland.
// The export token is Flatland's token for the surface,
// representing the surface in Flatland.
// The import token is our token for the surface, letting
// us tell Flatland to present that surface.
fuchsia::ui::composition::BufferCollectionExportToken export_token;
fuchsia::ui::composition::BufferCollectionImportToken import_token;
status = zx::eventpair::create(0, &export_token.value, &import_token.value);
if (status != ZX_OK) {
FX_LOGF(ERROR, kLogTag, "Failed to create Flatland token pair: %s",
zx_status_get_string(status));
return false;
}
import_token_ = std::move(import_token);
fuchsia::ui::composition::RegisterBufferCollectionArgs args;
args.set_export_token(std::move(export_token));
args.set_buffer_collection_token(std::move(buffer_collection_token));
args.set_usage(fuchsia::ui::composition::RegisterBufferCollectionUsage::DEFAULT);
flatland_allocator->RegisterBufferCollection(
std::move(args),
[](fuchsia::ui::composition::Allocator_RegisterBufferCollection_Result result) {
if (result.is_err()) {
FX_LOGF(ERROR, kLogTag,
"RegisterBufferCollection call to Flatland Allocator failed. "
"RegisterBufferCollectionError value: %d",
static_cast<int>(result.err()));
}
});
width_ = width;
height_ = height;
// Acquire Flutter's local handle to the sysmem buffer.
fuchsia::sysmem::BufferCollectionSyncPtr buffer_collection;
status = sysmem_allocator->BindSharedCollection(std::move(local_token),
buffer_collection.NewRequest());
if (status != ZX_OK) {
FX_LOGF(ERROR, kLogTag, "Failed to bind collection token: %s", zx_status_get_string(status));
return false;
}
// Set Flutter's constraints on the sysmem buffer. Software rendering
// only requires CPU access to the surface and a basic R8G8B8A8 pixel
// format.
fuchsia::sysmem::BufferCollectionConstraints constraints;
constraints.min_buffer_count = 1;
constraints.usage.cpu = fuchsia::sysmem::cpuUsageWrite | fuchsia::sysmem::cpuUsageWriteOften;
constraints.has_buffer_memory_constraints = true;
constraints.buffer_memory_constraints.physically_contiguous_required = false;
constraints.buffer_memory_constraints.secure_required = false;
constraints.buffer_memory_constraints.ram_domain_supported = true;
constraints.buffer_memory_constraints.cpu_domain_supported = true;
constraints.buffer_memory_constraints.inaccessible_domain_supported = false;
constraints.image_format_constraints_count = 1;
fuchsia::sysmem::ImageFormatConstraints& image_constraints =
constraints.image_format_constraints[0];
image_constraints = fuchsia::sysmem::ImageFormatConstraints();
image_constraints.min_coded_width = static_cast<uint32_t>(width);
image_constraints.min_coded_height = static_cast<uint32_t>(height);
image_constraints.min_bytes_per_row = static_cast<uint32_t>(width) * 4;
image_constraints.pixel_format.type = fuchsia::sysmem::PixelFormatType::R8G8B8A8;
image_constraints.color_spaces_count = 1;
image_constraints.color_space[0].type = fuchsia::sysmem::ColorSpaceType::SRGB;
image_constraints.pixel_format.has_format_modifier = true;
image_constraints.pixel_format.format_modifier.value = fuchsia::sysmem::FORMAT_MODIFIER_LINEAR;
status = buffer_collection->SetConstraints(true, constraints);
if (status != ZX_OK) {
FX_LOGF(ERROR, kLogTag, "Failed to set constraints: %s", zx_status_get_string(status));
return false;
}
// Wait for sysmem to allocate, now that constraints are set.
fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info;
zx_status_t allocation_status = ZX_OK;
zx_status_t wait_for_allocated_status =
buffer_collection->WaitForBuffersAllocated(&allocation_status, &buffer_collection_info);
if (allocation_status != ZX_OK) {
FX_LOGF(ERROR, kLogTag, "Failed to allocate: %s", zx_status_get_string(allocation_status));
return false;
}
if (wait_for_allocated_status != ZX_OK) {
FX_LOGF(ERROR, kLogTag, "Failed to wait for allocate: %s",
zx_status_get_string(wait_for_allocated_status));
return false;
}
if (buffer_collection_info.settings.buffer_settings.size_bytes == 0) {
FX_LOG(ERROR, kLogTag,
"Allocating the software surface succeeded but created an empty "
"buffer. This could indicate an internal bug.");
return false;
}
if (buffer_collection_info.buffers[0].vmo == ZX_HANDLE_INVALID) {
FX_LOG(ERROR, kLogTag,
"Allocating the software surface succeeded but returned an invalid "
"VMO. This could indicate an internal bug.");
return false;
}
// Cache the allocated surface VMO and metadata.
//
// TODO(akbiggs): Signal cache clean using this data.
// See
// https://github.com/flutter/engine/blob/main/shell/platform/fuchsia/flutter/software_surface.cc#L383.
vmo_ = std::move(buffer_collection_info.buffers[0].vmo);
size_bytes_ = buffer_collection_info.settings.buffer_settings.size_bytes;
if (buffer_collection_info.settings.buffer_settings.coherency_domain ==
fuchsia::sysmem::CoherencyDomain::RAM) {
// RAM coherency domain requires a cache clean when writes are
// finished.
needs_cache_clean_ = true;
}
// Map the allocated buffer to the CPU.
uint8_t* vmo_base = nullptr;
status = zx::vmar::root_self()->map(ZX_VM_PERM_WRITE | ZX_VM_PERM_READ /* options */,
0 /* vmar_offset */, vmo_, 0 /* vmo_offset */, size_bytes_,
reinterpret_cast<uintptr_t*>(&vmo_base) /* mapped_addr */);
if (status != ZX_OK) {
FX_LOGF(ERROR, kLogTag, "Failed to map buffer memory: %s", zx_status_get_string(status));
return false;
}
// Now that the buffer is CPU-readable, it's safe to discard Flutter's
// connection to sysmem.
status = buffer_collection->Close();
if (status != ZX_OK) {
FX_LOGF(ERROR, kLogTag, "Failed to close buffer: %s", zx_status_get_string(status));
return false;
}
const uint64_t vmo_offset = buffer_collection_info.buffers[0].vmo_usable_start;
bytes_per_row_ = BytesPerRow(buffer_collection_info.settings, 4u /* bytes_per_pixel */, width);
allocation_ = vmo_base + vmo_offset;
return true;
}
void SoftwareSurface::SetImageId(uint32_t image_id) {
if (image_id_ != 0) {
FX_LOGF(FATAL, kLogTag,
"Trying to set SoftwareSurface image ID to %d but it has already "
"been set to %d",
image_id, image_id_);
return;
}
image_id_ = image_id;
}
uint32_t SoftwareSurface::GetImageId() const { return image_id_; }
Size SoftwareSurface::GetSize() const {
if (!IsValid()) {
FX_LOG(ERROR, kLogTag, "Tried to get size of invalid surface. Returning (0, 0).");
return {.width = 0, .height = 0};
}
return {.width = width_, .height = height_};
}
fuchsia::ui::composition::BufferCollectionImportToken
SoftwareSurface::GetBufferCollectionImportToken() {
fuchsia::ui::composition::BufferCollectionImportToken import_dup;
import_token_.value.duplicate(ZX_RIGHT_SAME_RIGHTS, &import_dup.value);
return import_dup;
}
zx::event SoftwareSurface::GetAcquireFence() {
zx::event fence;
acquire_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &fence);
return fence;
}
zx::event SoftwareSurface::GetReleaseFence() {
zx::event fence;
release_event_.duplicate(ZX_RIGHT_SAME_RIGHTS, &fence);
return fence;
}
void SoftwareSurface::SetReleaseImageCallback(ReleaseImageCallback release_image_callback) {
release_image_callback_ = release_image_callback;
}
void SoftwareSurface::SignalWritesFinished(const std::function<void(void)>& on_read_finished) {
if (!IsValid()) {
on_read_finished();
return;
}
if (read_finished_callback_ != nullptr) {
// Commented out until we figure out why this is being triggered. It's spamming the logs.
// FX_LOG(ERROR, kLogTag,
// "Attempted to signal a write on the surface when the "
// "previous write has not yet been acknowledged by the "
// "compositor.");
return;
}
read_finished_callback_ = on_read_finished;
// Sysmem *may* require the cache to be cleared after writes to the surface
// are complete.
if (needs_cache_clean_) {
vmo_.op_range(ZX_VMO_OP_CACHE_CLEAN, 0, size_bytes_, nullptr /* buffer */, 0 /* buffer_size */);
}
// Inform Scenic that Flutter is finished writing to the surface.
zx_status_t status = acquire_event_.signal(0u, ZX_EVENT_SIGNALED);
if (status != ZX_OK) {
FX_LOGF(ERROR, kLogTag, "Failed to signal acquire event: %s", zx_status_get_string(status));
}
}
void SoftwareSurface::Reset() {
if (acquire_event_.signal(ZX_EVENT_SIGNALED, 0u) != ZX_OK ||
release_event_.signal(ZX_EVENT_SIGNALED, 0u) != ZX_OK) {
valid_ = false;
FX_LOG(ERROR, kLogTag, "Could not reset fences. The surface is no longer valid.");
}
wait_for_read_finished_.Begin(async_get_default_dispatcher());
// It is safe for the caller to collect the surface in the callback.
auto callback = read_finished_callback_;
read_finished_callback_ = nullptr;
if (callback) {
callback();
}
}
void SoftwareSurface::OnReadFinished(async_dispatcher_t* dispatcher, async::WaitBase* wait,
zx_status_t status, const zx_packet_signal_t* signal) {
if (status != ZX_OK) {
return;
}
// Validate that we were actually signalled before calling this.
if (!(signal->observed & ZX_EVENT_SIGNALED)) {
FX_LOG(ERROR, kLogTag, "SoftwareSurface::OnReadFinished called without a signal.");
}
Reset();
}
bool SoftwareSurface::IsValid() const { return valid_; }
uint8_t* SoftwareSurface::GetAllocation() const { return allocation_; }
uint32_t SoftwareSurface::GetBytesPerRow() const { return bytes_per_row_; }
} // namespace embedder