// 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.

#include "gpu/command_buffer/service/gpu_persistent_cache.h"

#include <optional>
#include <string_view>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/dcheck_is_on.h"
#include "base/functional/bind.h"
#include "base/functional/function_ref.h"
#include "base/immediate_crash.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_functions.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/task_runner.h"
#include "base/thread_annotations.h"
#include "base/threading/thread_restrictions.h"
#include "base/timer/elapsed_timer.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_request_args.h"
#include "base/trace_event/trace_event.h"
#include "base/types/expected_macros.h"
#include "components/persistent_cache/client.h"
#include "components/persistent_cache/persistent_cache.h"
#include "components/persistent_cache/transaction_error.h"
#include "components/version_info/version_info.h"
#include "gpu/command_buffer/service/memory_cache.h"
#include "ipc/common/gpu_client_ids.h"
#include "ui/gl/gl_bindings.h"

namespace gpu {

namespace {

constexpr size_t kMaxLoadStoreForTrackingCacheAvailable = 100;
constexpr double kRecordCacheEntrySizeFrequency = 0.01;
constexpr base::TimeDelta kDiskWriteDelaySeconds = base::Seconds(1);
constexpr base::TimeDelta kDiskOpWaitTimeoutMs = base::Milliseconds(20);

class ScopedHistogramTimer {
 public:
  explicit ScopedHistogramTimer(const std::string& name) : name_(name) {}
  ~ScopedHistogramTimer() {
    if (enabled_) {
      base::UmaHistogramCustomMicrosecondsTimes(name_, timer_.Elapsed(),
                                                base::Microseconds(1),
                                                base::Seconds(30), 100);
    }
  }

  void SetEnabled(bool enabled) { enabled_ = enabled; }

 private:
  const std::string name_;
  base::ElapsedTimer timer_;
  bool enabled_ = true;
};

class DiskCacheTraceScope {
 public:
  explicit DiskCacheTraceScope(perfetto::StaticString name) {
    TRACE_EVENT_BEGIN("gpu", name);
  }
  ~DiskCacheTraceScope() {
    if (pending_bytes_) {
      TRACE_EVENT_END("gpu", "idle_id", idle_id_, "pending_bytes",
                      pending_bytes_);
    } else {
      TRACE_EVENT_END("gpu", "idle_id", idle_id_);
    }
  }

  void SetIdleId(uint64_t idle_id) { idle_id_ = idle_id; }
  void SetPendingBytes(size_t pending_bytes) { pending_bytes_ = pending_bytes; }

