#ifndef CC_TILES_GPU_IMAGE_DECODE_CACHE_H_
#define CC_TILES_GPU_IMAGE_DECODE_CACHE_H_
#include <array>
#include <memory>
#include <optional>
#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/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/memory/ref_counted.h"
#include "base/memory/weak_ptr.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"
namespace viz {
class RasterContextProvider;
}
namespace cc {
class ColorFilter;
class RasterDarkModeFilter;
class CC_EXPORT GpuImageDecodeCache
: public ImageDecodeCache,
public base::trace_event::MemoryDumpProvider,
public base::MemoryPressureListener {
public:
explicit GpuImageDecodeCache(viz::RasterContextProvider* context,
SkColorType color_type,
size_t max_working_set_bytes,
int max_texture_size,
RasterDarkModeFilter* const dark_mode_filter);
~GpuImageDecodeCache() override;
static base::TimeDelta get_purge_interval();
static base::TimeDelta get_max_purge_age();
TaskResult GetTaskForImageAndRef(ClientId client_id,
const DrawImage& image,
const TracingInfo& tracing_info) override;
TaskResult GetOutOfRasterDecodeTaskForImageAndRef(ClientId client_id,
const DrawImage& image,
bool speculative) 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) override;
void ClearCache() override;
size_t GetMaximumMemoryLimitBytes() const override;
bool UseCacheForDrawImage(const DrawImage& image) const override;
void RecordStats() override;
bool OnMemoryDump(const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) override;
void OnMemoryPressure(base::MemoryPressureLevel level) override;
void DecodeImageInTask(const DrawImage& image, TaskType task_type);
void UploadImageInTask(const DrawImage& image);
void OnImageDecodeTaskCompleted(const DrawImage& image,
TaskType task_type,
ClientId client_id);
void OnImageUploadTaskCompleted(const DrawImage& image, ClientId client_id);
bool SupportsColorSpaceConversion() const;
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);
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();
}
size_t ids_pending_deletion_count_for_testing() const {
return ids_pending_deletion_.size();
}
void TouchCacheEntryForTesting(const DrawImage& draw_image)
LOCKS_EXCLUDED(lock_);
bool AcquireContextLockForTesting();
void ReleaseContextLockForTesting();
private:
using ImageTaskMap = base::flat_map<ClientId, scoped_refptr<TileTask>>;
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;
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;
};
int UsageState() const;
bool is_locked_ = false;
UsageStats usage_stats_;
};
struct DecodedAuxImageData {
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();
bool IsEmpty() const;
void ResetData();
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;
std::array<sk_sp<SkImage>, SkYUVAInfo::kMaxPlanes> images;
std::array<SkPixmap, SkYUVAInfo::kMaxPlanes> pixmaps;
};
struct DecodedImageData : public ImageDataBase {
explicit DecodedImageData(bool is_bitmap_backed);
~DecodedImageData();
bool Lock();
void Unlock();
void SetLockedData(
base::span<DecodedAuxImageData, kAuxImageCount> aux_image_data,
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];
}
base::span<const SkPixmap> pixmaps(AuxImage aux_image) const {
DCHECK(is_locked() || is_bitmap_backed_);
return aux_image_data_[AuxImageIndex(aux_image)].pixmaps;
}
sk_sp<SkImage> ImageForTesting() const {
return aux_image_data_[kAuxImageIndexDefault].images[0];
}
bool decode_failure = false;
ImageTaskMap stand_alone_task_map;
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<ColorFilter>, SkIRectCompare>
dark_mode_color_filter_cache;
private:
void ReportUsageStats() const;
const bool is_bitmap_backed_;
std::array<DecodedAuxImageData, kAuxImageCount> aux_image_data_;
};
struct UploadedImageData : public ImageDataBase {
UploadedImageData();
~UploadedImageData();
void SetTransferCacheId(uint32_t id);
void Reset();
std::optional<uint32_t> transfer_cache_id() const {
return transfer_cache_id_;
}
private:
void ReportUsageStats() const;
std::optional<uint32_t> transfer_cache_id_;
};
struct ImageInfo {
ImageInfo();
explicit ImageInfo(const SkImageInfo& rgba);
explicit ImageInfo(const SkYUVAPixmapInfo& yuva);
ImageInfo(const ImageInfo&);
ImageInfo& operator=(const ImageInfo&);
~ImageInfo();
std::optional<SkImageInfo> rgba;
std::optional<SkYUVAPixmapInfo> yuva;
size_t size = 0;
};
struct ImageData : public base::RefCountedThreadSafe<ImageData> {
ImageData(PaintImage::Id paint_image_id,
const gfx::ColorSpace& target_color_space,
PaintFlags::FilterQuality quality,
int upload_scale_mip_level,
bool needs_mips,
bool is_bitmap_backed,
bool speculative_decode,
base::span<ImageInfo, kAuxImageCount> image_info);
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;
}
}
size_t GetTotalSize() const;
bool IsSpeculativeDecode() const {
return speculative_decode_usage_stats_.has_value();
}
bool SpeculativeDecodeHasMatched() const {
return IsSpeculativeDecode() &&
speculative_decode_usage_stats_->min_raster_mip_level < INT_MAX;
}
void RecordSpeculativeDecodeMatch(int mip_level);
void RecordSpeculativeDecodeRasterTaskTakeover();
const PaintImage::Id paint_image_id;
const gfx::ColorSpace target_color_space;
PaintFlags::FilterQuality quality;
int upload_scale_mip_level;
bool needs_mips = false;
bool is_bitmap_backed;
bool is_budgeted = false;
base::TimeTicks last_use;
const ImageInfo info;
const ImageInfo gainmap_info;
bool is_orphaned = false;
DecodedImageData decode;
UploadedImageData upload;
struct SpeculativeDecodeUsageStats {
int speculative_decode_mip_level = -1;
int min_raster_mip_level = INT_MAX;
bool raster_task_takeover = false;
};
std::optional<SpeculativeDecodeUsageStats> speculative_decode_usage_stats_;
private:
friend class base::RefCountedThreadSafe<ImageData>;
~ImageData();
};
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;
};
struct InUseCacheKeyHash;
struct InUseCacheKey {
InUseCacheKey(const DrawImage& draw_image, int mip_level);
int mip_level() const { return upload_scale_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;
gfx::ColorSpace target_color_space;
};
struct InUseCacheKeyHash {
size_t operator()(const InUseCacheKey&) const;
};
int CalculateUploadScaleMipLevel(const DrawImage& draw_image,
AuxImage aux_image) const;
InUseCacheKey InUseCacheKeyFromDrawImage(const DrawImage& draw_image) const;
scoped_refptr<TileTask> GetImageDecodeTaskAndRef(
ClientId client_id,
const DrawImage& image,
const TracingInfo& tracing_info,
TaskType task_type) EXCLUSIVE_LOCKS_REQUIRED(lock_);
TaskResult GetTaskForImageAndRefInternal(ClientId client_id,
const DrawImage& image,
const TracingInfo& tracing_info,
TaskType task_type,
bool speculative);
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_);
void OwnershipChanged(const DrawImage& draw_image, ImageData* image_data)
EXCLUSIVE_LOCKS_REQUIRED(lock_);
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_);
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_);
scoped_refptr<GpuImageDecodeCache::ImageData> CreateImageData(
const DrawImage& image,
bool speculative_decode);
void WillAddCacheEntry(const DrawImage& draw_image)
EXCLUSIVE_LOCKS_REQUIRED(lock_);
std::tuple<SkImageInfo, int> CreateImageInfoForDrawImage(
const DrawImage& draw_image,
AuxImage aux_image) const;
ImageData* GetImageDataForDrawImage(
const DrawImage& image,
const InUseCacheKey& key,
bool record_speculative_decode_stats = false)
EXCLUSIVE_LOCKS_REQUIRED(lock_);
bool IsCompatible(const ImageData* image_data,
const DrawImage& draw_image) const;
void DeleteImage(ImageData* image_data) EXCLUSIVE_LOCKS_REQUIRED(lock_);
void UnlockImage(ImageData* image_data) EXCLUSIVE_LOCKS_REQUIRED(lock_);
enum class HaveContextLock { kYes, kNo };
bool TryLockImage(HaveContextLock have_context_lock,
const DrawImage& draw_image,
ImageData* data) EXCLUSIVE_LOCKS_REQUIRED(lock_);
void UploadImageIfNecessary(const DrawImage& draw_image,
ImageData* image_data)
EXCLUSIVE_LOCKS_REQUIRED(lock_);
void RunPendingContextThreadOperations() EXCLUSIVE_LOCKS_REQUIRED(lock_);
void CheckContextLockAcquiredIfNecessary();
sk_sp<SkColorSpace> ColorSpaceForImageDecode(const DrawImage& image) const;
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_);
void MaybePurgeOldCacheEntries() EXCLUSIVE_LOCKS_REQUIRED(lock_);
void PostPurgeOldCacheEntriesTask() EXCLUSIVE_LOCKS_REQUIRED(lock_);
bool DoPurgeOldCacheEntries(base::TimeDelta max_age)
EXCLUSIVE_LOCKS_REQUIRED(lock_);
bool TryFlushPendingWork() NO_THREAD_SAFETY_ANALYSIS;
void PurgeOldCacheEntriesCallback() LOCKS_EXCLUDED(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 has_pending_purge_task_;
}
const SkColorType color_type_;
raw_ptr<viz::RasterContextProvider> context_;
int max_texture_size_ = 0;
const PaintImage::GeneratorClientId generator_client_id_;
SkYUVAPixmapInfo::SupportedDataTypes yuva_supported_data_types_;
const bool enable_clipped_image_scaling_;
scoped_refptr<base::SequencedTaskRunner> task_runner_ = nullptr;
mutable base::Lock lock_;
bool has_pending_purge_task_ GUARDED_BY(lock_) = false;
PersistentCache persistent_cache_ GUARDED_BY(lock_);
size_t persistent_cache_memory_size_ GUARDED_BY(lock_) = 0;
struct CacheEntries {
std::array<PaintImage::ContentId, 2> content_ids = {
PaintImage::kInvalidContentId, PaintImage::kInvalidContentId};
size_t count = 0u;
};
base::flat_map<PaintImage::Id, CacheEntries> paint_image_entries_
GUARDED_BY(lock_);
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;
RAW_PTR_EXCLUSION RasterDarkModeFilter* const dark_mode_filter_;
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::AsyncMemoryPressureListenerRegistration>
memory_pressure_listener_registration_;
base::WeakPtrFactory<GpuImageDecodeCache> weak_ptr_factory_{this};
};
}
#endif