// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CC_TILES_GPU_IMAGE_DECODE_CACHE_H_
#define CC_TILES_GPU_IMAGE_DECODE_CACHE_H_

#include <memory>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/containers/lru_cache.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/discardable_memory.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/trace_event/memory_dump_provider.h"
#include "cc/cc_export.h"
#include "cc/paint/image_transfer_cache_entry.h"
#include "cc/tiles/image_decode_cache.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkYUVAInfo.h"
#include "third_party/skia/include/gpu/gl/GrGLTypes.h"

namespace viz {
class RasterContextProvider;
}

namespace cc {

CC_EXPORT BASE_DECLARE_FEATURE(kPurgeOldCacheEntriesOnTimer);

class RasterDarkModeFilter;

// OVERVIEW:
//
// GpuImageDecodeCache handles the decode and upload of images that will
// be used by Skia's GPU raster path. It also maintains a cache of these
// decoded/uploaded images for later re-use.
//
// Generally, when an image is required for raster, GpuImageDecodeCache
// creates two tasks, one to decode the image, and one to upload the image to
// the GPU. These tasks are completed before the raster task which depends on
// the image. We need to separate decode and upload tasks, as decode can occur
// simultaneously on multiple threads, while upload requires the GL context
// lock so it must happen on our non-concurrent raster thread.
//
// Decoded and Uploaded image data share a single cache entry. Depending on how
// far we've progressed, this cache entry may contain CPU-side decoded data,
// GPU-side uploaded data, or both. CPU-side decoded data is stored in software
// discardable memory and is only locked for short periods of time (until the
// upload completes). Uploaded GPU data is stored in GPU discardable memory and
// remains locked for the duration of the raster tasks which depend on it.
//
// In cases where the size of locked GPU images exceeds our working set limits,
// we operate in an "at-raster" mode. In this mode, there are no decode/upload
// tasks, and images are decoded/uploaded as needed, immediately before being
// used in raster. Cache entries for at-raster tasks are marked as such, which
// prevents future tasks from taking a dependency on them and extending their
// lifetime longer than is necessary.
//
// RASTER-SCALE CACHING:
//
// In order to save memory, images which are going to be scaled may be uploaded
// at lower than original resolution. In these cases, we may later need to
// re-upload the image at a higher resolution. To handle multiple images of
// different scales being in use at the same time, we have a two-part caching
// system.
//
// The first cache, |persistent_cache_|, stores one ImageData per image id.
// These ImageDatas are not necessarily associated with a given DrawImage, and
// are saved (persisted) even when their ref-count reaches zero (assuming they
// fit in the current memory budget). This allows for future re-use of image
// resources.
//
// The second cache, |in_use_cache_|, stores one image data per DrawImage -
// this may be the same ImageData that is in the persistent_cache_.  These
// cache entries are more transient and are deleted as soon as all refs to the
// given DrawImage are released (the image is no longer in-use).
//
// For examples of raster-scale caching, see https://goo.gl/0zCd9Z
//
// REF COUNTING:
//
// In dealing with the two caches in GpuImageDecodeCache, there are three
// ref-counting concepts in use:
//   1) ImageData upload/decode ref-counts.
//      These ref-counts represent the overall number of references to the
//      upload or decode portion of an ImageData. These ref-counts control
//      both whether the upload/decode data can be freed, as well as whether an
//      ImageData can be removed from the |persistent_cache_|. ImageDatas are
//      only removed from the |persistent_cache_| if their upload/decode
//      ref-counts are zero or if they are orphaned and replaced by a new entry.
//   2) InUseCacheEntry ref-counts.
//      These ref-counts represent the number of references to an
//      InUseCacheEntry from a specific DrawImage. When the InUseCacheEntry's
//      ref-count reaches 0 it will be deleted.
//   3) scoped_refptr ref-counts.
//      Because both the persistent_cache_ and the in_use_cache_ point at the
//      same ImageDatas (and may need to keep these ImageDatas alive independent
//      of each other), they hold ImageDatas by scoped_refptr. The scoped_refptr
//      keeps an ImageData alive while it is present in either the
//      |persistent_cache_| or |in_use_cache_|.
//
// HARDWARE ACCELERATED DECODES:
//
// In Chrome OS, we have the ability to use specialized hardware to decode
// certain images. Because this requires interacting with drivers, it must be
// done in the GPU process. Therefore, we follow a different path than the usual
// decode -> upload tasks:
//   1) We decide whether to do hardware decode acceleration for an image before
//      we create the decode/upload tasks. Under the hood, this involves parsing
//      the image and checking if it's supported by the hardware decoder
//      according to information advertised by the GPU process. Also, we only
//      allow hardware decoding in OOP-R mode.
//   2) If we do decide to do hardware decoding, we don't create a decode task.
//      Instead, we create only an upload task and store enough state to
//      indicate that the image will go through this hardware accelerated path.
//      The reason that we use the upload task is that we need to hold the
//      context lock in order to schedule the image decode.
//   3) When the upload task runs, we send a request to the GPU process to start
//      the image decode. This is an IPC message that does not require us to
//      wait for the response. Instead, we get a sync token that is signalled
//      when the decode completes. We insert a wait for this sync token right
//      after sending the decode request.
//
// We also handle the more unusual case where images are decoded at raster time.
// The process is similar: we skip the software decode and then request the
// hardware decode in the same way as step (3) above.
//
// Note that the decoded data never makes it back to the renderer. It stays in
// the GPU process. The sync token ensures that any raster work that needs the
// image happens after the decode completes.
class CC_EXPORT GpuImageDecodeCache
    : public ImageDecodeCache,
      public base::trace_event::MemoryDumpProvider {
 public:
  enum class DecodeTaskType { kPartOfUploadTask, kStandAloneDecodeTask };

  explicit GpuImageDecodeCache(viz::RasterContextProvider* context,
                               bool use_transfer_cache,
                               SkColorType color_type,
                               size_t max_working_set_bytes,
                               int max_texture_size,
                               RasterDarkModeFilter* const dark_mode_filter);
  ~GpuImageDecodeCache() override;

  // Returns the GL texture ID backing the given SkImage.
  static GrGLuint GlIdFromSkImage(const SkImage* image);

  static constexpr base::TimeDelta kPurgeInterval = base::Seconds(30);
  static constexpr base::TimeDelta kPurgeMaxAge = base::Seconds(30);

  // ImageDecodeCache overrides.

  // Finds the existing uploaded image for the provided DrawImage. Creates an
  // upload task to upload the image if an existing image does not exist.
  // See |GetTaskForImageAndRefInternal| to learn about the |client_id|.
  TaskResult GetTaskForImageAndRef(ClientId client_id,
                                   const DrawImage& image,
                                   const TracingInfo& tracing_info) override;
  // See |GetTaskForImageAndRefInternal| to learn about the |client_id|.
  TaskResult GetOutOfRasterDecodeTaskForImageAndRef(
      ClientId client_id,
      const DrawImage& image) override;
  void UnrefImage(const DrawImage& image) override;
  DecodedDrawImage GetDecodedImageForDraw(const DrawImage& draw_image) override;
  void DrawWithImageFinished(const DrawImage& image,
                             const DecodedDrawImage& decoded_image) override;
  void ReduceCacheUsage() override;
  void SetShouldAggressivelyFreeResources(bool aggressively_free_resources,
                                          bool context_lock_acquired) override;
  void ClearCache() override;
  size_t GetMaximumMemoryLimitBytes() const override;
  bool UseCacheForDrawImage(const DrawImage& image) const override;
  void RecordStats() override;

  // MemoryDumpProvider overrides.
  bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
                    base::trace_event::ProcessMemoryDump* pmd) override;