 private:
  uint64_t idle_id_ = 0;
  std::optional<size_t> pending_bytes_;
};

GLsizeiptr GL_APIENTRY GLBlobCacheGetCallback(const void* key,
                                              GLsizeiptr key_size,
                                              void* value_out,
                                              GLsizeiptr value_size,
                                              const void* user_param) {
  DCHECK(user_param != nullptr);
  GpuPersistentCache* cache =
      static_cast<GpuPersistentCache*>(const_cast<void*>(user_param));

  return cache->GLBlobCacheGet(key, key_size, value_out, value_size);
}

void GL_APIENTRY GLBlobCacheSetCallback(const void* key,
                                        GLsizeiptr key_size,
                                        const void* value,
                                        GLsizeiptr value_size,
                                        const void* user_param) {
  DCHECK(user_param != nullptr);
  GpuPersistentCache* cache =
      static_cast<GpuPersistentCache*>(const_cast<void*>(user_param));

  cache->GLBlobCacheSet(key, key_size, value, value_size);
}

// Cache prefix name used in all histograms, eg:
// GPU.PersistentCache.{CachePrefix}.MetricName
// Do not modify without changing
// tools/metrics/histograms/metadata/gpu/histograms.xml
const char* GetCacheHistogramPrefix(GpuDiskCacheHandle handle) {
  switch (GetHandleType(handle)) {
    case GpuDiskCacheType::kGlShaders:
      return IsReservedGpuDiskCacheHandle(handle) ? "Ganesh" : "WebGL";
    case GpuDiskCacheType::kDawnWebGPU:
      return "WebGPU";
    case GpuDiskCacheType::kDawnGraphite:
      return "GraphiteDawn";
    default:
      NOTREACHED();
  }
}

std::string GetHistogramName(std::string_view prefix, std::string_view metric) {
  return "GPU.PersistentCache." + std::string(prefix) + "." +
         std::string(metric);
}

NOINLINE NOOPT void HandlePersistentCacheError(
    GpuProcessShmCount* use_shader_cache_shm_count,
    persistent_cache::TransactionError error) {
  LOG(ERROR) << "Persistent cache error: " << static_cast<int>(error);
  switch (error) {
    case persistent_cache::TransactionError::kPermanent:
      if (use_shader_cache_shm_count) {
        GpuProcessShmCount::ScopedIncrement scoped_increment(
            use_shader_cache_shm_count);
        base::ImmediateCrash();
      }
      break;
    default:
      break;
  }
}

bool TimedWait(base::ConditionVariable& cond_var,
               base::TimeDelta timeout,
               base::FunctionRef<bool()> wait_condition) {
  base::TimeTicks deadline = base::TimeTicks::Now() + timeout;
  while (wait_condition()) {
    base::TimeDelta remaining = deadline - base::TimeTicks::Now();
    if (!remaining.is_positive()) {
      return false;  // Timeout
    }
    cond_var.TimedWait(remaining);
  }
  return true;
}

#if BUILDFLAG(USE_DAWN) || BUILDFLAG(SKIA_USE_DAWN)
bool IsVkPipelineCache(std::string_view key_str) {
  // Dawn/Vulkan appends a suffix to the cache key intentionally.
  return key_str.ends_with("MonolithicVkPipelineCache");
}
#endif

std::string_view GetCacheMetadataKey() {
  return version_info::GetProductNameAndVersionForUserAgent();
}
}  // namespace

// AsyncDiskWriteOpts
GpuPersistentCache::AsyncDiskWriteOpts::AsyncDiskWriteOpts() = default;
GpuPersistentCache::AsyncDiskWriteOpts::AsyncDiskWriteOpts(
    const AsyncDiskWriteOpts&) = default;
GpuPersistentCache::AsyncDiskWriteOpts::AsyncDiskWriteOpts(
    AsyncDiskWriteOpts&&) = default;
GpuPersistentCache::AsyncDiskWriteOpts::~AsyncDiskWriteOpts() = default;
GpuPersistentCache::AsyncDiskWriteOpts&
GpuPersistentCache::AsyncDiskWriteOpts::operator=(const AsyncDiskWriteOpts&) =
    default;
GpuPersistentCache::AsyncDiskWriteOpts&
GpuPersistentCache::AsyncDiskWriteOpts::operator=(AsyncDiskWriteOpts&&) =
    default;

// MetadataOpts
GpuPersistentCache::MetadataOpts::MetadataOpts() = default;

// Ref-counted wrapper for the persistent cache data, so it can be used safely
// with asynchronous operations.
struct GpuPersistentCache::DiskCache
    : public base::RefCountedThreadSafe<DiskCache> {
  explicit DiskCache(
      std::string_view cache_prefix,
      std::unique_ptr<persistent_cache::PersistentCache> cache,
      const GpuPersistentCache::AsyncDiskWriteOpts& async_write_options,
      scoped_refptr<RefCountedGpuProcessShmCount> use_shader_cache_shm_count);

  GpuPersistentCache::CacheLoadResult Load(
      std::string_view key,
      persistent_cache::BufferProvider buffer_provider);
  void Store(scoped_refptr<MemoryCacheEntry> entry);

  const persistent_cache::PersistentCache& persistent_cache() const {
    return *cache_;
  }

 private:
  friend class base::RefCountedThreadSafe<DiskCache>;
  ~DiskCache();

  void SignalUsingCacheComplete();
  void DoStoreToDisk(scoped_refptr<MemoryCacheEntry> entry);
  void DoDelayedStoreToDisk(scoped_refptr<MemoryCacheEntry> entry,
                            uint64_t idle_id);

  const std::string cache_prefix_;
  const std::unique_ptr<persistent_cache::PersistentCache> cache_;
  // Used to track cache activity to delay writes until idle. Relaxed memory
  // order is sufficient because we only need to detect if any change has
  // occurred, and the variable doesn't need to synchronize with other
  // variables.
  std::atomic<uint64_t> current_idle_id_{0};
  // Used to track the total bytes of pending writes. Relaxed memory order is
  // sufficient as this is used as a heuristic and strict synchronization is not
  // required.
  std::atomic<size_t> pending_bytes_to_write_{0};
  const scoped_refptr<base::SequencedTaskRunner> disk_write_task_runner_;
  const size_t max_pending_bytes_to_write_;

  const scoped_refptr<RefCountedGpuProcessShmCount> use_shader_cache_shm_count_;

  // Synchronization primitives to enforce timed waits between reads & writes.
  base::Lock cache_in_use_mutex_;
  base::ConditionVariable cache_in_use_cond_var_{&cache_in_use_mutex_};
  bool cache_in_use_ GUARDED_BY(cache_in_use_mutex_) = false;
};

GpuPersistentCache::DiskCache::DiskCache(
    std::string_view cache_prefix,
    std::unique_ptr<persistent_cache::PersistentCache> cache,
    const GpuPersistentCache::AsyncDiskWriteOpts& async_write_options,
    scoped_refptr<RefCountedGpuProcessShmCount> use_shader_cache_shm_count)
    : cache_prefix_(cache_prefix),
      cache_(std::move(cache)),
      disk_write_task_runner_(async_write_options.task_runner),
      max_pending_bytes_to_write_(
          async_write_options.max_pending_bytes_to_write),
      use_shader_cache_shm_count_(std::move(use_shader_cache_shm_count)) {}

GpuPersistentCache::DiskCache::~DiskCache() = default;

void GpuPersistentCache::DiskCache::SignalUsingCacheComplete() {
  {
    base::AutoLock lock(cache_in_use_mutex_);
    DCHECK(cache_in_use_);
    cache_in_use_ = false;
  }

  cache_in_use_cond_var_.Signal();
}

GpuPersistentCache::CacheLoadResult GpuPersistentCache::DiskCache::Load(
    std::string_view key,
    persistent_cache::BufferProvider buffer_provider) {
  ScopedHistogramTimer timer(GetHistogramName(cache_prefix_, "Load"));
  DiskCacheTraceScope trace_scope("GpuPersistentCache::DiskCache::Load");

  const uint64_t idle_id =
      current_idle_id_.fetch_add(1, std::memory_order_relaxed) + 1;
  trace_scope.SetIdleId(idle_id);

  // The persistent cache backend can't read and write in parallel. Wait for
  // any pending writes/reads to complete before loading from the cache, to
  // avoid long waits in the backend. We wait for a maximum of 10ms.
  {
    base::ScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
    base::AutoLock lock(cache_in_use_mutex_);
    if (!TimedWait(cache_in_use_cond_var_, kDiskOpWaitTimeoutMs,
                   [this]() { return cache_in_use_; })) {
      // Treat as cache miss
      return CacheLoadResult::kMissTimeout;
    }

    cache_in_use_ = true;
  }

  // The work
  base::expected<std::optional<persistent_cache::EntryMetadata>,
                 persistent_cache::TransactionError>
      result;
  {
    TRACE_EVENT0("gpu", "GpuPersistentCache::DiskCache::Cache::Find");
    result = cache_->Find(base::as_byte_span(key), buffer_provider);
  }

  // Notify other threads
  SignalUsingCacheComplete();

  ASSIGN_OR_RETURN(
      auto metadata, result, [&](persistent_cache::TransactionError error) {
        HandlePersistentCacheError(&use_shader_cache_shm_count_->data, error);
        return CacheLoadResult::kMissTransactionError;
      });

  // Hit if present; miss otherwise.
  return metadata.has_value() ? CacheLoadResult::kHitDisk
                              : CacheLoadResult::kMiss;
}

void GpuPersistentCache::DiskCache::Store(
    scoped_refptr<MemoryCacheEntry> entry) {
  DiskCacheTraceScope trace_scope("GpuPersistentCache::DiskCache::Store");
  const uint64_t idle_id =
      current_idle_id_.fetch_add(1, std::memory_order_relaxed) + 1;
  trace_scope.SetIdleId(idle_id);

  if (!disk_write_task_runner_) {
    // No async task runner, write to disk immediately.
    DoStoreToDisk(entry);
    return;
  }

  // Increment the pending bytes in write queue.
  const size_t bytes_to_write = entry->TotalSize();
  const auto pending_bytes = pending_bytes_to_write_.fetch_add(
      bytes_to_write, std::memory_order_relaxed);

  trace_scope.SetPendingBytes(pending_bytes + bytes_to_write);

  disk_write_task_runner_->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&GpuPersistentCache::DiskCache::DoDelayedStoreToDisk,
                     base::WrapRefCounted(this), std::move(entry), idle_id),
      kDiskWriteDelaySeconds);
}

