| // Copyright 2025 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef GPU_COMMAND_BUFFER_SERVICE_GPU_PERSISTENT_CACHE_H_ | |
| #define GPU_COMMAND_BUFFER_SERVICE_GPU_PERSISTENT_CACHE_H_ | |
| #include <atomic> | |
| #include <map> | |
| #include <memory> | |
| #include <string_view> | |
| #include "base/containers/flat_set.h" | |
| #include "base/containers/heap_array.h" | |
| #include "base/containers/span.h" | |
| #include "base/memory/ref_counted.h" | |
| #include "base/memory/scoped_refptr.h" | |
| #include "base/memory_coordinator/utils.h" | |
| #include "base/synchronization/atomic_flag.h" | |
| #include "base/synchronization/lock.h" | |
| #include "base/task/sequenced_task_runner.h" | |
| #include "base/thread_annotations.h" | |
| #include "base/trace_event/memory_dump_provider.h" | |
| #include "components/persistent_cache/buffer_provider.h" | |
| #include "components/persistent_cache/pending_backend.h" | |
| #include "gpu/command_buffer/common/shm_count.h" | |
| #include "gpu/gpu_gles2_export.h" | |
| #include "gpu/ipc/common/gpu_disk_cache_type.h" | |
| #include "skia/buildflags.h" | |
| #include "third_party/skia/include/gpu/ganesh/GrContextOptions.h" | |
| #include "ui/gl/buildflags.h" | |
| #if BUILDFLAG(USE_DAWN) || BUILDFLAG(SKIA_USE_DAWN) | |
| #include <dawn/platform/DawnPlatform.h> | |
| #endif | |
| namespace persistent_cache { | |
| class PersistentCache; | |
| } // namespace persistent_cache | |
| namespace gpu { | |
| class MemoryCache; | |
| // Wraps a persistent_cache::PersistentCache to be used as a Dawn, Skia or ANGLE | |
| // cache. Entries are always stored in a MemoryCache and PersistentCache as well | |
| // once it is initialized. Entries loaded before the PersistentCache is | |
| // initialized are copied into it on initialization. | |
| class GPU_GLES2_EXPORT GpuPersistentCache : | |
| #if BUILDFLAG(USE_DAWN) || BUILDFLAG(SKIA_USE_DAWN) | |
| public dawn::platform::CachingInterface, | |
| #endif | |
| public GrContextOptions::PersistentCache, | |
| public base::RefCountedThreadSafe<GpuPersistentCache> { | |
| public: | |
| struct GPU_GLES2_EXPORT AsyncDiskWriteOpts { | |
| AsyncDiskWriteOpts(); | |
| AsyncDiskWriteOpts(const AsyncDiskWriteOpts&); | |
| AsyncDiskWriteOpts(AsyncDiskWriteOpts&&); | |
| ~AsyncDiskWriteOpts(); | |
| AsyncDiskWriteOpts& operator=(const AsyncDiskWriteOpts&); | |
| AsyncDiskWriteOpts& operator=(AsyncDiskWriteOpts&&); | |
| // The task runner to use for asynchronous writes. If null, writes will be | |
| // synchronous. | |
| scoped_refptr<base::SequencedTaskRunner> task_runner; | |
| // The maximum number of bytes that can be pending for an asynchronous | |
| // write. If the pending bytes exceed this limit, the write will be | |
| // performed after the initial delay and will not be rescheduled even if the | |
| // cache is not idle. | |
| size_t max_pending_bytes_to_write = std::numeric_limits<size_t>::max(); | |
| }; | |
| struct GPU_GLES2_EXPORT MetadataOpts { | |
| MetadataOpts(); | |
| // Enable reading and writing of metadata about previous runs from the disk | |
| // cache. | |
| bool enabled = false; | |
| // Number of keys to write as the "first accessed" items in the cache. These | |
| // will be loaded first in subsequent runs. | |
| size_t preload_count = 50; | |
| }; | |
| // Metadata structure which stores information about cache usage. It is | |
| // written to the cache periodically and can be used to improve the | |
| // performance of future runs. | |
| // Public for unit testing only. | |
| struct GPU_GLES2_EXPORT CacheMetadata { | |
| CacheMetadata(); | |
| CacheMetadata(const CacheMetadata&); | |
| CacheMetadata(CacheMetadata&&); | |
| ~CacheMetadata(); | |
| CacheMetadata& operator=(const CacheMetadata&); | |
| CacheMetadata& operator=(CacheMetadata&&); | |
| base::HeapArray<uint8_t> Serialize() const; | |
| static CacheMetadata Deserialize(const base::HeapArray<uint8_t>& data); | |
| // The first N keys queried in the cache. | |
| // TODO(geofflang): Determine if it's faster to order the keys. If the | |
| // preloading of entries races with the queries, we want to load in the | |
| // order that the entries were queried. | |
| base::flat_set<std::string> first_loaded_keys; | |
| }; | |
| // If `async_write_options.task_runner` is null, then writes are synchronous. | |
| explicit GpuPersistentCache(std::string_view cache_prefix, | |
| scoped_refptr<MemoryCache> memory_cache, | |
| MetadataOpts metadata_options = {}, | |
| AsyncDiskWriteOpts async_write_options = {}); | |
| GpuPersistentCache(const GpuPersistentCache&) = delete; | |
| GpuPersistentCache& operator=(const GpuPersistentCache&) = delete; | |
| // This can only be called once but is thread safe w.r.t loads and stores. | |
| void InitializeCache(persistent_cache::PendingBackend pending_backend, | |
| scoped_refptr<RefCountedGpuProcessShmCount> | |
| use_shader_cache_shm_count = nullptr); | |
| #if BUILDFLAG(USE_DAWN) || BUILDFLAG(SKIA_USE_DAWN) | |
| size_t FindKey(std::string_view key); | |
| size_t LoadData(std::string_view key, base::span<uint8_t> dest); | |
| void StoreData(std::string_view key, base::span<const uint8_t> src); | |
| #endif | |
| // GrContextOptions::PersistentCache implementation. | |
| sk_sp<SkData> load(const SkData& key) override; | |
| void store(const SkData& key, const SkData& data) override; | |
| // OpenGL ES (GL_ANGLE_blob_cache) | |
| int64_t GLBlobCacheGet(const void* key, | |
| int64_t key_size, | |
| void* value_out, | |
| int64_t value_size); | |
| void GLBlobCacheSet(const void* key, | |
| int64_t key_size, | |
| const void* value, | |
| int64_t value_size); | |
| // Memory coordinator interface: | |
| // Triggers immediate eviction of cache entries down to `memory_limit`. | |
| void OnReleaseMemory(int memory_limit); | |
| // Updates the target cache size limit non-destructively without forcing | |
| // immediate eviction. | |
| void OnUpdateMemoryLimit(int memory_limit); | |
| void OnMemoryDump(const std::string& dump_name, | |
| base::trace_event::ProcessMemoryDump* pmd); | |
| const persistent_cache::PersistentCache& GetPersistentCacheForTesting() const; | |
| void FlushMetadataForTesting(); | |
| private: | |
| friend class base::RefCountedThreadSafe<GpuPersistentCache>; | |
| ~GpuPersistentCache() override; | |
| #if BUILDFLAG(USE_DAWN) || BUILDFLAG(SKIA_USE_DAWN) | |
| // dawn::platform::CachingInterface implementation. | |
| size_t FindKey(std::span<const std::byte> key) override; | |
| size_t LoadData(std::span<const std::byte> key, | |
| std::span<std::byte> dest) override; | |
| void StoreData(std::span<const std::byte> key, | |
| std::span<const std::byte> src) override; | |
| #endif | |
| struct DiskCache; | |
| // LINT.IfChange(CacheLoadResult) | |
| enum class CacheLoadResult { | |
| kMiss = 0, | |
| kMissNoDiskCache = 1, | |
| kMissTimeout = 2, | |
| kMissTransactionError = 3, | |
| kMaxMissValue = kMissTransactionError, | |
| // Extra enum space for future miss results | |
| kHitMemory = 10, | |
| kHitDisk = 11, | |
| kMaxValue = kHitDisk, | |
| }; | |
| // LINT.ThenChange(//tools/metrics/histograms/metadata/gpu/enums.xml:GpuPersistentCacheLoadResult) | |
| // LINT.IfChange(MetadataEvent) | |
| enum class MetadataEvent { | |
| kCacheInitialized = 0, | |
| kMetadataLoaded = 1, | |
| kMetadataFlushed = 2, | |
| kMaxValue = kMetadataFlushed, | |
| }; | |
| // LINT.ThenChange(//tools/metrics/histograms/metadata/gpu/enums.xml:GpuPersistentCacheMetadataEvent) | |
| static bool IsCacheHitResult(CacheLoadResult result); | |
| CacheLoadResult LoadImpl(std::string_view key, | |
| persistent_cache::BufferProvider buffer_provider); | |
| void StoreImpl(std::string_view key, | |
| base::span<const uint8_t> value, | |
| bool skip_memory_cache); | |
| void RecordCacheLoadResultHistogram(CacheLoadResult result); | |
| // Call after querying for a cache key to update the metadata of which entries | |
| // are loaded first. | |
| void UpdateCacheMetadataForLoad(std::string_view key); | |
| // Serialize and write the metadata to the disk cache. | |
| void FlushCacheMetadata(CacheMetadata metadata); | |
| // Attempt to load metadata from disk and process it, if it exists. | |
| void LoadAndProcessMetadataFromDiskCache(); | |
| // Process the metadata: | |
| // - Load first_loaded_keys from disk into the memory cache | |
| void ProcessMetadata(const CacheMetadata& metadata); | |
| // Prefix to prepend to UMA histogram's name. e.g GraphiteDawn, WebGPU | |
| const std::string cache_prefix_; | |
| std::atomic<size_t> load_count_ = 0; | |
| std::atomic<size_t> store_count_ = 0; | |
| // A MemoryCache is used for fast access to the most recently used elements of | |
| // the cache and allows data to be stored before the persistent_cache is | |
| // initialized | |
| scoped_refptr<MemoryCache> memory_cache_; | |
| base::AtomicFlag disk_cache_initialized_; | |
| scoped_refptr<DiskCache> disk_cache_; | |
| const AsyncDiskWriteOpts async_write_options_; | |
| const MetadataOpts metadata_options_; | |
| base::Lock metadata_mutex_; | |
| std::optional<CacheMetadata> cache_metadata_ GUARDED_BY(metadata_mutex_); | |
| }; | |
| void BindCacheToCurrentOpenGLContext(GpuPersistentCache* cache); | |
| void UnbindCacheFromCurrentOpenGLContext(); | |
| class GPU_GLES2_EXPORT GpuPersistentCacheCollection | |
| : public base::trace_event::MemoryDumpProvider { | |
| public: | |
| explicit GpuPersistentCacheCollection( | |
| size_t max_in_memory_cache_size, | |
| GpuPersistentCache::MetadataOpts metadata_options, | |
| GpuPersistentCache::AsyncDiskWriteOpts async_write_options); | |
| ~GpuPersistentCacheCollection() override; | |
| scoped_refptr<GpuPersistentCache> GetCache(const GpuDiskCacheHandle& handle); | |
| // Memory coordinator interface: | |
| // Triggers immediate eviction of cache entries down to `memory_limit`. | |
| void OnReleaseMemory(int memory_limit); | |
| // Updates the target cache size limit non-destructively without forcing | |
| // immediate eviction. | |
| void OnUpdateMemoryLimit(int memory_limit); | |
| bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, | |
| base::trace_event::ProcessMemoryDump* pmd) override; | |
| private: | |
| const size_t max_in_memory_cache_size_; | |
| const GpuPersistentCache::MetadataOpts metadata_options_; | |
| const GpuPersistentCache::AsyncDiskWriteOpts async_write_options_; | |
| base::Lock mutex_; | |
| std::map<GpuDiskCacheHandle, scoped_refptr<GpuPersistentCache>> caches_ | |
| GUARDED_BY(mutex_); | |
| int current_memory_limit_ GUARDED_BY(mutex_) = | |
| base::kNoMemoryPressureThreshold; | |
| }; | |
| } // namespace gpu | |
| #endif // GPU_COMMAND_BUFFER_SERVICE_GPU_PERSISTENT_CACHE_H_ |