  // TODO(gyuyoung): OnMemoryPressure is deprecated. So this should be removed
  // when the memory coordinator is enabled by default.
  void OnMemoryPressure(
      base::MemoryPressureListener::MemoryPressureLevel level);

  // Called by Decode / Upload tasks.
  void DecodeImageInTask(const DrawImage& image, TaskType task_type);
  void UploadImageInTask(const DrawImage& image);

  // Called by Decode / Upload tasks when tasks are finished.
  void OnImageDecodeTaskCompleted(const DrawImage& image,
                                  DecodeTaskType task_type);
  void OnImageUploadTaskCompleted(const DrawImage& image);

  bool SupportsColorSpaceConversion() const;

  // For testing only.
  void SetWorkingSetLimitsForTesting(size_t bytes_limit, size_t items_limit) {
    base::AutoLock locker(lock_);
    max_working_set_bytes_ = bytes_limit;
    max_working_set_items_ = items_limit;
  }
  size_t GetWorkingSetBytesForTesting() const {
    base::AutoLock locker(lock_);
    return working_set_bytes_;
  }
  size_t GetNumCacheEntriesForTesting() const {
    base::AutoLock locker(lock_);
    return persistent_cache_.size();
  }
  size_t GetInUseCacheEntriesForTesting() const {
    base::AutoLock locker(lock_);
    return in_use_cache_.size();
  }
  size_t GetDrawImageSizeForTesting(const DrawImage& image);
  void SetImageDecodingFailedForTesting(const DrawImage& image);
  bool DiscardableIsLockedForTesting(const DrawImage& image);
  bool IsInInUseCacheForTesting(const DrawImage& image) const;
  bool IsInPersistentCacheForTesting(const DrawImage& image) const;
  sk_sp<SkImage> GetSWImageDecodeForTesting(const DrawImage& image);
  sk_sp<SkImage> GetUploadedPlaneForTesting(const DrawImage& draw_image,
                                            YUVIndex index);
  size_t GetDarkModeImageCacheSizeForTesting(const DrawImage& draw_image);
  size_t paint_image_entries_count_for_testing() const {
    base::AutoLock locker(lock_);
    return paint_image_entries_.size();
  }
  bool NeedsDarkModeFilterForTesting(const DrawImage& draw_image);