void GpuPersistentCache::DiskCache::DoStoreToDisk(
    scoped_refptr<MemoryCacheEntry> entry) {
  ScopedHistogramTimer timer(GetHistogramName(cache_prefix_, "Store"));
  TRACE_EVENT0("gpu", "GpuPersistentCache::DiskCache::DoStoreToDisk");

  {
    base::ScopedAllowBaseSyncPrimitives allow_base_sync_primitives;
    base::AutoLock lock(cache_in_use_mutex_);
    // Wait until the cache is not in use.
    while (cache_in_use_) {
      cache_in_use_cond_var_.Wait();
    }
    cache_in_use_ = true;
  }

  // The work.
  base::expected<void, persistent_cache::TransactionError> result;
  {
    TRACE_EVENT0("gpu", "GpuPersistentCache::DiskCache::Cache::Insert");
    result = cache_->Insert(base::as_byte_span(entry->Key()), entry->Data());
  }

  // Unblock other threads.
  SignalUsingCacheComplete();

  RETURN_IF_ERROR(result, [&](persistent_cache::TransactionError error) {
    HandlePersistentCacheError(&use_shader_cache_shm_count_->data, error);
  });
}

void GpuPersistentCache::DiskCache::DoDelayedStoreToDisk(
    scoped_refptr<MemoryCacheEntry> entry,
    uint64_t idle_id) {
  DiskCacheTraceScope trace_scope(
      "GpuPersistentCache::DiskCache::DoDelayedStoreToDisk");
  trace_scope.SetIdleId(idle_id);

  // The idle ID is used to check if there has been any cache activity since
  // the delayed store task was posted. If the IDs don't match, it means
  // another cache operation has occurred, so we reschedule the task to wait
  // for the next idle period. This ensures that we only perform the write
  // when the cache is truly idle.
  const uint64_t current_idle_id =
      current_idle_id_.load(std::memory_order_relaxed);
  const bool idle_id_match = current_idle_id == idle_id;

  // We also force writing if the pending bytes exceed limit.
  const size_t pending_bytes =
      pending_bytes_to_write_.load(std::memory_order_relaxed);
  const bool exceed_max_pending_bytes =
      pending_bytes > max_pending_bytes_to_write_;

  trace_scope.SetPendingBytes(pending_bytes);

  if (idle_id_match || exceed_max_pending_bytes) {
    DoStoreToDisk(entry);
    pending_bytes_to_write_.fetch_sub(entry->TotalSize(),
                                      std::memory_order_relaxed);
    return;
  }

  // Re-schedule the write since the cache is not idle.
  disk_write_task_runner_->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&GpuPersistentCache::DiskCache::DoDelayedStoreToDisk,
                     base::WrapRefCounted(this), std::move(entry),
                     current_idle_id),
      kDiskWriteDelaySeconds);
}

