#include "cc/tiles/software_image_decode_cache_utils.h"
#include <algorithm>
#include <sstream>
#include <utility>
#include "base/atomic_sequence_num.h"
#include "base/functional/callback_helpers.h"
#include "base/hash/hash.h"
#include "base/memory/discardable_memory_allocator.h"
#include "base/metrics/histogram_macros.h"
#include "base/process/memory.h"
#include "base/trace_event/trace_event.h"
#include "cc/paint/paint_flags.h"
#include "cc/paint/tone_map_util.h"
#include "cc/tiles/mipmap_util.h"
#include "skia/ext/geometry.h"
#include "third_party/skia/include/core/SkColorSpace.h"
#include "third_party/skia/include/core/SkImage.h"
#include "ui/gfx/geometry/skia_conversions.h"
namespace cc {
namespace {
const size_t kMemoryThresholdToSubrect = 64 * 1024 * 1024;
const int kMinDimensionToSubrect = 4 * 1024;
const float kMemoryRatioToSubrect = 0.5f;
base::AtomicSequenceNumber g_next_tracing_id_;
gfx::Rect GetSrcRect(const DrawImage& image) {
const SkIRect& src_rect = image.src_rect();
int x = std::max(0, src_rect.x());
int y = std::max(0, src_rect.y());
int right = std::min(image.paint_image().width(), src_rect.right());
int bottom = std::min(image.paint_image().height(), src_rect.bottom());
if (x >= right || y >= bottom)
return gfx::Rect();
return gfx::Rect(x, y, right - x, bottom - y);
}
SkRect ComputeGainmapRect(SkISize base_image_dimensions,
SkISize gain_image_dimensions,
SkRect base_rect) {
SkRect base_image_rect =
SkRect::MakeSize(SkSize::Make(base_image_dimensions));
SkRect gain_image_rect =
SkRect::MakeSize(SkSize::Make(gain_image_dimensions));
return skia::ScaleSkRectProportional(gain_image_rect, base_image_rect,
base_rect);
}
void AllocateDiscardableSkImage(
const SkImageInfo& info,
const SkImageInfo& gainmap_info,
base::OnceClosure on_no_memory,
std::unique_ptr<base::DiscardableMemory>& memory,
sk_sp<SkImage>& image,
sk_sp<SkImage>& gainmap_image) {
image = nullptr;
gainmap_image = nullptr;
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"), "AllocateDiscardable");
size_t size = info.minRowBytes() * info.height() +
gainmap_info.minRowBytes() * gainmap_info.height();
memory = base::DiscardableMemoryAllocator::GetInstance()
->AllocateLockedDiscardableMemoryWithRetryOrDie(
size, std::move(on_no_memory));
if (!memory->data()) {
return;
}
UNSAFE_BUFFERS(
base::span<uint8_t> memory_as_span(memory->data_as<uint8_t>(), size));
auto gainmap_image_span = memory_as_span.subspan(
info.minRowBytes() * info.height(),
gainmap_info.minRowBytes() * gainmap_info.height());
SkPixmap pixmap(info, memory_as_span.data(), info.minRowBytes());
image = SkImages::RasterFromPixmap(
pixmap, [](const void* pixels, void* context) {}, nullptr);
if (!gainmap_image_span.empty()) {
SkPixmap gainmap_pixmap(gainmap_info, gainmap_image_span.data(),
gainmap_info.minRowBytes());
gainmap_image = SkImages::RasterFromPixmap(
gainmap_pixmap, [](const void* pixels, void* context) {}, nullptr);
}
}
}
std::unique_ptr<SoftwareImageDecodeCacheUtils::CacheEntry>
SoftwareImageDecodeCacheUtils::DoDecodeImage(
const CacheKey& key,
const PaintImage& paint_image,
SkColorType color_type,
PaintImage::GeneratorClientId client_id,
base::OnceClosure on_no_memory) {
const SkISize target_size =
SkISize::Make(key.target_size().width(), key.target_size().height());
DCHECK(target_size == paint_image.GetSupportedDecodeSize(target_size));
SkImageInfo target_info =
SkImageInfo::Make(target_size, color_type, kPremul_SkAlphaType,
key.target_color_params().color_space.ToSkColorSpace());
SkImageInfo target_gainmap_info;
if (paint_image.HasGainmapInfo()) {
target_gainmap_info = SkImageInfo::Make(
paint_image.GetSupportedDecodeSize(target_size, AuxImage::kGainmap),
color_type, kPremul_SkAlphaType);
}
sk_sp<SkImage> target_image;
sk_sp<SkImage> target_gainmap_image;
std::unique_ptr<base::DiscardableMemory> target_pixels;
AllocateDiscardableSkImage(target_info, target_gainmap_info,
std::move(on_no_memory), target_pixels,
target_image, target_gainmap_image);
if (!target_image) {
return nullptr;
}
SkPixmap target_pixmap;
target_image->peekPixels(&target_pixmap);
if (key.target_color_params().color_space.GetTransferID() ==
gfx::ColorSpace::TransferID::PQ ||
key.target_color_params().color_space.GetTransferID() ==
gfx::ColorSpace::TransferID::HLG) {
target_pixmap.setColorSpace(paint_image.GetSkImageInfo().refColorSpace());
}
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SoftwareImageDecodeCacheUtils::DoDecodeImage - "
"decode");
bool result = paint_image.Decode(target_pixmap, key.frame_key().frame_index(),
AuxImage::kDefault, client_id);
if (!result) {
target_pixels->Unlock();
return nullptr;
}
if (target_gainmap_image) {
SkPixmap target_gainmap_pixmap;
target_gainmap_image->peekPixels(&target_gainmap_pixmap);
bool gainmap_result =
paint_image.Decode(target_gainmap_pixmap, key.frame_key().frame_index(),
AuxImage::kGainmap, client_id);
if (!gainmap_result) {
target_gainmap_image = nullptr;
}
}
return std::make_unique<CacheEntry>(
target_image, target_gainmap_image, paint_image.GetHDRMetadata(),
std::move(target_pixels), SkSize::Make(0, 0));
}
std::unique_ptr<SoftwareImageDecodeCacheUtils::CacheEntry>
SoftwareImageDecodeCacheUtils::GenerateCacheEntryFromCandidate(
const CacheKey& key,
const DecodedDrawImage& candidate_image,
bool needs_extract_subset,
SkColorType color_type) {
TRACE_EVENT0(
TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SoftwareImageDecodeCacheUtils::GenerateCacheEntryFromCandidate");
SkPixmap decoded_pixmap;
bool result = candidate_image.image()->peekPixels(&decoded_pixmap);
DCHECK(result) << key.ToString();
sk_sp<SkImage> decoded_gainmap_image = candidate_image.gainmap_image();
SkIRect src_rect = SkIRect::MakeSize(decoded_pixmap.dimensions());
if (needs_extract_subset) {
result = src_rect.intersect(gfx::RectToSkIRect(key.src_rect()));
DCHECK(result) << key.ToString();
}
SkPixmap decoded_pixmap_sub_rect;
result = decoded_pixmap.extractSubset(&decoded_pixmap_sub_rect, src_rect);
DCHECK(result) << key.ToString();
SkRect src_gainmap_rect;
if (decoded_gainmap_image) {
src_gainmap_rect = ComputeGainmapRect(decoded_pixmap.dimensions(),
decoded_gainmap_image->dimensions(),
SkRect::Make(src_rect));
}
SkImageInfo target_info =
SkImageInfo::Make(gfx::SizeToSkISize(key.target_size()), color_type,
kPremul_SkAlphaType, decoded_pixmap.refColorSpace());
SkImageInfo target_gainmap_info;
if (decoded_gainmap_image) {
SkISize target_gainmap_max_size =
SkSize(src_gainmap_rect.width(), src_gainmap_rect.height()).toCeil();
SkISize target_gainmap_size = SkISize::Make(
std::min(key.target_size().width(), target_gainmap_max_size.width()),
std::min(key.target_size().height(), target_gainmap_max_size.height()));
target_gainmap_info =
decoded_gainmap_image->imageInfo().makeDimensions(target_gainmap_size);
}
sk_sp<SkImage> target_image;
sk_sp<SkImage> target_gainmap_image;
std::unique_ptr<base::DiscardableMemory> target_pixels;
AllocateDiscardableSkImage(target_info, target_gainmap_info,
base::DoNothing(), target_pixels, target_image,
target_gainmap_image);
if (!target_image) {
return nullptr;
}
SkPixmap target_pixmap;
target_image->peekPixels(&target_pixmap);
result = decoded_pixmap_sub_rect.scalePixels(
target_pixmap, PaintFlags::FilterQualityToSkSamplingOptions(
PaintFlags::FilterQuality::kMedium));
DCHECK(result) << key.ToString();
if (target_gainmap_image) {
SkPixmap target_gainmap_pixmap;
target_gainmap_image->peekPixels(&target_gainmap_pixmap);
auto canvas = SkCanvas::MakeRasterDirect(
target_gainmap_pixmap.info(), target_gainmap_pixmap.writable_addr(),
target_gainmap_pixmap.rowBytes());
canvas->drawImageRect(
decoded_gainmap_image, src_gainmap_rect,
SkRect::MakeSize(SkSize::Make(target_gainmap_info.dimensions())),
SkSamplingOptions(SkFilterMode::kLinear), nullptr,
SkCanvas::kStrict_SrcRectConstraint);
}
return std::make_unique<CacheEntry>(
target_image, target_gainmap_image, candidate_image.hdr_metadata(),
std::move(target_pixels),
SkSize::Make(-key.src_rect().x(), -key.src_rect().y()));
}
SoftwareImageDecodeCacheUtils::CacheKey
SoftwareImageDecodeCacheUtils::CacheKey::FromDrawImage(const DrawImage& image,
SkColorType color_type) {
DCHECK(!image.paint_image().IsTextureBacked());
const auto& paint_image = image.paint_image();
const PaintImage::FrameKey frame_key = image.frame_key();
const PaintImage::Id stable_id = paint_image.stable_id();
TargetColorParams target_color_params = image.target_color_params();
target_color_params.hdr_headroom = std::nullopt;
if (paint_image.HasGainmapInfo() ||
ToneMapUtil::UseGlobalToneMapFilter(paint_image.color_space())) {
if (paint_image.color_space()) {
target_color_params.color_space =
gfx::ColorSpace(*paint_image.color_space());
}
}
const SkSize& scale = image.scale();
const gfx::Rect& src_rect = GetSrcRect(image);
gfx::Size target_size(
SkScalarRoundToInt(std::abs(src_rect.width() * scale.width())),
SkScalarRoundToInt(std::abs(src_rect.height() * scale.height())));
if (target_size.IsEmpty()) {
return CacheKey(frame_key, stable_id, kSubrectAndScale, false,
paint_image.may_be_lcp_candidate(), src_rect, target_size,
target_color_params);
}
ProcessingType type = kOriginal;
bool is_nearest_neighbor =
image.filter_quality() == PaintFlags::FilterQuality::kNone;
int mip_level = MipMapUtil::GetLevelForSize(src_rect.size(), target_size);
if (is_nearest_neighbor || mip_level == 0 ||
!image.matrix_is_decomposable()) {
type = kOriginal;
target_size = gfx::Size(paint_image.width(), paint_image.height());
} else {
type = kSubrectAndScale;
target_size = MipMapUtil::GetSizeForLevel(src_rect.size(), mip_level);
}
if (type == kOriginal && (paint_image.width() >= kMinDimensionToSubrect ||
paint_image.height() >= kMinDimensionToSubrect)) {
base::CheckedNumeric<size_t> checked_original_size = 4u;
checked_original_size *= paint_image.width();
checked_original_size *= paint_image.height();
size_t original_size = checked_original_size.ValueOrDefault(
std::numeric_limits<size_t>::max());
base::CheckedNumeric<size_t> checked_src_rect_size = 4u;
checked_src_rect_size *= src_rect.width();
checked_src_rect_size *= src_rect.height();
size_t src_rect_size = checked_src_rect_size.ValueOrDefault(
std::numeric_limits<size_t>::max());
if (original_size > kMemoryThresholdToSubrect &&
src_rect_size <= original_size * kMemoryRatioToSubrect) {
type = kSubrectOriginal;
target_size = src_rect.size();
}
}
return CacheKey(frame_key, stable_id, type, is_nearest_neighbor,
image.paint_image().may_be_lcp_candidate(), src_rect,
target_size, target_color_params);
}
SoftwareImageDecodeCacheUtils::CacheKey::CacheKey(
PaintImage::FrameKey frame_key,
PaintImage::Id stable_id,
ProcessingType type,
bool is_nearest_neighbor,
bool may_be_lcp_candidate,
const gfx::Rect& src_rect,
const gfx::Size& target_size,
const TargetColorParams& target_color_params)
: frame_key_(frame_key),
stable_id_(stable_id),
type_(type),
is_nearest_neighbor_(is_nearest_neighbor),
may_be_lcp_candidate_(may_be_lcp_candidate),
src_rect_(src_rect),
target_size_(target_size),
target_color_params_(target_color_params) {
if (type == kOriginal) {
hash_ = frame_key_.hash();
} else {
uint64_t src_rect_hash = base::HashInts(
static_cast<uint64_t>(base::HashInts(src_rect_.x(), src_rect_.y())),
static_cast<uint64_t>(
base::HashInts(src_rect_.width(), src_rect_.height())));
uint64_t target_size_hash =
base::HashInts(target_size_.width(), target_size_.height());
hash_ = base::HashInts(base::HashInts(src_rect_hash, target_size_hash),
frame_key_.hash());
}
hash_ = base::HashInts(hash_, target_color_params.GetHash());
}
SoftwareImageDecodeCacheUtils::CacheKey::CacheKey(const CacheKey& other) =
default;
SoftwareImageDecodeCacheUtils::CacheKey&
SoftwareImageDecodeCacheUtils::CacheKey::operator=(const CacheKey& other) =
default;
std::string SoftwareImageDecodeCacheUtils::CacheKey::ToString() const {
std::ostringstream str;
str << "frame_key[" << frame_key_.ToString() << "]\ntype[";
switch (type_) {
case kOriginal:
str << "Original";
break;
case kSubrectOriginal:
str << "SubrectOriginal";
break;
case kSubrectAndScale:
str << "SubrectAndScale";
break;
}
str << "]\nis_nearest_neightbor[" << is_nearest_neighbor_ << "]\nsrc_rect["
<< src_rect_.ToString() << "]\ntarget_size[" << target_size_.ToString()
<< "]\ntarget_color_params[" << target_color_params_.ToString()
<< "]\nhash[" << hash_ << "]";
return str.str();
}
SoftwareImageDecodeCacheUtils::CacheEntry::CacheEntry()
: tracing_id_(g_next_tracing_id_.GetNext()) {}
SoftwareImageDecodeCacheUtils::CacheEntry::CacheEntry(
sk_sp<SkImage> image,
sk_sp<SkImage> gainmap_image,
const std::optional<gfx::HDRMetadata>& hdr_metadata,
std::unique_ptr<base::DiscardableMemory> in_memory,
const SkSize& src_rect_offset)
: is_locked(true),
memory(std::move(in_memory)),
image_(std::move(image)),
gainmap_image_(std::move(gainmap_image)),
hdr_metadata_(hdr_metadata),
src_rect_offset_(src_rect_offset),
tracing_id_(g_next_tracing_id_.GetNext()) {
DCHECK(memory);
}
SoftwareImageDecodeCacheUtils::CacheEntry::~CacheEntry() {
DCHECK(!is_locked);
}
void SoftwareImageDecodeCacheUtils::CacheEntry::MoveImageMemoryTo(
CacheEntry* entry) {
DCHECK(!is_budgeted);
DCHECK_EQ(ref_count, 0);
entry->decode_failed = decode_failed;
entry->is_locked = is_locked;
is_locked = false;
entry->memory = std::move(memory);
entry->src_rect_offset_ = std::move(src_rect_offset_);
entry->image_ = std::move(image_);
entry->gainmap_image_ = std::move(gainmap_image_);
entry->hdr_metadata_ = hdr_metadata_;
}
bool SoftwareImageDecodeCacheUtils::CacheEntry::Lock() {
if (!memory)
return false;
DCHECK(!is_locked);
bool success = memory->Lock();
if (!success) {
memory = nullptr;
return false;
}
is_locked = true;
return true;
}
void SoftwareImageDecodeCacheUtils::CacheEntry::Unlock() {
if (!memory)
return;
DCHECK(is_locked);
memory->Unlock();
is_locked = false;
}
}