  bool HasPendingPurgeTaskForTesting() const {
    base::AutoLock locker(lock_);
    return has_pending_purge_task();
  }

  void SetTimerTaskRunnerForTesting(
      scoped_refptr<base::SequencedTaskRunner> task_runner)
      LOCKS_EXCLUDED(lock_) {
    base::AutoLock locker(lock_);
    timer_.SetTaskRunner(task_runner);
  }

  // Updating the |last_use| field of the associated |ImageData|.
  void TouchCacheEntryForTesting(const DrawImage& draw_image)
      LOCKS_EXCLUDED(lock_);

 private:
  enum class DecodedDataMode { kGpu, kCpu, kTransferCache };
  using ImageTaskMap = base::flat_map<ClientId, scoped_refptr<TileTask>>;

  // Stores stats tracked by both DecodedImageData and UploadedImageData.
  struct ImageDataBase {
    ImageDataBase();
    ~ImageDataBase();

    bool is_locked() const { return is_locked_; }
    void OnSetLockedData(bool out_of_raster);
    void OnResetData();
    void OnLock();
    void OnUnlock();
    void mark_used() {
      DCHECK(is_locked_);
      usage_stats_.used = true;
    }

    uint32_t ref_count = 0;
    // If non-null, this is the pending task to populate this data.
    ImageTaskMap task_map;

   protected:
    using YUVSkImages = std::array<sk_sp<SkImage>, kNumYUVPlanes>;

    struct UsageStats {
      int lock_count = 1;
      bool used = false;
      bool first_lock_out_of_raster = false;
      bool first_lock_wasted = false;
    };

    // Returns the usage state (see cc file) for histogram logging.
    int UsageState() const;

    bool is_locked_ = false;
    UsageStats usage_stats_;
  };

  // Stores the CPU-side decoded bits and SkImage representation of a single
  // image (either the default or gainmap image).
  struct DecodedAuxImageData {
    // Initialize `data` and make `images` point to `rgba_pixmap` or
    // `yuva_pixmaps`, which must be backed by `data`.
    DecodedAuxImageData();
    DecodedAuxImageData(const SkPixmap& rgba_pixmap,
                        std::unique_ptr<base::DiscardableMemory> data);
    DecodedAuxImageData(const SkYUVAPixmaps& yuva_pixmaps,
                        std::unique_ptr<base::DiscardableMemory> data);
    DecodedAuxImageData(const DecodedAuxImageData&) = delete;
    DecodedAuxImageData(DecodedAuxImageData&&);
    DecodedAuxImageData& operator=(const DecodedAuxImageData&) = delete;
    DecodedAuxImageData& operator=(DecodedAuxImageData&&);
    ~DecodedAuxImageData();

    // Return true if all members are reset.
    bool IsEmpty() const;

    // Release `data` and all entries in `images` and `pixmaps`.
    void ResetData();

    // Check that images are non-nullptr only where pixmaps are non-empty.
    void ValidateImagesMatchPixmaps() const {
      for (int i = 0; i < SkYUVAInfo::kMaxPlanes; ++i) {
        DCHECK_EQ(images[i] == nullptr, pixmaps[i].dimensions().isEmpty());
      }
    }

    std::unique_ptr<base::DiscardableMemory> data;
    sk_sp<SkImage> images[SkYUVAInfo::kMaxPlanes];
    SkPixmap pixmaps[SkYUVAInfo::kMaxPlanes];
  };

  // Stores the CPU-side decoded bits of an image and supporting fields.
  struct DecodedImageData : public ImageDataBase {
    explicit DecodedImageData(bool is_bitmap_backed,
                              bool can_do_hardware_accelerated_decode,
                              bool do_hardware_accelerated_decode);
    ~DecodedImageData();

    bool Lock();
    void Unlock();

    void SetLockedData(DecodedAuxImageData aux_image_data[kAuxImageCount],
                       bool out_of_raster);
    void ResetData();
    bool HasData() const {
      for (const auto& aux_image_data : aux_image_data_) {
        if (aux_image_data.data) {
          return true;
        }
      }
      return false;
    }
    base::DiscardableMemory* data(AuxImage aux_image) const {
      return aux_image_data_[AuxImageIndex(aux_image)].data.get();
    }

    void SetBitmapImage(sk_sp<SkImage> image);
    void ResetBitmapImage();

    sk_sp<SkImage> image(int plane, AuxImage aux_image) const {
      DCHECK_LT(plane, SkYUVAInfo::kMaxPlanes);
      if (is_bitmap_backed_) {
        DCHECK_EQ(aux_image, AuxImage::kDefault);
      } else {
        DCHECK(is_locked());
      }
      return aux_image_data_[AuxImageIndex(aux_image)].images[plane];
    }

    const SkPixmap* pixmaps(AuxImage aux_image) const {
      DCHECK(is_locked() || is_bitmap_backed_);
      return aux_image_data_[AuxImageIndex(aux_image)].pixmaps;
    }