// GpuPersistentCache
GpuPersistentCache::GpuPersistentCache(std::string_view cache_prefix,
                                       scoped_refptr<MemoryCache> memory_cache,
                                       MetadataOpts metadata_options,
                                       AsyncDiskWriteOpts async_write_options)
    : cache_prefix_(cache_prefix),
      memory_cache_(std::move(memory_cache)),
      async_write_options_(std::move(async_write_options)),
      metadata_options_(std::move(metadata_options)) {
  if (metadata_options_.enabled) {
    cache_metadata_ = CacheMetadata();
  }
}

GpuPersistentCache::~GpuPersistentCache() = default;

void GpuPersistentCache::InitializeCache(
    persistent_cache::PendingBackend pending_backend,
    scoped_refptr<RefCountedGpuProcessShmCount> use_shader_cache_shm_count) {
  CHECK(!disk_cache_initialized_.IsSet());
  ASSIGN_OR_RETURN(
      auto cache,
      persistent_cache::PersistentCache::Bind(
          persistent_cache::Client::kShaderCache, std::move(pending_backend)),
      [&](persistent_cache::TransactionError error) {
        HandlePersistentCacheError(&use_shader_cache_shm_count->data, error);
      });

  disk_cache_ = base::MakeRefCounted<DiskCache>(
      cache_prefix_, std::move(cache), async_write_options_,
      std::move(use_shader_cache_shm_count));
  disk_cache_initialized_.Set();

  base::UmaHistogramEnumeration(
      GetHistogramName(cache_prefix_, "MetadataEvent"),
      MetadataEvent::kCacheInitialized);

  if (memory_cache_) {
    // If opening the persistent cache succeeded, copy all entries from the
    // memory cache into it.
    memory_cache_->ForEach([this](MemoryCacheEntry* memory_entry) {
      // Query the existence of the disk cache entry by providing an empty
      // buffer so no data is copied.
      CacheLoadResult result = disk_cache_->Load(
          memory_entry->Key(), [](size_t) { return base::span<uint8_t>(); });
      if (!IsCacheHitResult(result)) {
        disk_cache_->Store(memory_entry);
      }
    });
  }

  if (metadata_options_.enabled) {
    // Load and process any metadata found in the disk cache. Run it
    // asynchronously if possible.
    if (async_write_options_.task_runner) {
      async_write_options_.task_runner->PostTask(
          FROM_HERE,
          base::BindOnce(
              &GpuPersistentCache::LoadAndProcessMetadataFromDiskCache,
              base::WrapRefCounted(this)));
    } else {
      LoadAndProcessMetadataFromDiskCache();
    }
  }
}

#if BUILDFLAG(USE_DAWN) || BUILDFLAG(SKIA_USE_DAWN)
size_t GpuPersistentCache::FindKey(std::string_view key) {
  size_t discovered_size = 0;
  auto buffer_provider = [&discovered_size](size_t content_size) {
    discovered_size = content_size;
    return base::span<uint8_t>();
  };
  CacheLoadResult result = LoadImpl(key, std::move(buffer_provider));
  RecordCacheLoadResultHistogram(result);
  if (IsCacheHitResult(result) && IsVkPipelineCache(key)) {
    base::UmaHistogramMemoryKB(
        GetHistogramName(cache_prefix_, "VkPipelineCache.LoadedSize"),
        base::ByteSize(discovered_size));
  }
  return discovered_size;
}

size_t GpuPersistentCache::LoadData(std::string_view key,
                                    base::span<uint8_t> dest) {
  if (dest.empty()) {
    return FindKey(key);
  }

  size_t discovered_size = 0;
  auto buffer_provider = [dest, &discovered_size](size_t content_size) {
    discovered_size = content_size;
    if (dest.size() >= content_size) {
      return dest.first(content_size);
    }
    return base::span<uint8_t>();
  };

  CacheLoadResult result = LoadImpl(key, std::move(buffer_provider));
  if (!IsCacheHitResult(result)) {
    RecordCacheLoadResultHistogram(result);
  } else if (dest.size() < discovered_size) {
    // If the buffer size provided is smaller than the data, return 0.
    return 0;
  }

  return discovered_size;
}

size_t GpuPersistentCache::FindKey(std::span<const std::byte> key) {
  std::string_view key_str(reinterpret_cast<const char*>(key.data()),
                           key.size());
  return FindKey(key_str);
}

