blob: 81fcfdf7125f3e05d1b73812d4a398889320a3f6 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/graphics/gpu/webgpu_resource_provider_cache.h"
#include "base/containers/adapters.h"
#include "base/metrics/histogram_functions.h"
#include "base/time/time.h"
#include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink {
ResourceCacheKey::ResourceCacheKey(const SkImageInfo& info,
bool is_origin_top_left)
: info(info), is_origin_top_left(is_origin_top_left) {}
bool ResourceCacheKey::operator==(const ResourceCacheKey& other) const {
return (info == other.info && is_origin_top_left == other.is_origin_top_left);
}
bool ResourceCacheKey::operator!=(const ResourceCacheKey& other) const {
return !(*this == other);
}
RecyclableCanvasResource::RecyclableCanvasResource(
std::unique_ptr<CanvasResourceProvider> resource_provider,
const ResourceCacheKey& cache_key,
base::WeakPtr<WebGPURecyclableResourceCache> cache)
: resource_provider_(std::move(resource_provider)),
cache_key_(cache_key),
cache_(cache) {}
RecyclableCanvasResource::~RecyclableCanvasResource() {
if (!resource_provider_)
return;
// If the cache key is converted to a different value in
// CanvasResourceProvider creation, it will cause cache miss, such as
// kBGRA_8888_SkColorType to kRGBA_8888_SkColorType.
// TODO(magchen@):Remove the DCHECKs if we must create CanvasResourceProvider
// with unsupported parameters and if it's fine to lose the cache. Or, we can
// save the cache key in |unused_providers_| and only compare the saved cache
// key instead of the one in CanvasResourceProvider.
DCHECK(cache_key_.info == resource_provider_->GetSkImageInfo());
DCHECK(cache_key_.is_origin_top_left ==
resource_provider_->IsOriginTopLeft());
if (cache_) {
cache_->OnDestroyRecyclableResource(std::move(resource_provider_));
}
}
WebGPURecyclableResourceCache::WebGPURecyclableResourceCache(
base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider,
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: context_provider_(std::move(context_provider)),
task_runner_(std::move(task_runner)) {
weak_ptr_ = weak_ptr_factory_.GetWeakPtr();
timer_func_ = WTF::BindRepeating(
&WebGPURecyclableResourceCache::ReleaseStaleResources, weak_ptr_);
DCHECK_LE(kTimerDurationInSeconds, kCleanUpDelayInSeconds);
}
std::unique_ptr<RecyclableCanvasResource>
WebGPURecyclableResourceCache::GetOrCreateCanvasResource(
const SkImageInfo& info,
bool is_origin_top_left) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
const ResourceCacheKey cache_key(info, is_origin_top_left);
std::unique_ptr<CanvasResourceProvider> provider =
AcquireCachedProvider(cache_key);
if (!provider) {
provider = CanvasResourceProvider::CreateWebGPUImageProvider(
info, is_origin_top_left);
if (!provider)
return nullptr;
}
return std::make_unique<RecyclableCanvasResource>(std::move(provider),
cache_key, weak_ptr_);
}
void WebGPURecyclableResourceCache::OnDestroyRecyclableResource(
std::unique_ptr<CanvasResourceProvider> resource_provider) {
int resource_size = resource_provider->Size().width() *
resource_provider->Size().height() *
resource_provider->GetSkImageInfo().bytesPerPixel();
if (context_provider_) {
total_unused_resources_in_bytes_ += resource_size;
// WaitSyncToken on the canvas resource.
gpu::SyncToken finished_access_token;
auto* webgpu = context_provider_->ContextProvider()->WebGPUInterface();
webgpu->GenUnverifiedSyncTokenCHROMIUM(finished_access_token.GetData());
resource_provider->OnDestroyRecyclableCanvasResource(finished_access_token);
unused_providers_.push_front(Resource(std::move(resource_provider),
current_timer_id_, resource_size));
}
if (last_seen_max_unused_resources_in_bytes_ <
total_unused_resources_in_bytes_) {
last_seen_max_unused_resources_in_bytes_ = total_unused_resources_in_bytes_;
}
if (last_seen_max_unused_resources_ < unused_providers_.size()) {
last_seen_max_unused_resources_ = unused_providers_.size();
}
// If the cache is full, release LRU from the back.
while (total_unused_resources_in_bytes_ >
kMaxRecyclableResourceCachesInBytes) {
total_unused_resources_in_bytes_ -= unused_providers_.back().resource_size_;
unused_providers_.pop_back();
}
StartResourceCleanUpTimer();
}
WebGPURecyclableResourceCache::Resource::Resource(
std::unique_ptr<CanvasResourceProvider> resource_provider,
unsigned int timer_id,
int resource_size)
: resource_provider_(std::move(resource_provider)),
timer_id_(timer_id),
resource_size_(resource_size) {}
WebGPURecyclableResourceCache::Resource::Resource(Resource&& that) noexcept =
default;
WebGPURecyclableResourceCache::Resource::~Resource() = default;
std::unique_ptr<CanvasResourceProvider>
WebGPURecyclableResourceCache::AcquireCachedProvider(
const ResourceCacheKey& cache_key) {
// Loop from MRU to LRU
DequeResourceProvider::iterator it;
for (it = unused_providers_.begin(); it != unused_providers_.end(); ++it) {
CanvasResourceProvider* resource_provider = it->resource_provider_.get();
const auto it_cache_key =
ResourceCacheKey(resource_provider->GetSkImageInfo(),
resource_provider->IsOriginTopLeft());
if (cache_key == it_cache_key) {
break;
}
}
// Found one.
if (it != unused_providers_.end()) {
std::unique_ptr<CanvasResourceProvider> provider =
(std::move(it->resource_provider_));
total_unused_resources_in_bytes_ -= it->resource_size_;
// TODO(magchen@): If the cache capacity increases a lot, will erase(it)
// becomes inefficient?
// Remove the provider from the |unused_providers_|.
unused_providers_.erase(it);
provider->OnAcquireRecyclableCanvasResource();
return provider;
}
return nullptr;
}
void WebGPURecyclableResourceCache::ReleaseStaleResources() {
timer_is_running_ = false;
// Loop from LRU to MRU
int stale_resource_count = 0;
for (const auto& unused_provider : base::Reversed(unused_providers_)) {
if ((current_timer_id_ - unused_provider.timer_id_) <
kTimerIdDeltaForDeletion) {
// These are the resources which are recycled and stay in the cache for
// less than kCleanUpDelayInSeconds. They are not to be deleted this time.
break;
}
stale_resource_count++;
}
// Delete all stale resources.
for (int i = 0; i < stale_resource_count; ++i) {
total_unused_resources_in_bytes_ -= unused_providers_.back().resource_size_;
unused_providers_.pop_back();
}
// The number of stale Resources released this time in this function.
base::UmaHistogramCustomCounts("Blink.Canvas.WebGPUStaleResourceCount",
stale_resource_count, /*min=*/0,
/*max=*/300,
/*buckets=*/50);
// The maximum number of total resource memory size between two
// ReleaseStaleResources() calls.
// UmaHistogramCustomCounts only takes the int type as input.
int last_seen_max_unused_resources_in_kb =
static_cast<int>(last_seen_max_unused_resources_in_bytes_ / 1024);
base::UmaHistogramCustomCounts("Blink.Canvas.WebGPUMaxRecycledResourcesInKB",
last_seen_max_unused_resources_in_kb,
/*min=*/0,
/*max=*/kMaxRecyclableResourceCachesInKB,
/*buckets=*/50);
last_seen_max_unused_resources_in_bytes_ = 0;
// The maximum number of unused resources between two ReleaseStaleResources()
// calls.
base::UmaHistogramCustomCounts("Blink.Canvas.WebGPUMaxRecycledResourcesCount",
last_seen_max_unused_resources_,
/*min=*/0,
/*max=*/300,
/*buckets=*/50);
last_seen_max_unused_resources_ = 0;
current_timer_id_++;
StartResourceCleanUpTimer();
}
void WebGPURecyclableResourceCache::StartResourceCleanUpTimer() {
if (unused_providers_.size() > 0 && !timer_is_running_) {
task_runner_->PostDelayedTask(FROM_HERE, timer_func_,
base::Seconds(kTimerDurationInSeconds));
timer_is_running_ = true;
}
}
wtf_size_t
WebGPURecyclableResourceCache::CleanUpResourcesAndReturnSizeForTesting() {
ReleaseStaleResources();
return unused_providers_.size();
}
} // namespace blink