    bool can_do_hardware_accelerated_decode() const {
      return can_do_hardware_accelerated_decode_;
    }

    bool do_hardware_accelerated_decode() const {
      return do_hardware_accelerated_decode_;
    }

    // Test-only functions.
    sk_sp<SkImage> ImageForTesting() const {
      return aux_image_data_[kAuxImageIndexDefault].images[0];
    }

    bool decode_failure = false;
    // Similar to |task|, but only is generated if there is no associated upload
    // generated for this task (ie, this is an out-of-raster request for decode.
    ImageTaskMap stand_alone_task_map;

    // Dark mode color filter cache.
    struct SkIRectCompare {
      bool operator()(const SkIRect& a, const SkIRect& b) const {
        return a.fLeft < b.fLeft || a.fTop < b.fTop || a.fRight < b.fRight ||
               a.fBottom < b.fBottom;
      }
    };

    base::flat_map<SkIRect, sk_sp<SkColorFilter>, SkIRectCompare>
        dark_mode_color_filter_cache;

   private:
    void ReportUsageStats() const;

    const bool is_bitmap_backed_;
    DecodedAuxImageData aux_image_data_[kAuxImageCount];

    // Keeps tracks of images that could go through hardware decode acceleration
    // though they're possibly prevented from doing so because of a disabled
    // feature flag.
    bool can_do_hardware_accelerated_decode_;

    // |do_hardware_accelerated_decode_| keeps track of images that should go
    // through hardware decode acceleration. Currently, this path is intended
    // only for Chrome OS and only for some JPEG images (see
    // https://crbug.com/868400).
    bool do_hardware_accelerated_decode_;
  };

  // Stores the GPU-side image and supporting fields.
  struct UploadedImageData : public ImageDataBase {
    UploadedImageData();
    ~UploadedImageData();

    // If |represents_yuv_image| is true, the method knows not to check for a
    // texture ID for |image|, which would inadvertently flatten it to RGB.
    void SetImage(sk_sp<SkImage> image, bool represents_yuv_image = false);
    void SetYuvImage(sk_sp<SkImage> y_image_input,
                     sk_sp<SkImage> u_image_input,
                     sk_sp<SkImage> v_image_input);
    void SetTransferCacheId(uint32_t id);
    void Reset();

    // If in image mode.
    const sk_sp<SkImage>& image() const {
      DCHECK(mode_ == Mode::kSkImage || mode_ == Mode::kNone);
      return image_;
    }
    const sk_sp<SkImage>& y_image() const {
      return plane_image_internal(YUVIndex::kY);
    }
    const sk_sp<SkImage>& u_image() const {
      return plane_image_internal(YUVIndex::kU);
    }
    const sk_sp<SkImage>& v_image() const {
      return plane_image_internal(YUVIndex::kV);
    }
    GrGLuint gl_id() const {
      DCHECK(mode_ == Mode::kSkImage || mode_ == Mode::kNone);
      return gl_id_;
    }

    GrGLuint gl_y_id() const { return gl_plane_id_internal(YUVIndex::kY); }
    GrGLuint gl_u_id() const { return gl_plane_id_internal(YUVIndex::kU); }
    GrGLuint gl_v_id() const { return gl_plane_id_internal(YUVIndex::kV); }

    // We consider an image to be valid YUV if all planes are non-null.
    bool has_yuv_planes() const {
      if (!image_yuv_planes_) {
        return false;
      }
      auto yuv_planes_rstart = image_yuv_planes_->crbegin() + !is_alpha_;
      auto yuv_planes_rend = image_yuv_planes_->crend();
      // Iterates from end to beginning, skipping alpha plane (verified to be
      // last) if the image is not alpha.
      bool has_existing_planes = std::any_of(yuv_planes_rstart, yuv_planes_rend,
                                             [](auto& it) { return it; });
      bool has_null_planes = std::any_of(yuv_planes_rstart, yuv_planes_rend,
                                         [](auto& it) { return !it; });
      if (has_existing_planes && has_null_planes) {
        DLOG(ERROR) << "Image has a mix of null and decoded planes";
      }
      return has_existing_planes && !has_null_planes;
    }

    // If in transfer cache mode.
    absl::optional<uint32_t> transfer_cache_id() const {
      DCHECK(mode_ == Mode::kTransferCache || mode_ == Mode::kNone);
      return transfer_cache_id_;
    }

    void set_unmipped_image(sk_sp<SkImage> image) {
      unmipped_image_ = std::move(image);
    }
    sk_sp<SkImage> take_unmipped_image() {
      DCHECK(!is_locked_);
      return std::move(unmipped_image_);
    }

    void set_unmipped_yuv_images(sk_sp<SkImage> y_image,
                                 sk_sp<SkImage> u_image,
                                 sk_sp<SkImage> v_image) {
      if (!unmipped_yuv_images_) {
        unmipped_yuv_images_ = YUVSkImages();
      }
      unmipped_yuv_images_->at(static_cast<size_t>(YUVIndex::kY)) =
          std::move(y_image);
      unmipped_yuv_images_->at(static_cast<size_t>(YUVIndex::kU)) =
          std::move(u_image);
      unmipped_yuv_images_->at(static_cast<size_t>(YUVIndex::kV)) =
          std::move(v_image);
    }