size_t GpuPersistentCache::LoadData(std::span<const std::byte> key,
                                    std::span<std::byte> dest) {
  std::string_view key_str(reinterpret_cast<const char*>(key.data()),
                           key.size());
  // SAFETY: `dest` is provided by the caller who is responsible.
  base::span<uint8_t> dest_span = UNSAFE_BUFFERS(
      base::span(reinterpret_cast<uint8_t*>(dest.data()), dest.size()));
  return LoadData(key_str, dest_span);
}
#endif

sk_sp<SkData> GpuPersistentCache::load(const SkData& key) {
  std::string_view key_str(static_cast<const char*>(key.data()), key.size());
  sk_sp<SkData> output_data;

  // A BufferProvider for PersistentCache that allocates a new SkData to hold an
  // entry's content and returns a view into it.
  auto buffer_provider = [&output_data](size_t content_size) {
    output_data = SkData::MakeUninitialized(content_size);
    // SAFETY: SkData doesn't provide an API to get its buffer as a
    // writeable span.
    return UNSAFE_BUFFERS(
        base::span(static_cast<uint8_t*>(output_data->writable_data()),
                   output_data->size()));
  };

  CacheLoadResult result = LoadImpl(key_str, std::move(buffer_provider));
  RecordCacheLoadResultHistogram(result);

  return output_data;
}

int64_t GpuPersistentCache::GLBlobCacheGet(const void* key,
                                           int64_t key_size,
                                           void* value_out,
                                           int64_t value_size) {
  CHECK_GE(key_size, 0);
  std::string_view key_str(static_cast<const char*>(key),
                           static_cast<size_t>(key_size));
  size_t discovered_size = 0;

  // A BufferProvider for PersistentCache that puts the size of the content, in
  // bytes, into `discovered_size` and returns a view into the buffer at
  // `value_out` if it is big enough or an empty span otherwise.
  // SAFETY: Caller provides either null `value_out` or `value_out` plus
  // `value_size`.
  auto buffer_provider =
      [value = UNSAFE_BUFFERS(base::span(static_cast<uint8_t*>(value_out),
                                         static_cast<size_t>(value_size))),
       &discovered_size](size_t content_size) {
        // Cache hit: retain the size to return to the caller.
        discovered_size = content_size;
        if (value.size() >= content_size) {
          return value.first(content_size);
        }
        return base::span<uint8_t>();
      };

  CacheLoadResult result = LoadImpl(key_str, std::move(buffer_provider));
  if (!IsCacheHitResult(result) || value_size == 0) {
    // This function is called twice in the cache hit case, once to query the
    // size of the buffer and again with a buffer to write into. To avoid
    // skewing the metrics by generating two cache hit data points, only record
    // a cache hit when there is no buffer provided.
    RecordCacheLoadResultHistogram(result);
  }

  // If the buffer size provided is smaller than the data, return 0.
  // We exclude value_size == 0 because that is used to query the size of the
  // cached data without reading it.
  if (value_size > 0 && static_cast<size_t>(value_size) < discovered_size) {
    return 0;
  }

  return discovered_size;
}

void GpuPersistentCache::PurgeMemory(int memory_limit) {
  if (memory_cache_) {
    memory_cache_->OnReleaseMemory(memory_limit);
  }
}

void GpuPersistentCache::OnMemoryDump(
    const std::string& dump_name,
    base::trace_event::ProcessMemoryDump* pmd) {
  if (memory_cache_) {
    memory_cache_->OnMemoryDump(dump_name, pmd);
  }
}

const persistent_cache::PersistentCache&
GpuPersistentCache::GetPersistentCacheForTesting() const {
  return disk_cache_->persistent_cache();
}

void GpuPersistentCache::FlushMetadataForTesting() {
  if (!metadata_options_.enabled) {
    return;
  }

  base::AutoLock lock(metadata_mutex_);
  if (!cache_metadata_) {
    return;
  }

  FlushCacheMetadata(std::move(cache_metadata_.value()));
  cache_metadata_.reset();
}

bool GpuPersistentCache::IsCacheHitResult(CacheLoadResult result) {
  return result > CacheLoadResult::kMaxMissValue;
}

