| // 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 |