    sk_sp<SkImage> take_unmipped_y_image() {
      return take_unmipped_yuv_image_internal(YUVIndex::kY);
    }

    sk_sp<SkImage> take_unmipped_u_image() {
      return take_unmipped_yuv_image_internal(YUVIndex::kU);
    }

    sk_sp<SkImage> take_unmipped_v_image() {
      return take_unmipped_yuv_image_internal(YUVIndex::kV);
    }

    sk_sp<SkImage> take_unmipped_yuv_image_internal(const YUVIndex yuv_index) {
      DCHECK(!is_locked_);
      const size_t index = static_cast<size_t>(yuv_index);
      if (unmipped_yuv_images_ && unmipped_yuv_images_->size() > index) {
        return std::move(unmipped_yuv_images_->at(index));
      }
      return nullptr;
    }

   private:
    // Used for internal DCHECKs only.
    enum class Mode {
      kNone,
      kSkImage,
      kTransferCache,
    };

    void ReportUsageStats() const;

    const sk_sp<SkImage>& plane_image_internal(const YUVIndex yuv_index) const {
      DCHECK(mode_ == Mode::kSkImage || mode_ == Mode::kNone);
      DCHECK(image_yuv_planes_);
      const size_t index = static_cast<size_t>(yuv_index);
      DCHECK_GT(image_yuv_planes_->size(), index)
          << "Requested reference to a plane_id that is not set";
      return image_yuv_planes_->at(index);
    }

    GrGLuint gl_plane_id_internal(const YUVIndex yuv_index) const {
      DCHECK(mode_ == Mode::kSkImage || mode_ == Mode::kNone);
      DCHECK(gl_plane_ids_);
      const size_t index = static_cast<size_t>(yuv_index);
      DCHECK_GT(gl_plane_ids_->size(), index)
          << "Requested GL id for a plane texture that is not uploaded";
      return gl_plane_ids_->at(index);
    }

    Mode mode_ = Mode::kNone;

    // Used if |mode_| == kSkImage.
    // May be null if image not yet uploaded / prepared.
    sk_sp<SkImage> image_;
    absl::optional<YUVSkImages> image_yuv_planes_;
    // TODO(crbug/910276): Change after alpha support.
    bool is_alpha_ = false;
    GrGLuint gl_id_ = 0;
    absl::optional<std::array<GrGLuint, kNumYUVPlanes>> gl_plane_ids_;

    // Used if |mode_| == kTransferCache.
    absl::optional<uint32_t> transfer_cache_id_;

    // The original un-mipped image, for RGBX, or the representative image
    // backed by three planes for YUV. It is retained until it can be safely
    // deleted.
    sk_sp<SkImage> unmipped_image_;
    // Used for YUV decoding and null otherwise.
    absl::optional<YUVSkImages> unmipped_yuv_images_;
  };

  // A structure to represent either an RGBA or a YUVA image info.
  struct ImageInfo {
    // Initialize `rgba` or `yuva`, and compute `size`.
    ImageInfo();
    explicit ImageInfo(const SkImageInfo& rgba);
    explicit ImageInfo(const SkYUVAPixmapInfo& yuva);
    ImageInfo(const ImageInfo&);
    ImageInfo& operator=(const ImageInfo&);
    ~ImageInfo();

    // At most one of `rgba` or `yuva` may be valid.
    absl::optional<SkImageInfo> rgba;
    absl::optional<SkYUVAPixmapInfo> yuva;

    // The number of bytes used by this image.
    size_t size = 0;
  };

  struct ImageData : public base::RefCountedThreadSafe<ImageData> {
    ImageData(PaintImage::Id paint_image_id,
              DecodedDataMode mode,
              const TargetColorParams& target_color_params,
              PaintFlags::FilterQuality quality,
              int upload_scale_mip_level,
              bool needs_mips,
              bool is_bitmap_backed,
              bool can_do_hardware_accelerated_decode,
              bool do_hardware_accelerated_decode,
              ImageInfo image_info[kAuxImageCount]);

    bool IsGpuOrTransferCache() const;
    bool HasUploadedData() const;
    void ValidateBudgeted() const;

    const ImageInfo& GetImageInfo(AuxImage aux_image) const {
      switch (aux_image) {
        case AuxImage::kDefault:
          return info;
        case AuxImage::kGainmap:
          return gainmap_info;
      }
    }

    // Return the memory that is used by this image when decoded. This should
    // also equal the memory that is used on the GPU when this is uploaded.
    // In some circumstances the GPU memory usage is slightly different (e.g,
    // when a gainmap or HDR tonemapping is applied). This includes the memory
    // used by all auxiliary images.
    size_t GetTotalSize() const;