GpuPersistentCache::CacheLoadResult GpuPersistentCache::LoadImpl(
    std::string_view key,
    persistent_cache::BufferProvider buffer_provider) {
  const bool disk_cache_initialized = disk_cache_initialized_.IsSet();
  TRACE_EVENT1("gpu", "GpuPersistentCache::LoadImpl", "persistent_cache",
               disk_cache_initialized);

  // Track cache available for the 1st kMaxLoadStoreForTrackingCacheAvailable
  // loads.
  if (load_count_.fetch_add(1, std::memory_order_relaxed) <
      kMaxLoadStoreForTrackingCacheAvailable) {
    base::UmaHistogramBoolean(
        GetHistogramName(cache_prefix_, "Load.CacheAvailable"),
        disk_cache_initialized);
  }

  UpdateCacheMetadataForLoad(key);

  if (memory_cache_) {
    if (auto memory_entry = memory_cache_->Find(key)) {
      base::span<uint8_t> output_buffer =
          buffer_provider(memory_entry->DataSize());
      memory_entry->ReadData(output_buffer.data(), output_buffer.size());
      return CacheLoadResult::kHitMemory;
    }
  }

  if (!disk_cache_initialized) {
    return CacheLoadResult::kMissNoDiskCache;
  }

  base::span<uint8_t> provided_buffer;
  base::HeapArray<uint8_t> local_allocated_buffer;

  // A BufferProvider for PersistentCache that returns one of:
  // 1.  a view into the buffer at `provided_buffer` if it is big enough
  // 2.  an empty span if no memory_cache_ exists, or
  // 3.  a view into a new base::HeapArray (`local_allocated_buffer`)
  auto wrapped_buffer_provider =
      [buffer_provider, memory_cache_exists = memory_cache_ != nullptr,
       &provided_buffer, &local_allocated_buffer](size_t content_size) {
        // First attempt to use the buffer_provider to allocate a buffer for the
        // result.
        provided_buffer = buffer_provider(content_size);

        // If the `provided_buffer` is large enough, simply return it and let
        // the disk cache write into it
        if (provided_buffer.size() >= content_size) {
          return provided_buffer.first(content_size);  // Case 1.
        }

        if (!memory_cache_exists) {
          return base::span<uint8_t>();  // Case 2.
        }

        // Allocate our own buffer into `local_allocated_buffer` so the result
        // can be put in the memory cache
        DCHECK(content_size != 0);
        local_allocated_buffer = base::HeapArray<uint8_t>::Uninit(content_size);
        return base::span<uint8_t>(local_allocated_buffer);  // Case 3.
      };

  CacheLoadResult disk_load_result =
      disk_cache_->Load(key, wrapped_buffer_provider);
  if (!IsCacheHitResult(disk_load_result)) {
    return disk_load_result;
  }

  if (memory_cache_) {
    // Verify the assumptions above. There should always be data in one of the
    // two buffers if the load was successful and a memory cache exists.
    DCHECK(!local_allocated_buffer.empty() || !provided_buffer.empty());

    // After loading from the disk cache, copy the entry into the memory cache
    // for faster access on future loads.
    if (!local_allocated_buffer.empty()) {
      // Prefer the `local_allocated_buffer` because it can be moved directly
      // into the memory cache.
      memory_cache_->Store(key, std::move(local_allocated_buffer));
    } else {
      // Otherwise we need to copy the result from the user provided buffer
      memory_cache_->Store(key, provided_buffer);
    }
  }

  return disk_load_result;
}

#if BUILDFLAG(USE_DAWN) || BUILDFLAG(SKIA_USE_DAWN)
void GpuPersistentCache::StoreData(std::string_view key,
                                   base::span<const uint8_t> src) {
  // Serialized VkPipelineCache entries won't be loaded again until the GPU
  // process restarts. Storing this entry in the memory cache isn't useful but
  // does evict otherwise useful data.
  const bool skip_memory_cache = IsVkPipelineCache(key);
  StoreImpl(key, src, skip_memory_cache);
}

void GpuPersistentCache::StoreData(std::span<const std::byte> key,
                                   std::span<const std::byte> src) {
  std::string_view key_str(reinterpret_cast<const char*>(key.data()),
                           key.size());
  // SAFETY: `src` is provided by the caller who is responsible.
  base::span<const uint8_t> src_span = UNSAFE_BUFFERS(
      base::span(reinterpret_cast<const uint8_t*>(src.data()), src.size()));
  return StoreData(key_str, src_span);
}
#endif

void GpuPersistentCache::store(const SkData& key, const SkData& data) {
  std::string_view key_str(static_cast<const char*>(key.data()), key.size());
  base::span<const uint8_t> value_span = UNSAFE_BUFFERS(
      base::span(static_cast<const uint8_t*>(data.bytes()), data.size()));
  StoreImpl(key_str, value_span, /*skip_memory_cache=*/false);
}

void GpuPersistentCache::GLBlobCacheSet(const void* key,
                                        int64_t key_size,
                                        const void* value,
                                        int64_t value_size) {
  CHECK_GE(key_size, 0);
  CHECK_GE(value_size, 0);
  std::string_view key_str(static_cast<const char*>(key),
                           static_cast<size_t>(key_size));
  base::span<const uint8_t> value_span = UNSAFE_BUFFERS(base::span(
      static_cast<const uint8_t*>(value), static_cast<size_t>(value_size)));
  StoreImpl(key_str, value_span, /*skip_memory_cache=*/false);
}

