#include "cc/tiles/software_image_decode_cache.h"
#include <inttypes.h>
#include <stdint.h>
#include <algorithm>
#include <string>
#include <utility>
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/ostream_operators.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/memory_dump_manager.h"
#include "cc/base/devtools_instrumentation.h"
#include "cc/base/histograms.h"
#include "cc/raster/tile_task.h"
#include "cc/tiles/mipmap_util.h"
#include "ui/gfx/geometry/skia_conversions.h"
using base::trace_event::MemoryAllocatorDump;
using base::trace_event::MemoryDumpLevelOfDetail;
namespace cc {
namespace {
constexpr size_t kNormalMaxItemsInCacheForSoftware = 1000;
class AutoRemoveKeyFromTaskMap {
public:
AutoRemoveKeyFromTaskMap(
std::unordered_map<SoftwareImageDecodeCache::CacheKey,
scoped_refptr<TileTask>,
SoftwareImageDecodeCache::CacheKeyHash>* task_map,
const SoftwareImageDecodeCache::CacheKey& key)
: task_map_(task_map), key_(key) {}
~AutoRemoveKeyFromTaskMap() { task_map_->erase(*key_); }
private:
raw_ptr<std::unordered_map<SoftwareImageDecodeCache::CacheKey,
scoped_refptr<TileTask>,
SoftwareImageDecodeCache::CacheKeyHash>>
task_map_;
const raw_ref<const SoftwareImageDecodeCache::CacheKey> key_;
};
class SoftwareImageDecodeTaskImpl : public TileTask {
public:
SoftwareImageDecodeTaskImpl(
SoftwareImageDecodeCache* cache,
const SoftwareImageDecodeCache::CacheKey& image_key,
const PaintImage& paint_image,
ImageDecodeCache::TaskType task_type,
const ImageDecodeCache::TracingInfo& tracing_info)
: TileTask(TileTask::SupportsConcurrentExecution::kYes,
TileTask::SupportsBackgroundThreadPriority::kNo),
cache_(cache),
image_key_(image_key),
paint_image_(paint_image),
task_type_(task_type),
tracing_info_(tracing_info) {}
SoftwareImageDecodeTaskImpl(const SoftwareImageDecodeTaskImpl&) = delete;
SoftwareImageDecodeTaskImpl& operator=(const SoftwareImageDecodeTaskImpl&) =
delete;
void RunOnWorkerThread() override {
TRACE_EVENT2("cc", "SoftwareImageDecodeTaskImpl::RunOnWorkerThread", "mode",
"software", "source_prepare_tiles_id",
tracing_info_.prepare_tiles_id);
const auto* image_metadata = paint_image_.GetImageHeaderMetadata();
const ImageType image_type =
image_metadata ? image_metadata->image_type : ImageType::kInvalid;
devtools_instrumentation::ScopedImageDecodeTask image_decode_task(
paint_image_.GetSwSkImage().get(),
devtools_instrumentation::ScopedImageDecodeTask::DecodeType::kSoftware,
ImageDecodeCache::ToScopedTaskType(task_type_),
ImageDecodeCache::ToScopedImageType(image_type));
SoftwareImageDecodeCache::TaskProcessingResult result =
cache_->DecodeImageInTask(image_key_, paint_image_, task_type_);
if (result != SoftwareImageDecodeCache::TaskProcessingResult::kFullDecode)
image_decode_task.SuppressMetrics();
}
bool IsRasterTask() const override {
return task_type_ == ImageDecodeCache::TaskType::kInRaster;
}
void OnTaskCompleted() override {
cache_->OnImageDecodeTaskCompleted(image_key_, task_type_);
}
bool TaskContainsLCPCandidateImages() const override {
if (!HasCompleted() && paint_image_.may_be_lcp_candidate())
return true;
return TileTask::TaskContainsLCPCandidateImages();
}
protected:
~SoftwareImageDecodeTaskImpl() override = default;
private:
raw_ptr<SoftwareImageDecodeCache> cache_;
SoftwareImageDecodeCache::CacheKey image_key_;
PaintImage paint_image_;
ImageDecodeCache::TaskType task_type_;
const ImageDecodeCache::TracingInfo tracing_info_;
};
SkSize GetScaleAdjustment(const SoftwareImageDecodeCache::CacheKey& key) {
if (key.type() != SoftwareImageDecodeCache::CacheKey::kSubrectAndScale) {
return SkSize::Make(1.f, 1.f);
} else {
return MipMapUtil::GetScaleAdjustmentForSize(key.src_rect().size(),
key.target_size());
}
}
PaintFlags::FilterQuality GetDecodedFilterQuality(
const SoftwareImageDecodeCache::CacheKey& key) {
return key.is_nearest_neighbor() ? PaintFlags::FilterQuality::kNone
: PaintFlags::FilterQuality::kLow;
}
}
SoftwareImageDecodeCache::SoftwareImageDecodeCache(
SkColorType color_type,
size_t locked_memory_limit_bytes)
: decoded_images_(ImageLRUCache::NO_AUTO_EVICT),
locked_images_budget_(locked_memory_limit_bytes),
color_type_(color_type),
generator_client_id_(PaintImage::GetNextGeneratorClientId()),
max_items_in_cache_(kNormalMaxItemsInCacheForSoftware) {
DCHECK_NE(generator_client_id_, PaintImage::kDefaultGeneratorClientId);
if (base::SingleThreadTaskRunner::HasCurrentDefault()) {
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
this, "cc::SoftwareImageDecodeCache",
base::SingleThreadTaskRunner::GetCurrentDefault());
}
}
SoftwareImageDecodeCache::~SoftwareImageDecodeCache() {
base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
this);
}
ImageDecodeCache::TaskResult SoftwareImageDecodeCache::GetTaskForImageAndRef(
ClientId client_id,
const DrawImage& image,
const TracingInfo& tracing_info) {
DCHECK_EQ(client_id, ImageDecodeCache::kDefaultClientId)
<< "SoftwareImageDecodeCache cannot be shared between multiple clients.";
return GetTaskForImageAndRefInternal(image, tracing_info, TaskType::kInRaster,
false);
}
ImageDecodeCache::TaskResult
SoftwareImageDecodeCache::GetOutOfRasterDecodeTaskForImageAndRef(
ClientId client_id,
const DrawImage& image,
bool speculative) {
DCHECK_EQ(client_id, ImageDecodeCache::kDefaultClientId)
<< "SoftwareImageDecodeCache cannot be shared between multiple clients.";
return GetTaskForImageAndRefInternal(image, TracingInfo(0, TilePriority::NOW),
TaskType::kOutOfRaster, speculative);
}
ImageDecodeCache::TaskResult
SoftwareImageDecodeCache::GetTaskForImageAndRefInternal(
const DrawImage& image,
const TracingInfo& tracing_info,
TaskType task_type,
bool speculative) {
CacheKey key = CacheKey::FromDrawImage(
image, GetColorTypeForPaintImage(image.target_color_params(),
image.paint_image()));
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SoftwareImageDecodeCache::GetTaskForImageAndRefInternal", "key",
key.ToString());
if (key.target_size().IsEmpty())
return TaskResult(false, false);
if (!UseCacheForDrawImage(image))
return TaskResult(false, false);
base::AutoLock lock(lock_);
bool new_image_fits_in_memory =
locked_images_budget_.AvailableMemoryBytes() >= key.locked_bytes();
auto decoded_it = decoded_images_.Get(key);
CacheEntry* cache_entry = nullptr;
if (decoded_it == decoded_images_.end()) {
if (!new_image_fits_in_memory)
return TaskResult(false, true);
cache_entry = AddCacheEntry(key);
} else {
cache_entry = decoded_it->second.get();
}
DCHECK(cache_entry);
if (!cache_entry->is_budgeted) {
if (!new_image_fits_in_memory) {
return TaskResult(false, true);
}
AddBudgetForImage(key, cache_entry);
}
DCHECK(cache_entry->is_budgeted);
++cache_entry->ref_count;
if (cache_entry->is_locked)
return TaskResult(true, false);
scoped_refptr<TileTask>& task = task_type == TaskType::kInRaster
? cache_entry->in_raster_task
: cache_entry->out_of_raster_task;
if (!task) {
++cache_entry->ref_count;
task = base::MakeRefCounted<SoftwareImageDecodeTaskImpl>(
this, key, image.paint_image(), task_type, tracing_info);
if (task_type == TaskType::kInRaster && cache_entry->out_of_raster_task) {
if (cache_entry->out_of_raster_task->state().IsNew()) {
task->SetExternalDependent(cache_entry->out_of_raster_task);
} else {
cache_entry->out_of_raster_task->SetExternalDependent(task);
}
} else if (task_type == TaskType::kOutOfRaster &&
cache_entry->in_raster_task &&
!cache_entry->in_raster_task->HasCompleted()) {
cache_entry->in_raster_task->SetExternalDependent(task);
}
}
return TaskResult(task);
}
void SoftwareImageDecodeCache::AddBudgetForImage(const CacheKey& key,
CacheEntry* entry) {
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SoftwareImageDecodeCache::AddBudgetForImage", "key",
key.ToString());
DCHECK(!entry->is_budgeted);
DCHECK_GE(locked_images_budget_.AvailableMemoryBytes(), key.locked_bytes());
locked_images_budget_.AddUsage(key.locked_bytes());
entry->is_budgeted = true;
}
void SoftwareImageDecodeCache::RemoveBudgetForImage(const CacheKey& key,
CacheEntry* entry) {
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SoftwareImageDecodeCache::RemoveBudgetForImage", "key",
key.ToString());
DCHECK(entry->is_budgeted);
locked_images_budget_.SubtractUsage(key.locked_bytes());
entry->is_budgeted = false;
}
void SoftwareImageDecodeCache::UnrefImage(const DrawImage& image) {
const CacheKey& key = CacheKey::FromDrawImage(
image, GetColorTypeForPaintImage(image.target_color_params(),
image.paint_image()));
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SoftwareImageDecodeCache::UnrefImage", "key", key.ToString());
base::AutoLock lock(lock_);
UnrefImage(key);
}
void SoftwareImageDecodeCache::UnrefImage(const CacheKey& key) {
auto decoded_image_it = decoded_images_.Peek(key);
CHECK(decoded_image_it != decoded_images_.end());
auto* entry = decoded_image_it->second.get();
DCHECK_GT(entry->ref_count, 0);
if (--entry->ref_count == 0) {
if (entry->is_budgeted)
RemoveBudgetForImage(key, entry);
if (entry->is_locked)
entry->Unlock();
ReduceCacheUsageUntilWithinLimit(max_items_in_cache_);
}
}
SoftwareImageDecodeCache::TaskProcessingResult
SoftwareImageDecodeCache::DecodeImageInTask(const CacheKey& key,
const PaintImage& paint_image,
TaskType task_type) {
TRACE_EVENT1("cc,benchmark", "SoftwareImageDecodeCache::DecodeImageInTask",
"key", key.ToString());
base::AutoLock lock(lock_);
auto image_it = decoded_images_.Peek(key);
CHECK(image_it != decoded_images_.end());
auto* cache_entry = image_it->second.get();
DCHECK_GT(cache_entry->ref_count, 0);
DCHECK(cache_entry->is_budgeted);
TaskProcessingResult result =
DecodeImageIfNecessary(key, paint_image, cache_entry);
DCHECK(cache_entry->decode_failed || cache_entry->is_locked);
return result;
}
SoftwareImageDecodeCache::TaskProcessingResult
SoftwareImageDecodeCache::DecodeImageIfNecessary(const CacheKey& key,
const PaintImage& paint_image,
CacheEntry* entry) {
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SoftwareImageDecodeCache::DecodeImageIfNecessary", "key",
key.ToString());
DCHECK_GT(entry->ref_count, 0);
if (key.target_size().IsEmpty())
entry->decode_failed = true;
if (entry->decode_failed)
return TaskProcessingResult::kCancelled;
if (entry->memory) {
if (entry->is_locked)
return TaskProcessingResult::kLockOnly;
bool lock_succeeded = entry->Lock();
if (lock_succeeded)
return TaskProcessingResult::kLockOnly;
}
std::unique_ptr<CacheEntry> local_cache_entry;
if (key.type() == CacheKey::kOriginal) {
base::AutoUnlock release(lock_);
local_cache_entry = Utils::DoDecodeImage(
key, paint_image,
GetColorTypeForPaintImage(key.target_color_params(), paint_image),
generator_client_id_,
base::BindOnce(&SoftwareImageDecodeCache::ClearCache,
base::Unretained(this)));
} else {
std::optional<CacheKey> candidate_key = FindCachedCandidate(key);
SkISize desired_size = gfx::SizeToSkISize(key.target_size());
const bool should_decode_to_scale =
!candidate_key &&
key.src_rect() ==
gfx::Rect(paint_image.width(), paint_image.height()) &&
paint_image.GetSupportedDecodeSize(desired_size) == desired_size;
DCHECK(!should_decode_to_scale || !key.is_nearest_neighbor());
if (should_decode_to_scale) {
base::AutoUnlock release(lock_);
local_cache_entry = Utils::DoDecodeImage(
key, paint_image,
GetColorTypeForPaintImage(key.target_color_params(), paint_image),
generator_client_id_,
base::BindOnce(&SoftwareImageDecodeCache::ClearCache,
base::Unretained(this)));
}
if (!should_decode_to_scale && !candidate_key) {
auto src_rect =
key.type() == CacheKey::kSubrectOriginal
? SkIRect::MakeWH(paint_image.width(), paint_image.height())
: gfx::RectToSkIRect(key.src_rect());
DrawImage candidate_draw_image(
paint_image, false, src_rect, PaintFlags::FilterQuality::kNone,
SkM44(), key.frame_key().frame_index(), key.target_color_params());
candidate_key.emplace(CacheKey::FromDrawImage(
candidate_draw_image,
GetColorTypeForPaintImage(key.target_color_params(), paint_image)));
}
if (candidate_key) {
CHECK(*candidate_key != key) << key.ToString();
auto decoded_draw_image =
GetDecodedImageForDrawInternal(*candidate_key, paint_image);
if (!decoded_draw_image.image()) {
local_cache_entry = nullptr;
} else {
base::AutoUnlock release(lock_);
local_cache_entry = Utils::GenerateCacheEntryFromCandidate(
key, decoded_draw_image,
candidate_key->type() == CacheKey::kOriginal,
GetColorTypeForPaintImage(key.target_color_params(), paint_image));
}
UnrefImage(*candidate_key);
}
}
if (!local_cache_entry) {
entry->decode_failed = true;
return TaskProcessingResult::kCancelled;
}
if (entry->memory) {
DCHECK(entry->is_locked);
local_cache_entry->Unlock();
} else {
local_cache_entry->MoveImageMemoryTo(entry);
DCHECK(entry->is_locked);
}
return TaskProcessingResult::kFullDecode;
}
std::optional<SoftwareImageDecodeCache::CacheKey>
SoftwareImageDecodeCache::FindCachedCandidate(const CacheKey& key) {
auto image_keys_it = frame_key_to_image_keys_.find(key.frame_key());
CHECK(image_keys_it != frame_key_to_image_keys_.end());
auto& available_keys = image_keys_it->second;
std::sort(available_keys.begin(), available_keys.end(),
[](const CacheKey& one, const CacheKey& two) {
return one.target_size().width() < two.target_size().width() &&
one.target_size().height() < two.target_size().height();
});
for (auto& available_key : available_keys) {
if (available_key.src_rect() != key.src_rect())
continue;
if (available_key.target_size().width() < key.target_size().width() ||
available_key.target_size().height() < key.target_size().height()) {
continue;
}
auto image_it = decoded_images_.Peek(available_key);
CHECK(image_it != decoded_images_.end());
auto* available_entry = image_it->second.get();
if (available_entry->is_locked || available_entry->Lock()) {
return available_key;
}
}
return std::nullopt;
}
bool SoftwareImageDecodeCache::UseCacheForDrawImage(
const DrawImage& draw_image) const {
PaintImage paint_image = draw_image.paint_image();
if (paint_image.IsTextureBacked())
return false;
if (paint_image.IsLazyGenerated())
return true;
const gfx::ColorSpace& dst_color_space = draw_image.target_color_space();
if (dst_color_space.IsValid() &&
dst_color_space != gfx::ColorSpace::CreateSRGB()) {
return true;
}
return false;
}
ImageDecodeCache::ClientId SoftwareImageDecodeCache::GenerateClientId() {
ClientId next_client_id = ImageDecodeCache::GenerateClientId();
DCHECK_EQ(ImageDecodeCache::kDefaultClientId, next_client_id);
return next_client_id;
}
DecodedDrawImage SoftwareImageDecodeCache::GetDecodedImageForDraw(
const DrawImage& draw_image) {
DCHECK(UseCacheForDrawImage(draw_image));
base::AutoLock hold(lock_);
return GetDecodedImageForDrawInternal(
CacheKey::FromDrawImage(draw_image, GetColorTypeForPaintImage(
draw_image.target_color_params(),
draw_image.paint_image())),
draw_image.paint_image());
}
DecodedDrawImage SoftwareImageDecodeCache::GetDecodedImageForDrawInternal(
const CacheKey& key,
const PaintImage& paint_image) {
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SoftwareImageDecodeCache::GetDecodedImageForDrawInternal",
"key", key.ToString());
auto decoded_it = decoded_images_.Get(key);
CacheEntry* cache_entry = nullptr;
if (decoded_it == decoded_images_.end())
cache_entry = AddCacheEntry(key);
else
cache_entry = decoded_it->second.get();
++cache_entry->ref_count;
DecodeImageIfNecessary(key, paint_image, cache_entry);
auto decoded_image = cache_entry->image();
if (!decoded_image)
return DecodedDrawImage();
auto decoded_draw_image =
DecodedDrawImage(std::move(decoded_image), cache_entry->gainmap_image(),
cache_entry->hdr_metadata(), nullptr,
cache_entry->src_rect_offset(), GetScaleAdjustment(key),
GetDecodedFilterQuality(key), cache_entry->is_budgeted);
return decoded_draw_image;
}
void SoftwareImageDecodeCache::DrawWithImageFinished(
const DrawImage& image,
const DecodedDrawImage& decoded_image) {
DCHECK(UseCacheForDrawImage(image));
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SoftwareImageDecodeCache::DrawWithImageFinished", "key",
CacheKey::FromDrawImage(
image, GetColorTypeForPaintImage(image.target_color_params(),
image.paint_image()))
.ToString());
UnrefImage(image);
}
void SoftwareImageDecodeCache::ReduceCacheUsageUntilWithinLimit(size_t limit) {
TRACE_EVENT0("cc",
"SoftwareImageDecodeCache::ReduceCacheUsageUntilWithinLimit");
for (auto it = decoded_images_.rbegin();
decoded_images_.size() > limit && it != decoded_images_.rend();) {
if (it->second->ref_count != 0) {
++it;
continue;
}
const CacheKey& key = it->first;
auto vector_it = frame_key_to_image_keys_.find(key.frame_key());
auto item_it = std::ranges::find(vector_it->second, key);
CHECK(item_it != vector_it->second.end());
vector_it->second.erase(item_it);
if (vector_it->second.empty())
frame_key_to_image_keys_.erase(vector_it);
it = decoded_images_.Erase(it);
}
}
void SoftwareImageDecodeCache::ReduceCacheUsage() {
base::AutoLock lock(lock_);
ReduceCacheUsageUntilWithinLimit(max_items_in_cache_);
}
void SoftwareImageDecodeCache::ClearCache() {
base::AutoLock lock(lock_);
ReduceCacheUsageUntilWithinLimit(0);
}
size_t SoftwareImageDecodeCache::GetMaximumMemoryLimitBytes() const {
return locked_images_budget_.total_limit_bytes();
}
void SoftwareImageDecodeCache::OnImageDecodeTaskCompleted(const CacheKey& key,
TaskType task_type) {
base::AutoLock hold(lock_);
auto image_it = decoded_images_.Peek(key);
CHECK(image_it != decoded_images_.end());
CacheEntry* cache_entry = image_it->second.get();
UMA_HISTOGRAM_BOOLEAN("Compositing.DecodeLCPCandidateImage.Software",
key.may_be_lcp_candidate());
auto& task = task_type == TaskType::kInRaster
? cache_entry->in_raster_task
: cache_entry->out_of_raster_task;
task = nullptr;
UnrefImage(key);
}
bool SoftwareImageDecodeCache::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) {
base::AutoLock lock(lock_);
if (args.level_of_detail == MemoryDumpLevelOfDetail::kBackground) {
std::string dump_name = base::StringPrintf(
"cc/image_memory/cache_0x%" PRIXPTR, reinterpret_cast<uintptr_t>(this));
MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(dump_name);
dump->AddScalar("locked_size", MemoryAllocatorDump::kUnitsBytes,
locked_images_budget_.GetCurrentUsageSafe());
} else {
for (const auto& image_pair : decoded_images_) {
int image_id = static_cast<int>(image_pair.first.frame_key().hash());
CacheEntry* entry = image_pair.second.get();
DCHECK(entry);
if (!entry->memory)
continue;
std::string dump_name = base::StringPrintf(
"cc/image_memory/cache_0x%" PRIXPTR "/%s/image_%" PRIu64 "_id_%d",
reinterpret_cast<uintptr_t>(this),
entry->is_budgeted ? "budgeted" : "at_raster", entry->tracing_id(),
image_id);
MemoryAllocatorDump* dump =
entry->memory->CreateMemoryAllocatorDump(dump_name.c_str(), pmd);
DCHECK(dump);
size_t locked_bytes =
entry->is_locked ? image_pair.first.locked_bytes() : 0u;
dump->AddScalar("locked_size", MemoryAllocatorDump::kUnitsBytes,
locked_bytes);
}
}
return true;
}
SoftwareImageDecodeCache::CacheEntry* SoftwareImageDecodeCache::AddCacheEntry(
const CacheKey& key) {
frame_key_to_image_keys_[key.frame_key()].push_back(key);
auto it = decoded_images_.Put(key, std::make_unique<CacheEntry>());
it->second.get()->mark_cached();
return it->second.get();
}
size_t SoftwareImageDecodeCache::GetNumCacheEntriesForTesting() {
base::AutoLock lock(lock_);
return decoded_images_.size();
}
size_t SoftwareImageDecodeCache::GetMaxNumCacheEntriesForTesting() {
return kNormalMaxItemsInCacheForSoftware;
}
SkColorType SoftwareImageDecodeCache::GetColorTypeForPaintImage(
const TargetColorParams& target_color_params,
const PaintImage& paint_image) {
const gfx::ColorSpace& target_color_space = target_color_params.color_space;
if (paint_image.GetColorType() == kRGBA_F16_SkColorType &&
paint_image.GetContentColorUsage() == gfx::ContentColorUsage::kHDR &&
target_color_space.IsHDR()) {
return kRGBA_F16_SkColorType;
}
return color_type_;
}
SoftwareImageDecodeCache::MemoryBudget::MemoryBudget(size_t limit_bytes)
: limit_bytes_(limit_bytes), current_usage_bytes_(0u) {}
size_t SoftwareImageDecodeCache::MemoryBudget::AvailableMemoryBytes() const {
size_t usage = GetCurrentUsageSafe();
return usage >= limit_bytes_ ? 0u : (limit_bytes_ - usage);
}
void SoftwareImageDecodeCache::MemoryBudget::AddUsage(size_t usage) {
current_usage_bytes_ += usage;
}
void SoftwareImageDecodeCache::MemoryBudget::SubtractUsage(size_t usage) {
DCHECK_GE(current_usage_bytes_.ValueOrDefault(0u), usage);
current_usage_bytes_ -= usage;
}
void SoftwareImageDecodeCache::MemoryBudget::ResetUsage() {
current_usage_bytes_ = 0;
}
size_t SoftwareImageDecodeCache::MemoryBudget::GetCurrentUsageSafe() const {
return current_usage_bytes_.ValueOrDie();
}
}