    const PaintImage::Id paint_image_id;
    const DecodedDataMode mode;
    TargetColorParams target_color_params;
    PaintFlags::FilterQuality quality;
    int upload_scale_mip_level;
    bool needs_mips = false;
    bool is_bitmap_backed;
    bool is_budgeted = false;
    base::TimeTicks last_use;

    // The RGBA or YUVA image info for the decoded image. The dimensions may be
    // smaller than the original size if the image needs to be downscaled.
    const ImageInfo info;

    // The RGBA or YUVA image info for the decoded gainmap image. This will
    // return false from IsEmpty if and only if the image has a gainmap.
    const ImageInfo gainmap_info;

    // If true, this image is no longer in our |persistent_cache_| and will be
    // deleted as soon as its ref count reaches zero.
    bool is_orphaned = false;

    DecodedImageData decode;
    UploadedImageData upload;

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

  // A ref-count and ImageData, used to associate the ImageData with a specific
  // DrawImage in the |in_use_cache_|.
  struct InUseCacheEntry {
    explicit InUseCacheEntry(scoped_refptr<ImageData> image_data);
    InUseCacheEntry(const InUseCacheEntry& other);
    InUseCacheEntry(InUseCacheEntry&& other);
    ~InUseCacheEntry();

    uint32_t ref_count = 0;
    scoped_refptr<ImageData> image_data;
  };

  // Uniquely identifies (without collisions) a specific DrawImage for use in
  // the |in_use_cache_|.
  struct InUseCacheKeyHash;
  struct InUseCacheKey {
    InUseCacheKey(const DrawImage& draw_image, int mip_level);

    bool operator==(const InUseCacheKey& other) const;

   private:
    friend struct GpuImageDecodeCache::InUseCacheKeyHash;

    PaintImage::FrameKey frame_key;
    int upload_scale_mip_level;
    PaintFlags::FilterQuality filter_quality;
    TargetColorParams target_color_params;
  };
  struct InUseCacheKeyHash {
    size_t operator()(const InUseCacheKey&) const;
  };

  // All private functions should only be called while holding |lock_|. Some
  // functions also require the |context_| lock. These are indicated by
  // additional comments.

  // Calculate the mip level to upload-scale the image to before uploading. We
  // use mip levels rather than exact scales to increase re-use of scaled
  // images.
  int CalculateUploadScaleMipLevel(const DrawImage& draw_image,
                                   AuxImage aux_image) const;

  InUseCacheKey InUseCacheKeyFromDrawImage(const DrawImage& draw_image) const;

  // Similar to GetTaskForImageAndRef, but gets the dependent decode task
  // rather than the upload task, if necessary.
  scoped_refptr<TileTask> GetImageDecodeTaskAndRef(
      ClientId client_id,
      const DrawImage& image,
      const TracingInfo& tracing_info,
      DecodeTaskType task_type) EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Note that this function behaves as if it was public (all of the same locks
  // need to be acquired). Uses |client_id| to identify which client created a
  // task as the client run their tasks in different namespaces. The client
  // which ran their task first will execute the task. All the other clients
  // will have their tasks executed as no-op.
  TaskResult GetTaskForImageAndRefInternal(ClientId client_id,
                                           const DrawImage& image,
                                           const TracingInfo& tracing_info,
                                           DecodeTaskType task_type);

  void RefImageDecode(const DrawImage& draw_image,
                      const InUseCacheKey& cache_key)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void UnrefImageDecode(const DrawImage& draw_image,
                        const InUseCacheKey& cache_key)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void RefImage(const DrawImage& draw_image, const InUseCacheKey& cache_key)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void UnrefImageInternal(const DrawImage& draw_image,
                          const InUseCacheKey& cache_key)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Called any time the ownership of an object changed. This includes changes
  // to ref-count or to orphaned status.
  void OwnershipChanged(const DrawImage& draw_image, ImageData* image_data)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Ensures that the working set can hold an element of |required_size|,
  // freeing unreferenced cache entries to make room.
  bool EnsureCapacity(size_t required_size) EXCLUSIVE_LOCKS_REQUIRED(lock_);
  bool CanFitInWorkingSet(size_t size) const EXCLUSIVE_LOCKS_REQUIRED(lock_);
  bool ExceedsCacheLimits() const EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void ReduceCacheUsageLocked() EXCLUSIVE_LOCKS_REQUIRED(lock_);

  void InsertTransferCacheEntry(
      const ClientImageTransferCacheEntry& image_entry,
      ImageData* image_data) EXCLUSIVE_LOCKS_REQUIRED(lock_);
  bool NeedsDarkModeFilter(const DrawImage& draw_image, ImageData* image_data);
  void DecodeImageAndGenerateDarkModeFilterIfNecessary(
      const DrawImage& draw_image,
      ImageData* image_data,
      TaskType task_type) EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void DecodeImageIfNecessary(const DrawImage& draw_image,
                              ImageData* image_data,
                              TaskType task_type,
                              bool needs_decode_for_dark_mode)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void GenerateDarkModeFilter(const DrawImage& draw_image,
                              ImageData* image_data)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);
  sk_sp<SkImage> CreateImageFromYUVATexturesInternal(
      const SkImage* uploaded_y_image,
      const SkImage* uploaded_u_image,
      const SkImage* uploaded_v_image,
      const int image_width,
      const int image_height,
      const SkYUVAInfo::PlaneConfig yuva_plane_config,
      const SkYUVAInfo::Subsampling yuva_subsampling,
      const SkYUVColorSpace yuva_color_space,
      sk_sp<SkColorSpace> target_color_space,
      sk_sp<SkColorSpace> decoded_color_space) const
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  scoped_refptr<GpuImageDecodeCache::ImageData> CreateImageData(
      const DrawImage& image,
      bool allow_hardware_decode);
  void WillAddCacheEntry(const DrawImage& draw_image)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Returns the SkImageInfo for the resulting image and the mip level for
  // upload.
  std::tuple<SkImageInfo, int> CreateImageInfoForDrawImage(
      const DrawImage& draw_image,
      AuxImage aux_image) const;

