#include "cc/paint/discardable_image_map.h"
#include <stddef.h>
#include <algorithm>
#include <limits>
#include "base/auto_reset.h"
#include "base/check.h"
#include "base/containers/adapters.h"
#include "base/memory/stack_allocated.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/sequence_checker.h"
#include "base/trace_event/trace_event.h"
#include "cc/base/features.h"
#include "cc/paint/display_item_list.h"
#include "cc/paint/frame_metadata.h"
#include "cc/paint/image_provider.h"
#include "cc/paint/paint_filter.h"
#include "cc/paint/paint_op_buffer.h"
#include "cc/paint/paint_op_buffer_iterator.h"
#include "cc/paint/paint_shader.h"
#include "cc/paint/skottie_wrapper.h"
#include "third_party/skia/include/utils/SkNoDrawCanvas.h"
#include "ui/gfx/display_color_spaces.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/skia_conversions.h"
namespace cc {
class DiscardableImageMap::Generator {
STACK_ALLOCATED();
public:
Generator(DiscardableImageMap& map,
const PaintOpBuffer& buffer,
const gfx::Rect& bounds,
const ScrollOffsetMap& raster_inducing_scroll_offsets,
DecodingModeMap* decoding_mode_map,
PaintWorkletInputs* paint_worklet_inputs)
: map_(map),
canvas_(gfx::RectToSkIRect(bounds)),
raster_inducing_scroll_offsets_(raster_inducing_scroll_offsets),
decoding_mode_map_(decoding_mode_map),
paint_worklet_inputs_(paint_worklet_inputs) {
GatherDiscardableImages(buffer, nullptr);
}
private:
static constexpr int kMaxRectsSize = 256;
class ImageGatheringProvider : public ImageProvider {
STACK_ALLOCATED();
public:
ImageGatheringProvider(Generator& generator, const gfx::Rect& op_rect)
: generator_(generator), op_rect_(op_rect) {}
~ImageGatheringProvider() override = default;
ScopedResult GetRasterContent(const DrawImage& draw_image) override {
generator_.AddImage(draw_image.paint_image(), false,
SkRect::Make(draw_image.src_rect()), op_rect_,
SkM44(), draw_image.filter_quality());
return ScopedResult();
}
private:
Generator& generator_;
gfx::Rect op_rect_;
};
void GatherDiscardableImages(const PaintOpBuffer& buffer,
const gfx::Rect* top_level_op_rect) {
if (!buffer.has_discardable_images()) {
return;
}
SkAutoCanvasRestore save_restore(&canvas_, true);
PlaybackParams params(nullptr, canvas_.getLocalToDevice());
for (const PaintOp& op : buffer) {
if (!op.IsDrawOp())
op.Raster(&canvas_, params);
if (!PaintOp::OpHasDiscardableImages(op))
continue;
gfx::Rect op_rect;
if (top_level_op_rect) {
op_rect = *top_level_op_rect;
} else {
const SkRect& clip_rect = SkRect::Make(canvas_.getDeviceClipBounds());
const SkMatrix& ctm = canvas_.getTotalMatrix();
op_rect = PaintOp::ComputePaintRect(op, clip_rect, ctm);
}
if (!collect_invisible_images_ && op_rect.IsEmpty()) {
continue;
}
const SkM44& ctm = canvas_.getLocalToDevice();
if (op.IsPaintOpWithFlags()) {
AddImageFromFlags(op_rect,
static_cast<const PaintOpWithFlags&>(op).flags, ctm);
}
PaintOpType op_type = op.GetType();
if (op_type == PaintOpType::kDrawImage) {
const auto& image_op = static_cast<const DrawImageOp&>(op);
PaintFlags::FilterQuality quality =
base::FeatureList::IsEnabled(
features::kPreserveDiscardableImageMapQuality)
? image_op.GetImageQuality()
: image_op.flags.getFilterQuality();
AddImage(
image_op.image, image_op.flags.useDarkModeForImage(),
SkRect::MakeIWH(image_op.image.width(), image_op.image.height()),
op_rect, ctm, quality);
} else if (op_type == PaintOpType::kDrawImageRect) {
const auto& image_rect_op = static_cast<const DrawImageRectOp&>(op);
SkM44 matrix = ctm * SkM44(SkMatrix::RectToRect(image_rect_op.src,
image_rect_op.dst));
PaintFlags::FilterQuality quality =
base::FeatureList::IsEnabled(
features::kPreserveDiscardableImageMapQuality)
? image_rect_op.GetImageQuality()
: image_rect_op.flags.getFilterQuality();
AddImage(image_rect_op.image, image_rect_op.flags.useDarkModeForImage(),
image_rect_op.src, op_rect, matrix, quality);
} else if (op_type == PaintOpType::kDrawSkottie) {
const auto& skottie_op = static_cast<const DrawSkottieOp&>(op);
for (const auto& image_pair : skottie_op.images) {
const SkottieFrameData& frame_data = image_pair.second;
SkRect image_src_rect = SkRect::MakeIWH(frame_data.image.width(),
frame_data.image.height());
gfx::Rect dst_rect = op_rect;
static constexpr SkMatrix::ScaleToFit kScalingMode =
SkMatrix::kCenter_ScaleToFit;
SkRect skottie_frame_native_size =
SkRect::MakeSize(skottie_op.skottie->size());
SkM44 matrix = ctm * SkM44(SkMatrix::RectToRect(
skottie_frame_native_size,
gfx::RectToSkRect(dst_rect), kScalingMode));
AddImage(frame_data.image, false,
std::move(image_src_rect), std::move(dst_rect), matrix,
frame_data.quality);
}
} else if (op_type == PaintOpType::kDrawRecord) {
GatherDiscardableImages(
static_cast<const DrawRecordOp&>(op).record.buffer(),
top_level_op_rect);
} else if (op_type == PaintOpType::kDrawScrollingContents) {
const auto& draw_scrolling_contents_op =
static_cast<const DrawScrollingContentsOp&>(op);
if (draw_scrolling_contents_op.display_item_list
->has_discardable_images()) {
canvas_.save();
base::AutoReset<bool> reset(&collect_invisible_images_, true);
gfx::PointF scroll_offset = raster_inducing_scroll_offsets_.at(
draw_scrolling_contents_op.scroll_element_id);
canvas_.translate(-scroll_offset.x(), -scroll_offset.y());
GatherDiscardableImages(
draw_scrolling_contents_op.display_item_list->paint_op_buffer(),
top_level_op_rect);
canvas_.restore();
}
}
}
}
void AddImageFromFlags(const gfx::Rect& op_rect,
const PaintFlags& flags,
const SkM44& ctm) {
AddImageFromShader(op_rect, flags.getShader(), ctm,
flags.getFilterQuality());
AddImageFromFilter(op_rect, flags.getImageFilter().get());
}
void AddImageFromShader(const gfx::Rect& op_rect,
const PaintShader* shader,
const SkM44& ctm,
PaintFlags::FilterQuality filter_quality) {
if (!shader || !shader->HasDiscardableImages()) {
return;
}
if (shader->shader_type() == PaintShader::Type::kImage) {
const PaintImage& paint_image = shader->paint_image();
SkM44 matrix = ctm * SkM44(shader->GetLocalMatrix());
AddImage(paint_image, false,
SkRect::MakeWH(paint_image.width(), paint_image.height()),
op_rect, matrix, filter_quality);
return;
}
if (shader->shader_type() == PaintShader::Type::kPaintRecord) {
if (shader->image_analysis_state() ==
ImageAnalysisState::kNoAnimatedImages) {
return;
}
SkRect scaled_tile_rect;
if (!shader->GetRasterizationTileRect(ctm.asM33(), &scaled_tile_rect)) {
return;
}
base::AutoReset<bool> auto_reset(&only_gather_animated_images_, true);
size_t prev_images_size = map_.images_.size();
GatherDiscardableImages(shader->paint_record()->buffer(), &op_rect);
DCHECK_GE(map_.images_.size(), prev_images_size);
const bool has_animated_images = map_.images_.size() > prev_images_size;
const_cast<PaintShader*>(shader)->set_has_animated_images(
has_animated_images);
}
}
void AddImageFromFilter(const gfx::Rect& op_rect, const PaintFilter* filter) {
if (!filter || !filter->has_discardable_images() ||
filter->image_analysis_state() ==
ImageAnalysisState::kNoAnimatedImages) {
return;
}
base::AutoReset<bool> auto_reset(&only_gather_animated_images_, true);
size_t prev_images_size = map_.images_.size();
ImageGatheringProvider image_provider(*this, op_rect);
filter->SnapshotWithImages(&image_provider);
DCHECK_GE(map_.images_.size(), prev_images_size);
const bool has_animated_images = map_.images_.size() > prev_images_size;
const_cast<PaintFilter*>(filter)->set_has_animated_images(
has_animated_images);
}
void AddImage(PaintImage paint_image,
bool use_dark_mode,
const SkRect& src_rect,
const gfx::Rect& image_rect,
const SkM44& matrix,
PaintFlags::FilterQuality filter_quality) {
if (paint_image.IsTextureBacked())
return;
SkIRect src_irect;
src_rect.roundOut(&src_irect);
auto& rects = map_.image_id_to_rects_[paint_image.stable_id()];
if (rects.size() >= kMaxRectsSize) {
rects.back().Union(image_rect);
} else {
rects.push_back(image_rect);
}
if (paint_worklet_inputs_ && paint_image.IsPaintWorklet()) {
paint_worklet_inputs_->emplace_back(paint_image.GetPaintWorkletInput(),
paint_image.stable_id());
}
if (decoding_mode_map_ && paint_image.IsLazyGenerated()) {
auto decoding_mode_it = decoding_mode_map_->find(paint_image.stable_id());
if (decoding_mode_it == decoding_mode_map_->end()) {
(*decoding_mode_map_)[paint_image.stable_id()] =
paint_image.decoding_mode();
} else {
decoding_mode_it->second = PaintImage::GetConservative(
decoding_mode_it->second, paint_image.decoding_mode());
}
}
if (paint_image.ShouldAnimate()) {
map_.animated_images_metadata_.emplace_back(
paint_image.stable_id(), paint_image.completion_state(),
paint_image.GetFrameMetadata(), paint_image.repetition_count(),
paint_image.reset_animation_sequence_id());
}
bool add_image = true;
if (paint_image.IsPaintWorklet()) {
add_image = false;
} else if (only_gather_animated_images_) {
add_image = paint_image.ShouldAnimate();
}
if (add_image) {
map_.images_.emplace_back(DrawImage(std::move(paint_image), use_dark_mode,
src_irect, filter_quality, matrix),
image_rect);
}
}
DiscardableImageMap& map_;
SkNoDrawCanvas canvas_;
const ScrollOffsetMap& raster_inducing_scroll_offsets_;
DecodingModeMap* const decoding_mode_map_;
PaintWorkletInputs* const paint_worklet_inputs_;
bool only_gather_animated_images_ = false;
bool collect_invisible_images_ = false;
};
DiscardableImageMap::DiscardableImageMap() = default;
DiscardableImageMap::~DiscardableImageMap() = default;
scoped_refptr<DiscardableImageMap> DiscardableImageMap::Generate(
const PaintOpBuffer& paint_op_buffer,
const gfx::Rect& bounds,
const ScrollOffsetMap& raster_inducing_scroll_offsets,
DecodingModeMap* decoding_mode_map,
PaintWorkletInputs* paint_worklet_inputs) {
TRACE_EVENT0("cc", "DiscardableImageMap::Generate");
scoped_refptr<DiscardableImageMap> image_map(new DiscardableImageMap());
Generator generator(*image_map, paint_op_buffer, bounds,
raster_inducing_scroll_offsets, decoding_mode_map,
paint_worklet_inputs);
CHECK(!image_map->images_rtree_);
return image_map;
}
std::vector<const DrawImage*> DiscardableImageMap::GetDiscardableImagesInRect(
const gfx::Rect& rect) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::vector<const DrawImage*> result;
if (!images_rtree_) {
images_rtree_ = std::make_unique<RTree<const DrawImage*>>();
images_rtree_->Build(
images_.size(), [this](size_t index) { return images_[index].second; },
[this](size_t index) { return &images_[index].first; });
}
images_rtree_->Search(rect, &result);
return result;
}
const DiscardableImageMap::Rects& DiscardableImageMap::GetRectsForImage(
PaintImage::Id image_id) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
static const base::NoDestructor<Rects> kEmptyRects;
auto it = image_id_to_rects_.find(image_id);
return it == image_id_to_rects_.end() ? *kEmptyRects : it->second;
}
DiscardableImageMap::AnimatedImageMetadata::AnimatedImageMetadata(
PaintImage::Id paint_image_id,
PaintImage::CompletionState completion_state,
std::vector<FrameMetadata> frames,
int repetition_count,
PaintImage::AnimationSequenceId reset_animation_sequence_id)
: paint_image_id(paint_image_id),
completion_state(completion_state),
frames(std::move(frames)),
repetition_count(repetition_count),
reset_animation_sequence_id(reset_animation_sequence_id) {}
DiscardableImageMap::AnimatedImageMetadata::~AnimatedImageMetadata() = default;
DiscardableImageMap::AnimatedImageMetadata::AnimatedImageMetadata(
const AnimatedImageMetadata& other) = default;
}