| // Copyright 2024 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "gpu/command_buffer/client/shared_image_pool.h" | |
| #include <inttypes.h> | |
| #include "base/strings/stringprintf.h" | |
| #include "base/trace_event/process_memory_dump.h" | |
| #include "gpu/command_buffer/client/shared_image_interface.h" | |
| namespace { | |
| gpu::ImageInfo GetImageInfo(scoped_refptr<gpu::ClientImage> image) { | |
| auto shared_image = image->GetSharedImage(); | |
| return gpu::ImageInfo( | |
| shared_image->size(), shared_image->format(), shared_image->usage(), | |
| shared_image->color_space(), shared_image->surface_origin(), | |
| shared_image->alpha_type(), shared_image->buffer_usage(), | |
| shared_image->is_software()); | |
| } | |
| } // namespace | |
| namespace gpu { | |
| // Implementation of the ClientImage class. | |
| ClientImage::ClientImage(scoped_refptr<ClientSharedImage> shared_image) | |
| : shared_image_(std::move(shared_image)) { | |
| CHECK(shared_image_); | |
| sync_token_ = shared_image_->creation_sync_token(); | |
| } | |
| ClientImage::~ClientImage() { | |
| CHECK(shared_image_); | |
| if (!subclass_manages_destruction_sync_token_) { | |
| shared_image_->UpdateDestructionSyncToken(std::move(sync_token_)); | |
| } | |
| } | |
| const scoped_refptr<ClientSharedImage>& ClientImage::GetSharedImage() const { | |
| return shared_image_; | |
| } | |
| const SyncToken& ClientImage::GetSyncToken() const { | |
| return sync_token_; | |
| } | |
| void ClientImage::SetReleaseSyncToken(SyncToken release_sync_token) { | |
| sync_token_ = std::move(release_sync_token); | |
| } | |
| const SharedImagePoolId& ClientImage::GetPoolIdForTesting() const { | |
| return pool_id_; | |
| } | |
| void ClientImage::OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd, | |
| const std::string& parent_path) const { | |
| CHECK(shared_image_); | |
| CHECK(!parent_path.empty()); | |
| std::string dump_name = base::StringPrintf( | |
| "%s/shared_image_pool_%s/client_image_0x%" PRIXPTR, parent_path, | |
| pool_id_.ToString(), reinterpret_cast<uintptr_t>(this)); | |
| auto* dump = pmd->CreateAllocatorDump(dump_name); | |
| size_t memory_size = | |
| shared_image_->format().EstimatedSizeInBytes(shared_image_->size()); | |
| dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize, | |
| base::trace_event::MemoryAllocatorDump::kUnitsBytes, | |
| memory_size); | |
| shared_image_->OnMemoryDump( | |
| pmd, dump->guid(), | |
| static_cast<int>(gpu::TracingImportance::kClientOwner)); | |
| } | |
| SharedImagePoolBase::SharedImagePoolBase( | |
| const SharedImagePoolId& pool_id, | |
| const ImageInfo& image_info, | |
| std::string_view debug_label, | |
| const scoped_refptr<SharedImageInterface> sii, | |
| std::optional<uint8_t> max_pool_size, | |
| std::optional<base::TimeDelta> unused_resource_expiration_time) | |
| : pool_id_(pool_id), | |
| image_info_(image_info), | |
| debug_label_(debug_label), | |
| sii_(std::move(sii)), | |
| max_pool_size_(std::move(max_pool_size)), | |
| unused_resource_expiration_time_( | |
| std::move(unused_resource_expiration_time)) {} | |
| SharedImagePoolBase::~SharedImagePoolBase() { | |
| ClearInternal(); | |
| } | |
| size_t SharedImagePoolBase::GetPoolSizeForTesting() const { | |
| return image_pool_.size(); | |
| } | |
| bool SharedImagePoolBase::IsReclaimTimerRunningForTesting() const { | |
| return unused_resources_reclaim_timer_.IsRunning(); | |
| } | |
| scoped_refptr<ClientSharedImage> | |
| SharedImagePoolBase::CreateSharedImageInternal() { | |
| CHECK(sii_); | |
| if (image_info_.is_software) { | |
| return sii_->CreateSharedImageForSoftwareCompositor( | |
| {image_info_.format, image_info_.size, image_info_.color_space, | |
| image_info_.surface_origin, image_info_.alpha_type, image_info_.usage, | |
| debug_label_ + "Software"}); | |
| } else if (image_info_.buffer_usage.has_value()) { | |
| // Creates a Mappable shared image. Note that eventually when shared image | |
| // usage is merged with buffer usage, there will be only one method to | |
| // create both mappable and non-mappable shared image. These 2 paths will be | |
| // merged after that. | |
| return sii_->CreateSharedImage( | |
| {image_info_.format, image_info_.size, image_info_.color_space, | |
| image_info_.surface_origin, image_info_.alpha_type, image_info_.usage, | |
| debug_label_ + "Mappable"}, | |
| gpu::kNullSurfaceHandle, image_info_.buffer_usage.value()); | |
| } else { | |
| return sii_->CreateSharedImage( | |
| {image_info_.format, image_info_.size, image_info_.color_space, | |
| image_info_.surface_origin, image_info_.alpha_type, image_info_.usage, | |
| debug_label_}, | |
| gpu::kNullSurfaceHandle); | |
| } | |
| } | |
| scoped_refptr<ClientImage> SharedImagePoolBase::GetImageFromPoolInternal() { | |
| if (!image_pool_.empty()) { | |
| auto image = image_pool_.back(); | |
| image_pool_.pop_back(); | |
| return image; | |
| } | |
| return nullptr; | |
| } | |
| void SharedImagePoolBase::ReleaseImageInternal( | |
| scoped_refptr<ClientImage> image) { | |
| if (!image || (GetImageInfo(image) != image_info_)) { | |
| return; | |
| } | |
| // Ensure that the |image| belongs to |this| pool. | |
| CHECK_EQ(image->pool_id_.ToString(), pool_id_.ToString()); | |
| // Ensure that there is only one reference which the current |image| and | |
| // clients are not accidentally keeping more references alive while releasing | |
| // this |image|. | |
| CHECK(image->HasOneRef()); | |
| // Recycle the image into the pool only if the pool is not full or if | |
| // |max_pool_size_| is not specified. Otherwise the last ref of |image| here | |
| // will get destroyed automatically. | |
| if (!max_pool_size_.has_value() || | |
| image_pool_.size() < max_pool_size_.value()) { | |
| // Update the last used time. | |
| image->last_used_time_ = base::TimeTicks::Now(); | |
| image_pool_.push_back(std::move(image)); | |
| MaybePostUnusedResourcesReclaimTask(); | |
| } | |
| } | |
| void SharedImagePoolBase::ClearInternal() { | |
| image_pool_.clear(); | |
| CHECK(sii_); | |
| // ClientSharedImage destructor calls DestroySharedImage which in turn ensures | |
| // that the deferred destroy request is flushed. Thus, clients don't need to | |
| // call SharedImageInterface::Flush explicitly. | |
| } | |
| void SharedImagePoolBase::ReconfigureInternal(const ImageInfo& image_info) { | |
| if (image_info_ == image_info) { | |
| return; | |
| } | |
| // If ImageInfo does not matches, we clear the existing images. | |
| ClearInternal(); | |
| image_info_ = image_info; | |
| } | |
| void SharedImagePoolBase::MaybePostUnusedResourcesReclaimTask() { | |
| if (!unused_resources_reclaim_timer_.IsRunning() && !image_pool_.empty() && | |
| unused_resource_expiration_time_.has_value()) { | |
| unused_resources_reclaim_timer_.Start( | |
| FROM_HERE, unused_resource_expiration_time_.value(), | |
| base::BindOnce(&SharedImagePoolBase::ClearOldUnusedResources, | |
| base::Unretained(this))); | |
| } | |
| } | |
| void SharedImagePoolBase::ClearOldUnusedResources() { | |
| CHECK(unused_resource_expiration_time_.has_value()); | |
| // Get the current time. | |
| auto now = base::TimeTicks::Now(); | |
| // Clear the resources that have expired. | |
| // Remove elements that satisfy the predicate by using std::remove_if and | |
| // erase. | |
| auto new_end = | |
| std::remove_if(image_pool_.begin(), image_pool_.end(), | |
| [this, now](const scoped_refptr<ClientImage>& resource) { | |
| return now - resource->last_used_time_ >= | |
| unused_resource_expiration_time_.value(); | |
| }); | |
| // Erase the "removed" elements from the vector. | |
| image_pool_.erase(new_end, image_pool_.end()); | |
| // ClientSharedImage destructor calls DestroySharedImage which in turn ensures | |
| // that the deferred destroy request is flushed. Thus, clients don't need to | |
| // call SharedImageInterface::Flush explicitly. | |
| // Reclaim unused resource again. | |
| MaybePostUnusedResourcesReclaimTask(); | |
| } | |
| } // namespace gpu |