blob: 478c31d56323d17b7df889f646e3d4f52e5324e7 [file] [log] [blame]
// Copyright 2018 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/service/gr_shader_cache.h"
#include <inttypes.h>
#include "base/base64.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "gpu/command_buffer/service/service_utils.h"
#include "gpu/config/gpu_finch_features.h"
#include "skia/ext/skia_utils_base.h"
#include "third_party/skia/include/gpu/ganesh/GrDirectContext.h"
namespace gpu {
namespace raster {
namespace {
// TODO(b/375264422): Temporary to debug potential shader cache entries
// mismatch.
BASE_FEATURE(kGrShaderCacheLoad, base::FEATURE_ENABLED_BY_DEFAULT);
std::string MakeString(const SkData* data) {
return std::string(static_cast<const char*>(data->data()), data->size());
}
sk_sp<SkData> MakeData(const std::string& str) {
return SkData::MakeWithCopy(str.c_str(), str.length());
}
} // namespace
GrShaderCache::GrShaderCache(size_t max_cache_size_bytes, Client* client)
: cache_size_limit_(max_cache_size_bytes),
store_(Store::NO_AUTO_EVICT),
client_(client) {
if (base::SingleThreadTaskRunner::HasCurrentDefault()) {
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
this, "GrShaderCache",
base::SingleThreadTaskRunner::GetCurrentDefault());
}
}
GrShaderCache::~GrShaderCache() {
base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
this);
}
sk_sp<SkData> GrShaderCache::load(const SkData& key) {
TRACE_EVENT0("gpu", "GrShaderCache::load");
// TODO(b/375264422): Temporary to debug potential shader cache entries
// mismatch.
if (!base::FeatureList::IsEnabled(kGrShaderCacheLoad)) {
return nullptr;
}
base::AutoLock auto_lock(lock_);
DCHECK_NE(current_client_id(), kInvalidClientId);
CacheKey cache_key(SkData::MakeWithoutCopy(key.data(), key.size()));
auto it = store_.Get(cache_key);
UMA_HISTOGRAM_BOOLEAN("Gpu.GrShaderCacheLoadHitInCache",
(it != store_.end()));
if (it == store_.end())
return nullptr;
if (it->second.prefetched_but_not_read) {
it->second.prefetched_but_not_read = false;
// Skia just loaded shader that was loaded from the disk. We assume it
// happens during VkGraphicsPipeline creation and might alter the pipeline
// cache. We'll store it to disk later when we're idle.
// Note, there is no reliable way to check if this is VkGraphicsPipeline
// entry or not, so we don't distinguish here and will try to store pipeline
// cache even if we just loaded it. It should happen only once per GrContext
// creation.
need_store_pipeline_cache_ = true;
}
WriteToDisk(it->first, &it->second);
return it->second.data;
}
void GrShaderCache::store(const SkData& key, const SkData& data) {
TRACE_EVENT0("gpu", "GrShaderCache::store");
base::AutoLock auto_lock(lock_);
DCHECK_NE(current_client_id(), kInvalidClientId);
CacheKey cache_key(SkData::MakeWithCopy(key.data(), key.size()));
if (data.size() > cache_size_limit_)
return;
EnforceLimits(data.size());
auto existing_it = store_.Get(cache_key);
if (existing_it != store_.end()) {
// Skia may ignore the cached entry and regenerate a shader if it fails to
// link, in which case replace the current version with the latest one.
EraseFromCache(existing_it);
}
CacheData cache_data(SkData::MakeWithCopy(data.data(), data.size()));
auto it = AddToCache(cache_key, std::move(cache_data));
WriteToDisk(it->first, &it->second);
// Skia just stored new shader, we assume it happens during VkGraphicsPipeline
// creation and might alter the pipeline cache. We'll store it to disk later
// when we're idle.
need_store_pipeline_cache_ = true;
}
void GrShaderCache::PopulateCache(const std::string& key,
const std::string& data) {
TRACE_EVENT0("gpu", "GrShaderCache::PopulateCache");
base::AutoLock auto_lock(lock_);
std::string decoded_key;
base::Base64Decode(key, &decoded_key);
CacheKey cache_key(MakeData(decoded_key));
if (data.length() > cache_size_limit_) {
return;
}
EnforceLimits(data.size());
// If we already have this in the cache, skia may have stored it before it
// was loaded off the disk cache. Its better to keep the latest version
// generated version than overwriting it here.
if (store_.Get(cache_key) != store_.end()) {
return;
}
CacheData cache_data(MakeData(data));
auto it = AddToCache(cache_key, std::move(cache_data));
// This was loaded off the disk cache, no need to push this back for disk
// write.
it->second.pending_disk_write = false;
it->second.prefetched_but_not_read = true;
}
GrShaderCache::Store::iterator GrShaderCache::AddToCache(CacheKey key,
CacheData data) {
lock_.AssertAcquired();
auto it = store_.Put(key, std::move(data));
curr_size_bytes_ += it->second.data->size();
return it;
}
template <typename Iterator>
void GrShaderCache::EraseFromCache(Iterator it) {
lock_.AssertAcquired();
DCHECK_GE(curr_size_bytes_, it->second.data->size());
curr_size_bytes_ -= it->second.data->size();
store_.Erase(it);
}
void GrShaderCache::CacheClientIdOnDisk(int32_t client_id) {
base::AutoLock auto_lock(lock_);
client_ids_to_cache_on_disk_.insert(client_id);
}
void GrShaderCache::PurgeMemory(
base::MemoryPressureLevel memory_pressure_level) {
base::AutoLock auto_lock(lock_);
size_t original_limit = cache_size_limit_;
cache_size_limit_ = gpu::UpdateShaderCacheSizeOnMemoryPressure(
cache_size_limit_, memory_pressure_level);
EnforceLimits(0u);
cache_size_limit_ = original_limit;
}
bool GrShaderCache::OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) {
base::AutoLock auto_lock(lock_);
using base::trace_event::MemoryAllocatorDump;
std::string dump_name =
base::StringPrintf("gpu/shader_cache/gr_shader_cache/cache_0x%" PRIXPTR,
reinterpret_cast<uintptr_t>(this));
MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(dump_name);
dump->AddScalar(MemoryAllocatorDump::kNameSize,
MemoryAllocatorDump::kUnitsBytes, curr_size_bytes_);
dump->AddScalar(MemoryAllocatorDump::kNameObjectCount,
MemoryAllocatorDump::kUnitsObjects, store_.size());
return true;
}
size_t GrShaderCache::num_cache_entries() const {
base::AutoLock auto_lock(lock_);
return store_.size();
}
size_t GrShaderCache::curr_size_bytes_for_testing() const {
base::AutoLock auto_lock(lock_);
return curr_size_bytes_;
}
void GrShaderCache::WriteToDisk(const CacheKey& key, CacheData* data) {
lock_.AssertAcquired();
DCHECK_NE(current_client_id(), kInvalidClientId);
if (!data->pending_disk_write)
return;
// Only cache the shader on disk if this client id is permitted.
if (client_ids_to_cache_on_disk_.count(current_client_id()) == 0)
return;
data->pending_disk_write = false;
std::string encoded_key = base::Base64Encode(MakeString(key.data.get()));
client_->StoreShader(encoded_key, MakeString(data->data.get()));
}
void GrShaderCache::EnforceLimits(size_t size_needed) {
lock_.AssertAcquired();
DCHECK_LE(size_needed, cache_size_limit_);
while (size_needed + curr_size_bytes_ > cache_size_limit_)
EraseFromCache(store_.rbegin());
}
void GrShaderCache::StoreVkPipelineCacheIfNeeded(GrDirectContext* gr_context) {
// This method must be called only by one gpu thread which is gpu main
// thread. Calling it from multiple gpu threads and hence multiple context is
// redundant and expensive since each GrContext will have same key. Hence
// adding a DCHECK here.
DCHECK_CALLED_ON_VALID_THREAD(gpu_main_thread_checker_);
bool need_store_pipeline_cache = false;
{
base::AutoLock auto_lock(lock_);
need_store_pipeline_cache = need_store_pipeline_cache_;
}
if (need_store_pipeline_cache) {
gr_context->storeVkPipelineCacheData();
{
base::AutoLock auto_lock(lock_);
need_store_pipeline_cache_ = false;
}
}
}
int32_t GrShaderCache::current_client_id() const {
lock_.AssertAcquired();
auto it = current_client_id_.find(base::PlatformThread::CurrentId());
if (it != current_client_id_.end())
return it->second;
return kInvalidClientId;
}
GrShaderCache::ScopedCacheUse::ScopedCacheUse(GrShaderCache* cache,
int32_t client_id)
: cache_(cache) {
base::AutoLock auto_lock(cache_->lock_);
DCHECK_EQ(cache_->current_client_id(), kInvalidClientId);
DCHECK_NE(client_id, kInvalidClientId);
cache_->current_client_id_[base::PlatformThread::CurrentId()] = client_id;
}
GrShaderCache::ScopedCacheUse::~ScopedCacheUse() {
base::AutoLock auto_lock(cache_->lock_);
cache_->current_client_id_.erase(base::PlatformThread::CurrentId());
}
GrShaderCache::CacheKey::CacheKey(sk_sp<SkData> data) : data(std::move(data)) {
hash = base::FastHash(skia::as_byte_span(*this->data));
}
GrShaderCache::CacheKey::CacheKey(const CacheKey& other) = default;
GrShaderCache::CacheKey::CacheKey(CacheKey&& other) = default;
GrShaderCache::CacheKey& GrShaderCache::CacheKey::operator=(
const CacheKey& other) = default;
GrShaderCache::CacheKey& GrShaderCache::CacheKey::operator=(CacheKey&& other) =
default;
GrShaderCache::CacheKey::~CacheKey() = default;
bool GrShaderCache::CacheKey::operator==(const CacheKey& other) const {
return data->equals(other.data.get());
}
GrShaderCache::CacheData::CacheData(sk_sp<SkData> data)
: data(std::move(data)) {}
GrShaderCache::CacheData::CacheData(CacheData&& other) = default;
GrShaderCache::CacheData& GrShaderCache::CacheData::operator=(
CacheData&& other) = default;
GrShaderCache::CacheData::~CacheData() = default;
bool GrShaderCache::CacheData::operator==(const CacheData& other) const {
return data->equals(other.data.get());
}
} // namespace raster
} // namespace gpu