void GpuPersistentCache::StoreImpl(std::string_view key,
                                   base::span<const uint8_t> value,
                                   bool skip_memory_cache) {
  const bool disk_cache_initialized = disk_cache_initialized_.IsSet();
  TRACE_EVENT1("gpu", "GpuPersistentCache::StoreImpl", "persistent_cache",
               disk_cache_initialized);

  // Track cache available for the 1st kMaxLoadStoreForTrackingCacheAvailable
  // stores.
  if (store_count_.fetch_add(1, std::memory_order_relaxed) <
      kMaxLoadStoreForTrackingCacheAvailable) {
    base::UmaHistogramBoolean(
        GetHistogramName(cache_prefix_, "Store.CacheAvailable"),
        disk_cache_initialized);
  }

  // Record the cache key and value sizes infrequently.
  if (base::ShouldRecordSubsampledMetric(kRecordCacheEntrySizeFrequency)) {
    base::UmaHistogramMemoryKB(GetHistogramName(cache_prefix_, "KeySize"),
                               base::ByteSize(key.size()));
    base::UmaHistogramMemoryKB(GetHistogramName(cache_prefix_, "ValueSize"),
                               base::ByteSize(value.size()));
  }

  scoped_refptr<MemoryCacheEntry> memory_cache_entry;
  if (memory_cache_ && !skip_memory_cache) {
    memory_cache_entry = memory_cache_->Store(key, value);
  }

  if (!disk_cache_initialized) {
    return;
  }

  // If there was no memory cache, wrap the data in a new MemoryCacheEntry for
  // insertion.
  if (!memory_cache_entry) {
    memory_cache_entry = base::MakeRefCounted<MemoryCacheEntry>(key, value);
  }

  disk_cache_->Store(memory_cache_entry);
}

void GpuPersistentCache::RecordCacheLoadResultHistogram(
    CacheLoadResult result) {
  base::UmaHistogramEnumeration(GetHistogramName(cache_prefix_, "LoadResult"),
                                result);
}

// CacheMetadata
GpuPersistentCache::CacheMetadata::CacheMetadata::CacheMetadata() = default;
GpuPersistentCache::CacheMetadata::CacheMetadata(const CacheMetadata&) =
    default;
GpuPersistentCache::CacheMetadata::CacheMetadata(CacheMetadata&&) = default;
GpuPersistentCache::CacheMetadata::~CacheMetadata() = default;
GpuPersistentCache::CacheMetadata& GpuPersistentCache::CacheMetadata::operator=(
    const CacheMetadata&) = default;
GpuPersistentCache::CacheMetadata& GpuPersistentCache::CacheMetadata::operator=(
    CacheMetadata&&) = default;

base::HeapArray<uint8_t> GpuPersistentCache::CacheMetadata::Serialize() const {
  base::CheckedNumeric<size_t> buffer_size_needed = 0;
  buffer_size_needed += sizeof(uint64_t);  // Count of first_loaded_keys
  for (const std::string& key : first_loaded_keys) {
    buffer_size_needed += sizeof(uint64_t);
    buffer_size_needed += key.size();
  }

  auto serialized_data =
      base::HeapArray<uint8_t>::WithSize(buffer_size_needed.ValueOrDie());
  size_t current_buffer_offset = 0;

  auto write_serialized_data = [&serialized_data, &current_buffer_offset](
                                   base::span<const uint8_t> data) {
    base::span<uint8_t> dst_span =
        serialized_data.subspan(current_buffer_offset, data.size());
    dst_span.copy_from_nonoverlapping(data);
    current_buffer_offset += data.size();
  };

  uint64_t first_loaded_keys_count = first_loaded_keys.size();
  write_serialized_data(
      base::as_byte_span(base::span_from_ref(first_loaded_keys_count)));
  for (const std::string& key : first_loaded_keys) {
    uint64_t key_size = key.size();
    write_serialized_data(base::as_byte_span(base::span_from_ref(key_size)));
    write_serialized_data(base::as_byte_span(key));
  }

  return serialized_data;
}

GpuPersistentCache::CacheMetadata
GpuPersistentCache::CacheMetadata::Deserialize(
    const base::HeapArray<uint8_t>& data) {
  size_t current_buffer_offset = 0;

  auto read_serialized_data =
      [&data, &current_buffer_offset](base::span<uint8_t> dst_data) {
        base::span<const uint8_t> src_data =
            data.subspan(current_buffer_offset, dst_data.size());
        dst_data.copy_from_nonoverlapping(src_data);
        current_buffer_offset += dst_data.size();
      };
  auto read_serialized_string = [read_serialized_data]() {
    uint64_t string_length = 0;
    read_serialized_data(
        base::as_writable_bytes(base::span_from_ref(string_length)));

    std::string result;
    result.resize(string_length);
    read_serialized_data(base::as_writable_bytes(base::span(result)));

    return result;
  };

  CacheMetadata metadata;

  uint64_t first_loaded_keys_count = 0;
  read_serialized_data(
      base::as_writable_bytes(base::span_from_ref(first_loaded_keys_count)));
  for (uint64_t key_index = 0; key_index < first_loaded_keys_count;
       key_index++) {
    metadata.first_loaded_keys.insert(read_serialized_string());
  }

  return metadata;
}

void GpuPersistentCache::UpdateCacheMetadataForLoad(std::string_view key) {
  if (!metadata_options_.enabled) {
    return;
  }

  base::AutoLock lock(metadata_mutex_);
  if (!cache_metadata_) {
    return;
  }

  if (cache_metadata_->first_loaded_keys.size() <
      metadata_options_.preload_count) {
    cache_metadata_->first_loaded_keys.insert(
        std::string(key.data(), key.size()));
  }

  // Right now the metadata only contains the first_loaded_keys so flush once we
  // can't fit any more in the set. If the metadata contains more data in the
  // future, we may want to flush multiple times.
  bool flush_metadata = cache_metadata_->first_loaded_keys.size() >=
                        metadata_options_.preload_count;
  if (flush_metadata) {
    base::UmaHistogramEnumeration(
        GetHistogramName(cache_prefix_, "MetadataEvent"),
        MetadataEvent::kMetadataFlushed);
    FlushCacheMetadata(std::move(cache_metadata_.value()));
    cache_metadata_.reset();
  }
}