  // Finds the ImageData that should be used for the given DrawImage. Looks
  // first in the |in_use_cache_|, and then in the |persistent_cache_|.
  ImageData* GetImageDataForDrawImage(const DrawImage& image,
                                      const InUseCacheKey& key)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Returns true if the given ImageData can be used to draw the specified
  // DrawImage.
  bool IsCompatible(const ImageData* image_data,
                    const DrawImage& draw_image) const;

  // Helper to delete an image and remove it from the cache. Ensures that
  // the image is unlocked and Skia cleanup is handled on the right thread.
  void DeleteImage(ImageData* image_data) EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Helper to unlock an image, indicating that it is no longer actively
  // being used. An image must be locked via TryLockImage below before it
  // can be used again.
  void UnlockImage(ImageData* image_data) EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Attempts to lock an image for use. If locking fails (the image is deleted
  // on the service side), this function will delete the local reference to the
  // image and return false.
  enum class HaveContextLock { kYes, kNo };
  bool TryLockImage(HaveContextLock have_context_lock,
                    const DrawImage& draw_image,
                    ImageData* data) EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Requires that the |context_| lock be held when calling.
  void UploadImageIfNecessary(const DrawImage& draw_image,
                              ImageData* image_data)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Implementation of UploadImageIfNecessary for each sub-case.
  void UploadImageIfNecessary_TransferCache_HardwareDecode(
      const DrawImage& draw_image,
      ImageData* image_data,
      sk_sp<SkColorSpace> color_space) EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void UploadImageIfNecessary_TransferCache_SoftwareDecode(
      const DrawImage& draw_image,
      ImageData* image_data,
      sk_sp<SkColorSpace> decoded_target_colorspace,
      absl::optional<TargetColorParams> target_color_params)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void UploadImageIfNecessary_GpuCpu_YUVA(
      const DrawImage& draw_image,
      ImageData* image_data,
      sk_sp<SkImage> uploaded_image,
      GrMipMapped image_needs_mips,
      sk_sp<SkColorSpace> decoded_target_colorspace,
      sk_sp<SkColorSpace> color_space) EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void UploadImageIfNecessary_GpuCpu_RGBA(const DrawImage& draw_image,
                                          ImageData* image_data,
                                          sk_sp<SkImage> uploaded_image,
                                          GrMipMapped image_needs_mips,
                                          sk_sp<SkColorSpace> color_space)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Flush pending operations on context_->GrContext() for each element of
  // |yuv_images| and then clear the vector.
  void FlushYUVImages(std::vector<sk_sp<SkImage>>* yuv_images)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Runs pending operations that required the |context_| lock to be held, but
  // were queued up during a time when the |context_| lock was unavailable.
  // These including deleting, unlocking, and locking textures.
  void RunPendingContextThreadOperations() EXCLUSIVE_LOCKS_REQUIRED(lock_);

  void CheckContextLockAcquiredIfNecessary();

  sk_sp<SkColorSpace> ColorSpaceForImageDecode(const DrawImage& image,
                                               DecodedDataMode mode) const;

  // Helper function to add a memory dump to |pmd| for a single texture
  // identified by |gl_id| with size |bytes| and |locked_size| equal to either
  // |bytes| or 0 depending on whether the texture is currently locked.
  void AddTextureDump(base::trace_event::ProcessMemoryDump* pmd,
                      const std::string& texture_dump_name,
                      const size_t bytes,
                      const GrGLuint gl_id,
                      const size_t locked_size) const
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Alias each texture of the YUV image entry to its Skia texture counterpart,
  // taking ownership of the memory and preventing double counting.
  //
  // Given |dump_base_name| as the location where single RGB image textures are
  // dumped, this method creates dumps under |pmd| for the planar textures
  // backing |image_data| as subcategories plane_0, plane_1, etc.
  void MemoryDumpYUVImage(base::trace_event::ProcessMemoryDump* pmd,
                          const ImageData* image_data,
                          const std::string& dump_base_name,
                          size_t locked_size) const
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // |persistent_cache_| represents the long-lived cache, keeping a certain
  // budget of ImageDatas alive even when their ref count reaches zero.
  using PersistentCache = base::HashingLRUCache<PaintImage::FrameKey,
                                                scoped_refptr<ImageData>,
                                                PaintImage::FrameKeyHash>;
  void AddToPersistentCache(const DrawImage& draw_image,
                            scoped_refptr<ImageData> data)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);
  template <typename Iterator>
  Iterator RemoveFromPersistentCache(Iterator it)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  // Purges any old entries from the PersistentCache if the feature to enable
  // this behavior is turned on.
  void MaybePurgeOldCacheEntries() EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void PostPurgeOldCacheEntriesTask() EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void DoPurgeOldCacheEntries(base::TimeDelta max_age)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);
  void PurgeOldCacheEntriesCallback() LOCKS_EXCLUDED(lock_);

