1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
|
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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/tiles/mipmap_util.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 {
// If the size of the original sized image breaches kMemoryRatioToSubrect but we
// don't need to scale the image, consider caching only the needed subrect.
// The second part that much be true is that we cache only the needed subrect if
// the total size needed for the subrect is at most kMemoryRatioToSubrect *
// (size needed for the full original image).
// Note that at least one of the dimensions has to be at least
// kMinDimensionToSubrect before an image can breach the threshold.
const size_t kMemoryThresholdToSubrect = 64 * 1024 * 1024;
const int kMinDimensionToSubrect = 4 * 1024;
const float kMemoryRatioToSubrect = 0.5f;
// Tracing ID sequence for use in CacheEntry.
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);
}
// Does *not* return nullptr.
std::unique_ptr<base::DiscardableMemory> AllocateDiscardable(
const SkImageInfo& info,
base::OnceClosure on_no_memory) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"), "AllocateDiscardable");
size_t size = info.minRowBytes() * info.height();
auto* allocator = base::DiscardableMemoryAllocator::GetInstance();
return allocator->AllocateLockedDiscardableMemoryWithRetryOrDie(
size, std::move(on_no_memory));
}
} // namespace
// static
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));
sk_sp<SkColorSpace> target_color_space =
key.target_color_params().color_space.ToSkColorSpace();
SkImageInfo target_info = SkImageInfo::Make(
target_size, color_type, kPremul_SkAlphaType, target_color_space);
std::unique_ptr<base::DiscardableMemory> target_pixels =
AllocateDiscardable(target_info, std::move(on_no_memory));
if (!target_pixels->data())
return nullptr;
SkPixmap target_pixmap(target_info, target_pixels->data(),
target_info.minRowBytes());
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;
}
return std::make_unique<CacheEntry>(target_info, std::move(target_pixels),
SkSize::Make(0, 0));
}
// static
std::unique_ptr<SoftwareImageDecodeCacheUtils::CacheEntry>
SoftwareImageDecodeCacheUtils::GenerateCacheEntryFromCandidate(
const CacheKey& key,
const DecodedDrawImage& candidate_image,
bool needs_extract_subset,
SkColorType color_type) {
SkISize target_size =
SkISize::Make(key.target_size().width(), key.target_size().height());
SkImageInfo target_info =
SkImageInfo::Make(target_size, color_type, kPremul_SkAlphaType);
// TODO(crbug.com/40095682): If this turns into a crasher, pass an actual
// "free memory" closure.
std::unique_ptr<base::DiscardableMemory> target_pixels =
AllocateDiscardable(target_info, base::DoNothing());
if (key.type() == CacheKey::kSubrectOriginal) {
DCHECK(needs_extract_subset);
TRACE_EVENT0(
TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SoftwareImageDecodeCacheUtils::GenerateCacheEntryFromCandidate - "
"subrect");
bool result = candidate_image.image()->readPixels(
target_info, target_pixels->data(), target_info.minRowBytes(),
key.src_rect().x(), key.src_rect().y(), SkImage::kDisallow_CachingHint);
// We have a decoded image, and we're reading into already allocated memory.
// This should never fail.
DCHECK(result) << key.ToString();
return std::make_unique<CacheEntry>(
target_info.makeColorSpace(candidate_image.image()->refColorSpace()),
std::move(target_pixels),
SkSize::Make(-key.src_rect().x(), -key.src_rect().y()));
}
DCHECK_EQ(key.type(), CacheKey::kSubrectAndScale);
TRACE_EVENT0(
TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"SoftwareImageDecodeCacheUtils::GenerateCacheEntryFromCandidate - "
"scale");
SkPixmap decoded_pixmap;
// We don't need to subrect this image, since all candidates passed in would
// already have a src_rect applied to them.
bool result = candidate_image.image()->peekPixels(&decoded_pixmap);
DCHECK(result) << key.ToString();
if (needs_extract_subset) {
result = decoded_pixmap.extractSubset(&decoded_pixmap,
gfx::RectToSkIRect(key.src_rect()));
DCHECK(result) << key.ToString();
}
// Nearest neighbor would only be set in the unscaled case.
DCHECK(!key.is_nearest_neighbor());
SkPixmap target_pixmap(target_info, target_pixels->data(),
target_info.minRowBytes());
PaintFlags::FilterQuality filter_quality = PaintFlags::FilterQuality::kMedium;
result = decoded_pixmap.scalePixels(
target_pixmap,
PaintFlags::FilterQualityToSkSamplingOptions(filter_quality));
DCHECK(result) << key.ToString();
return std::make_unique<CacheEntry>(
target_info.makeColorSpace(candidate_image.image()->refColorSpace()),
std::move(target_pixels),
SkSize::Make(-key.src_rect().x(), -key.src_rect().y()));
}
// CacheKey --------------------------------------------------------------------
// static
SoftwareImageDecodeCacheUtils::CacheKey
SoftwareImageDecodeCacheUtils::CacheKey::FromDrawImage(const DrawImage& image,
SkColorType color_type) {
DCHECK(!image.paint_image().IsTextureBacked());
const PaintImage::FrameKey frame_key = image.frame_key();
const PaintImage::Id stable_id = image.paint_image().stable_id();
const SkSize& scale = image.scale();
// If the src_rect falls outside of the image, we need to clip it since
// otherwise we might end up with uninitialized memory in the decode process.
// Note that the scale is still unchanged and the target size is now a
// function of the new src_rect.
const gfx::Rect& src_rect = GetSrcRect(image);
// Start with the exact target size. However, this will be adjusted below to
// be either a mip level, the original size, or a subrect size. This is done
// to keep memory accounting correct.
gfx::Size target_size(
SkScalarRoundToInt(std::abs(src_rect.width() * scale.width())),
SkScalarRoundToInt(std::abs(src_rect.height() * scale.height())));
// If the target size is empty, then we'll be skipping the decode anyway, so
// the filter quality doesn't matter. Early out instead.
if (target_size.IsEmpty()) {
return CacheKey(frame_key, stable_id, kSubrectAndScale, false,
image.paint_image().may_be_lcp_candidate(), src_rect,
target_size, image.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 any of the following conditions hold, then use at most low filter
// quality and adjust the target size to match the original image:
// - Quality is none: We need a pixelated image, so we can't upgrade it.
// - Mip level is 0: The required mip is the original image, so just use low
// filter quality.
// - Matrix is not decomposable: There's perspective on this image and we
// can't determine the size, so use the original.
if (is_nearest_neighbor || mip_level == 0 ||
!image.matrix_is_decomposable()) {
type = kOriginal;
// Update the size to be the original image size.
target_size =
gfx::Size(image.paint_image().width(), image.paint_image().height());
} else {
type = kSubrectAndScale;
// Update the target size to be a mip level size.
target_size = MipMapUtil::GetSizeForLevel(src_rect.size(), mip_level);
}
// If the original image is large, we might want to do a subrect instead if
// the subrect would be kMemoryRatioToSubrect times smaller.
if (type == kOriginal &&
(image.paint_image().width() >= kMinDimensionToSubrect ||
image.paint_image().height() >= kMinDimensionToSubrect)) {
base::CheckedNumeric<size_t> checked_original_size = 4u;
checked_original_size *= image.paint_image().width();
checked_original_size *= image.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 the sizes are such that we get good savings by subrecting, then do
// that. Also update the target size to be the src rect size since that's
// the rect we want to use.
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, image.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 {
// TODO(vmpstr): This is a mess. Maybe it's faster to just search the vector
// always (forwards or backwards to account for LRU).
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());
}
// Include the target color space in the hash regardless of scaling.
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();
}
// CacheEntry ------------------------------------------------------------------
SoftwareImageDecodeCacheUtils::CacheEntry::CacheEntry()
: tracing_id_(g_next_tracing_id_.GetNext()) {}
SoftwareImageDecodeCacheUtils::CacheEntry::CacheEntry(
const SkImageInfo& info,
std::unique_ptr<base::DiscardableMemory> in_memory,
const SkSize& src_rect_offset)
: is_locked(true),
memory(std::move(in_memory)),
image_info_(info),
src_rect_offset_(src_rect_offset),
tracing_id_(g_next_tracing_id_.GetNext()) {
DCHECK(memory);
SkPixmap pixmap(image_info_, memory->data(), image_info_.minRowBytes());
image_ = SkImages::RasterFromPixmap(
pixmap, [](const void* pixels, void* context) {}, nullptr);
}
SoftwareImageDecodeCacheUtils::CacheEntry::~CacheEntry() {
DCHECK(!is_locked);
}
void SoftwareImageDecodeCacheUtils::CacheEntry::MoveImageMemoryTo(
CacheEntry* entry) {
DCHECK(!is_budgeted);
DCHECK_EQ(ref_count, 0);
// Copy/move most things except budgeted and ref counts.
entry->decode_failed = decode_failed;
entry->is_locked = is_locked;
is_locked = false;
entry->memory = std::move(memory);
entry->image_info_ = std::move(image_info_);
entry->src_rect_offset_ = std::move(src_rect_offset_);
entry->image_ = std::move(image_);
}
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;
}
} // namespace cc
|