void GpuPersistentCache::FlushCacheMetadata(CacheMetadata metadata) {
  // If there is no disk cache to flush to, drop the metadata.
  const bool disk_cache_initialized = disk_cache_initialized_.IsSet();
  if (!disk_cache_initialized) {
    return;
  }

  auto entry = base::MakeRefCounted<MemoryCacheEntry>(GetCacheMetadataKey(),
                                                      metadata.Serialize());
  disk_cache_->Store(std::move(entry));
}

void GpuPersistentCache::LoadAndProcessMetadataFromDiskCache() {
  CHECK(disk_cache_);

  base::HeapArray<uint8_t> metadata_blob;
  bool metadata_exists = IsCacheHitResult(disk_cache_->Load(
      GetCacheMetadataKey(), [&metadata_blob](size_t content_size) {
        metadata_blob = base::HeapArray<uint8_t>::WithSize(content_size);
        return metadata_blob.first(content_size);
      }));
  if (metadata_exists) {
    base::UmaHistogramEnumeration(
        GetHistogramName(cache_prefix_, "MetadataEvent"),
        MetadataEvent::kMetadataLoaded);
    CHECK(!metadata_blob.empty());
    CacheMetadata metadata = CacheMetadata::Deserialize(metadata_blob);
    ProcessMetadata(std::move(metadata));
  }
}

void GpuPersistentCache::ProcessMetadata(const CacheMetadata& metadata) {
  for (const std::string& key : metadata.first_loaded_keys) {
    base::HeapArray<uint8_t> value;
    if (IsCacheHitResult(disk_cache_->Load(key, [&value](size_t content_size) {
          value = base::HeapArray<uint8_t>::WithSize(content_size);
          return value.first(content_size);
        }))) {
      memory_cache_->Store(key, std::move(value));
    }
  }
}

void BindCacheToCurrentOpenGLContext(GpuPersistentCache* cache) {
  if (!cache || !gl::g_current_gl_driver->ext.b_GL_ANGLE_blob_cache) {
    return;
  }

  glBlobCacheCallbacksANGLE(GLBlobCacheSetCallback, GLBlobCacheGetCallback,
                            cache);
}

void UnbindCacheFromCurrentOpenGLContext() {
  if (!gl::g_current_gl_driver->ext.b_GL_ANGLE_blob_cache) {
    return;
  }

  glBlobCacheCallbacksANGLE(nullptr, nullptr, nullptr);
}

GpuPersistentCacheCollection::GpuPersistentCacheCollection(
    size_t max_in_memory_cache_size,
    GpuPersistentCache::MetadataOpts metadata_options,
    GpuPersistentCache::AsyncDiskWriteOpts async_write_options)
    : max_in_memory_cache_size_(max_in_memory_cache_size),
      metadata_options_(metadata_options),
      async_write_options_(async_write_options) {
  if (base::SingleThreadTaskRunner::HasCurrentDefault()) {
    base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
        this, "GpuPersistentCache",
        base::SingleThreadTaskRunner::GetCurrentDefault());
  }
}

GpuPersistentCacheCollection::~GpuPersistentCacheCollection() {
  base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
      this);
}

scoped_refptr<GpuPersistentCache> GpuPersistentCacheCollection::GetCache(
    const GpuDiskCacheHandle& handle) {
  base::AutoLock lock(mutex_);
  if (auto iter = caches_.find(handle); iter != caches_.end()) {
    return iter->second.get();
  }

  auto memory_cache =
      base::MakeRefCounted<MemoryCache>(max_in_memory_cache_size_);

  auto [iter, inserted] = caches_.emplace(
      handle, base::MakeRefCounted<GpuPersistentCache>(
                  GetCacheHistogramPrefix(handle), std::move(memory_cache),
                  metadata_options_, async_write_options_));
  DCHECK(inserted);
  return iter->second;
}

void GpuPersistentCacheCollection::PurgeMemory(int memory_limit) {
  base::AutoLock lock(mutex_);
  for (auto& [_, cache] : caches_) {
    cache->PurgeMemory(memory_limit);
  }
}

bool GpuPersistentCacheCollection::OnMemoryDump(
    const base::trace_event::MemoryDumpArgs& args,
    base::trace_event::ProcessMemoryDump* pmd) {
  base::AutoLock lock(mutex_);
  for (auto& [handle, cache] : caches_) {
    std::ostringstream dump_name;
    dump_name << "gpu/shader_cache/" << GetCacheHistogramPrefix(handle);
    if (!IsReservedGpuDiskCacheHandle(handle)) {
      int32_t value = GetHandleValue(handle);
      DCHECK_GE(value, 0);
      dump_name << "_" << value;
    }
    cache->OnMemoryDump(dump_name.str(), pmd);
  }
  return true;
}

}  // namespace gpu