  // Adds mips to an image if required.
  void UpdateMipsIfNeeded(const DrawImage& draw_image, ImageData* image_data)
      EXCLUSIVE_LOCKS_REQUIRED(lock_);

  static scoped_refptr<TileTask> GetTaskFromMapForClientId(
      const ClientId client_id,
      const ImageTaskMap& task_map);

  bool has_pending_purge_task() const EXCLUSIVE_LOCKS_REQUIRED(lock_) {
    return timer_.IsRunning();
  }

  const SkColorType color_type_;
  const bool use_transfer_cache_ = false;
  raw_ptr<viz::RasterContextProvider> context_;
  int max_texture_size_ = 0;
  const PaintImage::GeneratorClientId generator_client_id_;
  bool allow_accelerated_jpeg_decodes_ = false;
  bool allow_accelerated_webp_decodes_ = false;
  SkYUVAPixmapInfo::SupportedDataTypes yuva_supported_data_types_;
  const bool enable_clipped_image_scaling_;

  // All members below this point must only be accessed while holding |lock_|.
  // The exception are const members like |normal_max_cache_bytes_| that can
  // be accessed without a lock since they are thread safe.
  mutable base::Lock lock_;

  base::OneShotTimer timer_ GUARDED_BY(lock_);

  PersistentCache persistent_cache_ GUARDED_BY(lock_);

  // Tracks the total number of bytes of image data represented by the elements
  // in `persistent_cache_`. Must be updated on AddTo/RemoveFromPersistentCache.
  size_t persistent_cache_memory_size_ GUARDED_BY(lock_) = 0;

  struct CacheEntries {
    PaintImage::ContentId content_ids[2] = {PaintImage::kInvalidContentId,
                                            PaintImage::kInvalidContentId};

    // The number of cache entries for a PaintImage. Note that there can be
    // multiple entries per content_id.
    size_t count = 0u;
  };
  // A map of PaintImage::Id to entries for this image in the
  // |persistent_cache_|.
  base::flat_map<PaintImage::Id, CacheEntries> paint_image_entries_
      GUARDED_BY(lock_);

  // |in_use_cache_| represents the in-use (short-lived) cache. Entries are
  // cleaned up as soon as their ref count reaches zero.
  using InUseCache =
      std::unordered_map<InUseCacheKey, InUseCacheEntry, InUseCacheKeyHash>;
  InUseCache in_use_cache_ GUARDED_BY(lock_);

  size_t max_working_set_bytes_ GUARDED_BY(lock_) = 0;
  size_t max_working_set_items_ GUARDED_BY(lock_) = 0;
  size_t working_set_bytes_ GUARDED_BY(lock_) = 0;
  size_t working_set_items_ GUARDED_BY(lock_) = 0;
  bool aggressively_freeing_resources_ GUARDED_BY(lock_) = false;

  // This field is not a raw_ptr<> because of incompatibilities with tracing
  // (TRACE_EVENT*), perfetto::TracedDictionary::Add and gmock/EXPECT_THAT.
  RAW_PTR_EXCLUSION RasterDarkModeFilter* const dark_mode_filter_;

  // We can't modify GPU backed SkImages without holding the context lock, so
  // we queue up operations to run the next time the lock is held.
  std::vector<SkImage*> images_pending_complete_lock_;
  std::vector<SkImage*> images_pending_unlock_;
  std::vector<sk_sp<SkImage>> images_pending_deletion_;
  // Images that are backed by planar textures must be handled differently
  // to avoid inadvertently flattening to RGB and creating additional textures.
  // See comment in RunPendingContextThreadOperations().
  std::vector<sk_sp<SkImage>> yuv_images_pending_deletion_;
  std::vector<sk_sp<SkImage>> yuv_images_pending_unlock_;
  const sk_sp<SkColorSpace> target_color_space_;

  std::vector<uint32_t> ids_pending_unlock_;
  std::vector<uint32_t> ids_pending_deletion_;

  std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
};

}  // namespace cc

#endif  // CC_TILES_GPU_IMAGE_DECODE_CACHE_H_