| // 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_producer.h" |
| |
| #include <lib/fdio/directory.h> |
| #include <lib/syslog/global.h> |
| #include <lib/zx/process.h> |
| #include <zircon/status.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "src/embedder/engine/embedder.h" |
| #include "src/embedder/logging.h" |
| |
| namespace embedder { |
| |
| namespace { |
| |
| /// Gets the current name of the Fuchsia process that |
| /// is running the embedder. |
| std::string GetCurrentProcessName() { |
| char name[ZX_MAX_NAME_LEN]; |
| zx_status_t status = zx::process::self()->get_property(ZX_PROP_NAME, name, sizeof(name)); |
| if (status != ZX_OK) { |
| FX_LOG(ERROR, kLogTag, "Failed to get process name for sysmem; using \"\"."); |
| return std::string(); |
| } |
| |
| return std::string(name); |
| } |
| |
| /// Gets the current process ID of the Fuchsia process that |
| /// is running the embedder. |
| zx_koid_t GetCurrentProcessId() { |
| zx_info_handle_basic_t info; |
| zx_status_t status = |
| zx::process::self()->get_info(ZX_INFO_HANDLE_BASIC, &info, sizeof(info), |
| nullptr /* actual_count */, nullptr /* avail_count */); |
| if (status != ZX_OK) { |
| FX_LOG(ERROR, kLogTag, "Failed to get process ID for sysmem; using ZX_KOID_INVALID."); |
| return ZX_KOID_INVALID; |
| } |
| |
| return info.koid; |
| } |
| |
| } // namespace |
| |
| SoftwareSurfaceProducer::SoftwareSurfaceProducer() { |
| zx_status_t status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator", |
| sysmem_allocator_.NewRequest().TakeChannel().release()); |
| sysmem_allocator_->SetDebugClientInfo(GetCurrentProcessName(), GetCurrentProcessId()); |
| if (status != ZX_OK) { |
| FX_LOGF(ERROR, kLogTag, "Failed to connect to fuchsia.sysmem.Allocator: %s", |
| zx_status_get_string(status)); |
| return; |
| } |
| |
| status = fdio_service_connect("/svc/fuchsia.ui.composition.Allocator", |
| flatland_allocator_.NewRequest().TakeChannel().release()); |
| if (status != ZX_OK) { |
| FX_LOGF(ERROR, kLogTag, "Failed to connect to fuchsia.ui.composition.Allocator: %s", |
| zx_status_get_string(status)); |
| return; |
| } |
| |
| valid_ = true; |
| } |
| |
| SoftwareSurfaceProducer::~SoftwareSurfaceProducer() = default; |
| |
| std::unique_ptr<SoftwareSurface> SoftwareSurfaceProducer::ProduceOffscreenSurface(const Size size) { |
| if (!IsValid()) { |
| FX_LOG(FATAL, kLogTag, "ProduceOffscreenSurface called with invalid state."); |
| return nullptr; |
| } |
| |
| return CreateSurface(size); |
| } |
| |
| std::unique_ptr<SoftwareSurface> SoftwareSurfaceProducer::ProduceSurface(const Size size) { |
| // TODO(akbiggs): Trace width and height here for parity with |
| // Github code. |
| FlutterEngineTraceEventInstant("SoftwareSurfaceProducer::ProduceSurface"); |
| |
| if (!IsValid()) { |
| FX_LOG(FATAL, kLogTag, "ProduceSurface called with invalid state."); |
| return nullptr; |
| } |
| |
| std::unique_ptr<SoftwareSurface> surface; |
| auto exact_match_it = std::find_if( |
| available_surfaces_.begin(), available_surfaces_.end(), [&size](const auto& surface) { |
| auto surface_size = surface->GetSize(); |
| return surface->IsValid() && surface_size.width == size.width && |
| surface_size.height == size.height; |
| }); |
| if (exact_match_it != available_surfaces_.end()) { |
| FlutterEngineTraceEventInstant("Exact match found"); |
| surface = std::move(*exact_match_it); |
| available_surfaces_.erase(exact_match_it); |
| } else { |
| surface = CreateSurface(size); |
| } |
| |
| if (surface == nullptr) { |
| FX_LOG(ERROR, kLogTag, "Could not acquire surface."); |
| return nullptr; |
| } |
| |
| surface->ResetAge(); |
| return surface; |
| } |
| |
| void SoftwareSurfaceProducer::SubmitSurfaces( |
| std::vector<std::unique_ptr<SoftwareSurface>> surfaces) { |
| FlutterEngineTraceEventInstant("SoftwareSurfaceProducer::SubmitSurfaces"); |
| if (!IsValid()) { |
| FX_LOG(FATAL, kLogTag, "SubmitSurfaces called with invalid state."); |
| return; |
| } |
| |
| for (auto& surface : surfaces) { |
| SubmitSurface(std::move(surface)); |
| } |
| AgeAndCollectOldBuffers(); |
| } |
| |
| void SoftwareSurfaceProducer::SubmitSurface(std::unique_ptr<SoftwareSurface> surface) { |
| FlutterEngineTraceEventInstant("SoftwareSurfacePool::SubmitSurface"); |
| |
| if (surface == nullptr) { |
| FX_LOG(ERROR, kLogTag, "Tried to submit null surface."); |
| return; |
| } |
| |
| uintptr_t surface_key = reinterpret_cast<uintptr_t>(surface.get()); |
| auto insert_iterator = pending_surfaces_.insert(std::make_pair(surface_key, // key |
| std::move(surface) // value |
| )); |
| if (insert_iterator.second) { |
| insert_iterator.first->second->SignalWritesFinished( |
| std::bind(&SoftwareSurfaceProducer::RecyclePendingSurface, this, surface_key)); |
| } |
| } |
| |
| std::unique_ptr<SoftwareSurface> SoftwareSurfaceProducer::CreateSurface(const Size size) { |
| FlutterEngineTraceEventInstant("SoftwareSurfaceProducer::CreateSurface"); |
| auto surface = std::make_unique<SoftwareSurface>(sysmem_allocator_, flatland_allocator_, size); |
| if (!surface->IsValid()) { |
| FX_LOG(ERROR, kLogTag, "Created surface is invalid."); |
| return nullptr; |
| } |
| return surface; |
| } |
| |
| void SoftwareSurfaceProducer::RecycleSurface(std::unique_ptr<SoftwareSurface> surface) { |
| // The surface may have become invalid (for example if the fences could |
| // not be reset). |
| if (!surface->IsValid()) { |
| FX_LOG(ERROR, kLogTag, "Attempted to recycle invalid surface."); |
| return; |
| } |
| |
| FlutterEngineTraceEventInstant("SoftwareSurfacePool::RecycleSurface"); |
| // Recycle the buffer by putting it in the list of available surfaces if we |
| // have not reached the maximum amount of cached surfaces. |
| if (available_surfaces_.size() < kMaxSurfaces) { |
| available_surfaces_.push_back(std::move(surface)); |
| } else { |
| FlutterEngineTraceEventInstant("Too many surfaces in pool, dropping"); |
| } |
| |
| // TODO(akbiggs): Trace stats here for parity with GitHub code. |
| } |
| |
| void SoftwareSurfaceProducer::RecyclePendingSurface(uintptr_t surface_key) { |
| // Before we do anything, we must clear the surface from the collection of |
| // pending surfaces. |
| auto found_in_pending = pending_surfaces_.find(surface_key); |
| if (found_in_pending == pending_surfaces_.end()) { |
| FX_LOG(ERROR, kLogTag, "Attempted to recycle a surface that wasn't pending."); |
| return; |
| } |
| |
| // Grab a hold of the surface to recycle and clear the entry in the pending |
| // surfaces collection. |
| auto surface_to_recycle = std::move(found_in_pending->second); |
| pending_surfaces_.erase(found_in_pending); |
| |
| RecycleSurface(std::move(surface_to_recycle)); |
| } |
| |
| void SoftwareSurfaceProducer::AgeAndCollectOldBuffers() { |
| FlutterEngineTraceEventInstant("SoftwareSurfacePool::AgeAndCollectOldBuffers"); |
| |
| // Remove all surfaces that are no longer valid or are too old. |
| available_surfaces_.erase(std::remove_if(available_surfaces_.begin(), available_surfaces_.end(), |
| [&](auto& surface) { |
| return !surface->IsValid() || |
| surface->AdvanceAndGetAge() >= kMaxSurfaceAge; |
| }), |
| available_surfaces_.end()); |
| |
| // TODO(akbiggs): Trace the aged surfaces here for parity with |
| // GitHub code. |
| } |
| |
| bool SoftwareSurfaceProducer::IsValid() const { return valid_; } |
| |
| } // namespace embedder |