#include "cc/tiles/checker_image_tracker.h"
#include <algorithm>
#include <limits>
#include <sstream>
#include <string>
#include <utility>
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/trace_event/trace_event.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
namespace cc {
namespace {
enum class CheckerImagingDecision {
kCanChecker,
kVetoedAnimatedImage,
kVetoedVideoFrame,
kVetoedMultipartImage,
kVetoedPartiallyLoadedImage,
kVetoedSmallerThanCheckeringSize,
kVetoedLargerThanCacheSize,
kVetoedForceDisable,
kVetoedSyncRequested,
kCheckerImagingDecisionCount
};
std::string ToString(PaintImage::Id paint_image_id,
CheckerImagingDecision decision) {
std::ostringstream str;
str << "paint_image_id[" << paint_image_id << "] decision["
<< static_cast<int>(decision) << "]";
return str.str();
}
CheckerImagingDecision GetAnimationDecision(const PaintImage& image) {
if (image.is_multipart())
return CheckerImagingDecision::kVetoedMultipartImage;
switch (image.animation_type()) {
case PaintImage::AnimationType::kAnimated:
return CheckerImagingDecision::kVetoedAnimatedImage;
case PaintImage::AnimationType::kVideo:
return CheckerImagingDecision::kVetoedVideoFrame;
case PaintImage::AnimationType::kStatic:
return CheckerImagingDecision::kCanChecker;
}
NOTREACHED();
}
CheckerImagingDecision GetLoadDecision(const PaintImage& image) {
switch (image.completion_state()) {
case PaintImage::CompletionState::kDone:
return CheckerImagingDecision::kCanChecker;
case PaintImage::CompletionState::kPartiallyDone:
return CheckerImagingDecision::kVetoedPartiallyLoadedImage;
}
NOTREACHED();
}
CheckerImagingDecision GetSizeDecision(const SkIRect& src_rect,
size_t min_bytes,
size_t max_bytes) {
base::CheckedNumeric<size_t> checked_size = 4;
checked_size *= src_rect.width();
checked_size *= src_rect.height();
size_t size = checked_size.ValueOrDefault(std::numeric_limits<size_t>::max());
if (size < min_bytes)
return CheckerImagingDecision::kVetoedSmallerThanCheckeringSize;
else if (size > max_bytes)
return CheckerImagingDecision::kVetoedLargerThanCacheSize;
else
return CheckerImagingDecision::kCanChecker;
}
CheckerImagingDecision GetCheckerImagingDecision(const PaintImage& image,
const SkIRect& src_rect,
size_t min_bytes,
size_t max_bytes) {
CheckerImagingDecision decision = GetAnimationDecision(image);
if (decision != CheckerImagingDecision::kCanChecker)
return decision;
decision = GetLoadDecision(image);
if (decision != CheckerImagingDecision::kCanChecker)
return decision;
return GetSizeDecision(src_rect, min_bytes, max_bytes);
}
}
const int CheckerImageTracker::kNoDecodeAllowedPriority = -1;
CheckerImageTracker::ImageDecodeRequest::ImageDecodeRequest(
PaintImage paint_image,
DecodeType type)
: paint_image(std::move(paint_image)), type(type) {}
CheckerImageTracker::CheckerImageTracker(ImageController* image_controller,
CheckerImageTrackerClient* client,
bool enable_checker_imaging,
size_t min_image_bytes_to_checker)
: image_controller_(image_controller),
client_(client),
enable_checker_imaging_(enable_checker_imaging),
min_image_bytes_to_checker_(min_image_bytes_to_checker) {}
CheckerImageTracker::~CheckerImageTracker() = default;
void CheckerImageTracker::SetNoDecodesAllowed() {
decode_priority_allowed_ = kNoDecodeAllowedPriority;
}
void CheckerImageTracker::SetMaxDecodePriorityAllowed(DecodeType decode_type) {
DCHECK_GT(decode_type, kNoDecodeAllowedPriority);
DCHECK_GE(decode_type, decode_priority_allowed_);
DCHECK_LE(decode_type, DecodeType::kLast);
if (decode_priority_allowed_ == decode_type)
return;
decode_priority_allowed_ = decode_type;
ScheduleNextImageDecode();
}
void CheckerImageTracker::ScheduleImageDecodeQueue(
ImageDecodeQueue image_decode_queue) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"CheckerImageTracker::ScheduleImageDecodeQueue");
#if DCHECK_IS_ON()
DecodeType type = DecodeType::kRaster;
for (const auto& image_request : image_decode_queue) {
DCHECK_GE(image_request.type, type);
type = image_request.type;
}
#endif
image_decode_queue_ = std::move(image_decode_queue);
ScheduleNextImageDecode();
}
const PaintImageIdFlatSet&
CheckerImageTracker::TakeImagesToInvalidateOnSyncTree() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"CheckerImageTracker::TakeImagesToInvalidateOnSyncTree");
DCHECK_EQ(invalidated_images_on_current_sync_tree_.size(), 0u)
<< "Sync tree can not be invalidated more than once";
invalidated_images_on_current_sync_tree_.swap(images_pending_invalidation_);
images_pending_invalidation_.clear();
return invalidated_images_on_current_sync_tree_;
}
void CheckerImageTracker::DidActivateSyncTree() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"CheckerImageTracker::DidActivateSyncTree");
for (auto image_id : invalidated_images_on_current_sync_tree_)
image_id_to_decode_.erase(image_id);
invalidated_images_on_current_sync_tree_.clear();
}
void CheckerImageTracker::ClearTracker(bool can_clear_decode_policy_tracking) {
image_id_to_decode_.clear();
if (can_clear_decode_policy_tracking) {
decoding_mode_map_.clear();
image_async_decode_state_.clear();
image_decode_queue_.clear();
} else {
for (auto image_id : images_pending_invalidation_) {
auto it = image_async_decode_state_.find(image_id);
CHECK(it != image_async_decode_state_.end());
DCHECK_EQ(it->second.policy, DecodePolicy::SYNC);
it->second.policy = DecodePolicy::ASYNC;
}
}
images_pending_invalidation_.clear();
}
void CheckerImageTracker::DisallowCheckeringForImage(const PaintImage& image) {
image_async_decode_state_.insert(
std::make_pair(image.stable_id(), DecodeState()));
}
void CheckerImageTracker::DidFinishImageDecode(
PaintImage::Id image_id,
ImageController::ImageDecodeRequestId request_id,
ImageController::ImageDecodeResult result) {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"CheckerImageTracker::DidFinishImageDecode");
TRACE_EVENT_END(
"cc",
perfetto::Track(image_id));
DCHECK_NE(ImageController::ImageDecodeResult::DECODE_NOT_REQUIRED, result);
DCHECK_EQ(outstanding_image_decode_.value().stable_id(), image_id);
outstanding_image_decode_.reset();
auto it = image_async_decode_state_.find(image_id);
if (it == image_async_decode_state_.end()) {
DCHECK_EQ(image_id_to_decode_.count(image_id), 0u);
return;
}
if (it->second.policy == DecodePolicy::SYNC) {
DCHECK(decoding_mode_map_.find(image_id) != decoding_mode_map_.end());
DCHECK_EQ(decoding_mode_map_[image_id], PaintImage::DecodingMode::kSync);
ScheduleNextImageDecode();
return;
}
it->second.policy = DecodePolicy::SYNC;
images_pending_invalidation_.insert(image_id);
ScheduleNextImageDecode();
client_->NeedsInvalidationForCheckerImagedTiles();
}
bool CheckerImageTracker::ShouldCheckerImage(const DrawImage& draw_image,
WhichTree tree) {
const PaintImage& image = draw_image.paint_image();
PaintImage::Id image_id = image.stable_id();
TRACE_EVENT1("cc.debug", "CheckerImageTracker::ShouldCheckerImage",
"image_id", image_id);
if (!enable_checker_imaging_)
return false;
if (!image.IsLazyGenerated())
return false;
if (invalidated_images_on_current_sync_tree_.count(image_id) != 0 &&
tree == WhichTree::ACTIVE_TREE) {
return true;
}
if (base::Contains(images_pending_invalidation_, image_id)) {
return true;
}
auto decoding_mode_it = decoding_mode_map_.find(image_id);
PaintImage::DecodingMode decoding_mode_hint =
decoding_mode_it == decoding_mode_map_.end()
? PaintImage::DecodingMode::kUnspecified
: decoding_mode_it->second;
if (decoding_mode_hint != PaintImage::DecodingMode::kAsync)
return false;
auto insert_result = image_async_decode_state_.insert(
std::pair<PaintImage::Id, DecodeState>(image_id, DecodeState()));
auto it = insert_result.first;
if (insert_result.second) {
CheckerImagingDecision decision = GetCheckerImagingDecision(
image, draw_image.src_rect(), min_image_bytes_to_checker_,
image_controller_->image_cache_max_limit_bytes());
if (decision == CheckerImagingDecision::kCanChecker && force_disabled_) {
decision = CheckerImagingDecision::kVetoedForceDisable;
}
it->second.policy = decision == CheckerImagingDecision::kCanChecker
? DecodePolicy::ASYNC
: DecodePolicy::SYNC;
TRACE_EVENT1(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"CheckerImageTracker::CheckerImagingDecision", "image_params",
ToString(image_id, decision));
}
UpdateDecodeState(draw_image, image_id, &it->second);
return it->second.policy == DecodePolicy::ASYNC;
}
void CheckerImageTracker::UpdateDecodeState(const DrawImage& draw_image,
PaintImage::Id paint_image_id,
DecodeState* decode_state) {
if (decode_state->policy != DecodePolicy::ASYNC)
return;
if (outstanding_image_decode_.has_value() &&
outstanding_image_decode_.value().stable_id() == paint_image_id) {
return;
}
decode_state->scale = SkSize::Make(
std::max(decode_state->scale.fWidth, draw_image.scale().fWidth),
std::max(decode_state->scale.fHeight, draw_image.scale().fHeight));
decode_state->use_dark_mode = draw_image.use_dark_mode();
decode_state->filter_quality =
std::max(decode_state->filter_quality, draw_image.filter_quality());
decode_state->target_color_params = draw_image.target_color_params();
decode_state->frame_index = draw_image.frame_index();
}
void CheckerImageTracker::ScheduleNextImageDecode() {
TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
"CheckerImageTracker::ScheduleNextImageDecode");
if (outstanding_image_decode_.has_value())
return;
if (image_decode_queue_.empty())
return;
if (image_decode_queue_.front().type > decode_priority_allowed_)
return;
DrawImage draw_image;
while (!image_decode_queue_.empty()) {
auto candidate = std::move(image_decode_queue_.front().paint_image);
image_decode_queue_.erase(image_decode_queue_.begin());
PaintImage::Id image_id = candidate.stable_id();
auto it = image_async_decode_state_.find(image_id);
CHECK(it != image_async_decode_state_.end());
if (it->second.policy != DecodePolicy::ASYNC)
continue;
draw_image = DrawImage(
candidate, it->second.use_dark_mode,
SkIRect::MakeWH(candidate.width(), candidate.height()),
it->second.filter_quality,
SkM44::Scale(it->second.scale.width(), it->second.scale.height()),
it->second.frame_index, it->second.target_color_params);
outstanding_image_decode_.emplace(candidate);
break;
}
if (!outstanding_image_decode_.has_value()) {
DCHECK(image_decode_queue_.empty());
return;
}
PaintImage::Id image_id = outstanding_image_decode_.value().stable_id();
DCHECK_EQ(image_id_to_decode_.count(image_id), 0u);
TRACE_EVENT_BEGIN("cc", "CheckerImageTracker::DeferImageDecode",
perfetto::Track(image_id));
ImageController::ImageDecodeRequestId request_id =
image_controller_->QueueImageDecode(
draw_image,
base::BindOnce(&CheckerImageTracker::DidFinishImageDecode,
weak_factory_.GetWeakPtr(), image_id),
false);
image_id_to_decode_.emplace(image_id, std::make_unique<ScopedDecodeHolder>(
image_controller_, request_id));
}
void CheckerImageTracker::UpdateImageDecodingHints(
base::flat_map<PaintImage::Id, PaintImage::DecodingMode>
decoding_mode_map) {
if (!enable_checker_imaging_)
return;
for (auto pair : decoding_mode_map) {
PaintImage::Id id = pair.first;
PaintImage::DecodingMode decoding_mode = pair.second;
auto state_it = image_async_decode_state_.find(id);
if (state_it != image_async_decode_state_.end()) {
auto& state = state_it->second;
if (state.policy == DecodePolicy::ASYNC &&
decoding_mode == PaintImage::DecodingMode::kSync) {
state.policy = DecodePolicy::SYNC;
images_pending_invalidation_.insert(id);
}
}
auto decoding_mode_it = decoding_mode_map_.find(id);
if (decoding_mode_it == decoding_mode_map_.end()) {
decoding_mode_map_[id] = decoding_mode;
} else {
decoding_mode_it->second =
PaintImage::GetConservative(decoding_mode_it->second, decoding_mode);
}
}
}
}