1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "cc/paint/discardable_image_map.h"
6
7 #include <stddef.h>
8
9 #include <algorithm>
10 #include <limits>
11
12 #include "base/auto_reset.h"
13 #include "base/containers/adapters.h"
14 #include "base/metrics/histogram_macros.h"
15 #include "base/no_destructor.h"
16 #include "base/trace_event/trace_event.h"
17 #include "cc/paint/paint_filter.h"
18 #include "cc/paint/paint_op_buffer.h"
19 #include "third_party/skia/include/utils/SkNoDrawCanvas.h"
20 #include "ui/gfx/display_color_spaces.h"
21 #include "ui/gfx/geometry/rect_conversions.h"
22 #include "ui/gfx/skia_util.h"
23
24 namespace cc {
25 namespace {
26 const int kMaxRectsSize = 256;
27
28 class DiscardableImageGenerator {
29 public:
DiscardableImageGenerator(int width,int height,const PaintOpBuffer * buffer)30 DiscardableImageGenerator(int width,
31 int height,
32 const PaintOpBuffer* buffer) {
33 SkNoDrawCanvas canvas(width, height);
34 GatherDiscardableImages(buffer, nullptr, &canvas);
35 }
36 ~DiscardableImageGenerator() = default;
37
TakeImages()38 std::vector<std::pair<DrawImage, gfx::Rect>> TakeImages() {
39 return std::move(image_set_);
40 }
41 base::flat_map<PaintImage::Id, DiscardableImageMap::Rects>
TakeImageIdToRectsMap()42 TakeImageIdToRectsMap() {
43 return std::move(image_id_to_rects_);
44 }
45 base::flat_map<PaintImage::Id, PaintImage::DecodingMode>
TakeDecodingModeMap()46 TakeDecodingModeMap() {
47 return std::move(decoding_mode_map_);
48 }
49 std::vector<DiscardableImageMap::AnimatedImageMetadata>
TakeAnimatedImagesMetadata()50 TakeAnimatedImagesMetadata() {
51 return std::move(animated_images_metadata_);
52 }
53 std::vector<DiscardableImageMap::PaintWorkletInputWithImageId>
TakePaintWorkletInputs()54 TakePaintWorkletInputs() {
55 return std::move(paint_worklet_inputs_);
56 }
57
content_color_usage() const58 gfx::ContentColorUsage content_color_usage() const {
59 return content_color_usage_;
60 }
contains_hbd_images() const61 bool contains_hbd_images() const { return contains_hbd_images_; }
62
63 private:
64 class ImageGatheringProvider : public ImageProvider {
65 public:
ImageGatheringProvider(DiscardableImageGenerator * generator,const gfx::Rect & op_rect)66 ImageGatheringProvider(DiscardableImageGenerator* generator,
67 const gfx::Rect& op_rect)
68 : generator_(generator), op_rect_(op_rect) {}
69 ~ImageGatheringProvider() override = default;
70
GetRasterContent(const DrawImage & draw_image)71 ScopedResult GetRasterContent(const DrawImage& draw_image) override {
72 generator_->AddImage(draw_image.paint_image(), false,
73 SkRect::Make(draw_image.src_rect()), op_rect_,
74 SkMatrix::I(), draw_image.filter_quality());
75 return ScopedResult();
76 }
77
78 private:
79 DiscardableImageGenerator* generator_;
80 gfx::Rect op_rect_;
81 };
82
83 // Adds discardable images from |buffer| to the set of images tracked by
84 // this generator. If |buffer| is being used in a DrawOp that requires
85 // rasterization of the buffer as a pre-processing step for execution of the
86 // op (for instance, with PaintRecord backed PaintShaders),
87 // |top_level_op_rect| is set to the rect for that op. If provided, the
88 // |top_level_op_rect| will be used as the rect for tracking the position of
89 // this image in the top-level buffer.
GatherDiscardableImages(const PaintOpBuffer * buffer,const gfx::Rect * top_level_op_rect,SkNoDrawCanvas * canvas)90 void GatherDiscardableImages(const PaintOpBuffer* buffer,
91 const gfx::Rect* top_level_op_rect,
92 SkNoDrawCanvas* canvas) {
93 if (!buffer->HasDiscardableImages())
94 return;
95
96 // Prevent PaintOpBuffers from having side effects back into the canvas.
97 SkAutoCanvasRestore save_restore(canvas, true);
98
99 PlaybackParams params(nullptr, canvas->getTotalMatrix());
100 // TODO(khushalsagar): Optimize out save/restore blocks if there are no
101 // images in the draw ops between them.
102 for (auto* op : PaintOpBuffer::Iterator(buffer)) {
103 // We need to play non-draw ops on the SkCanvas since they can affect the
104 // transform/clip state.
105 if (!op->IsDrawOp())
106 op->Raster(canvas, params);
107
108 if (!PaintOp::OpHasDiscardableImages(op))
109 continue;
110
111 gfx::Rect op_rect;
112 base::Optional<gfx::Rect> local_op_rect;
113
114 if (top_level_op_rect) {
115 op_rect = *top_level_op_rect;
116 } else {
117 const SkRect& clip_rect = SkRect::Make(canvas->getDeviceClipBounds());
118 const SkMatrix& ctm = canvas->getTotalMatrix();
119
120 local_op_rect = PaintOp::ComputePaintRect(op, clip_rect, ctm);
121 if (local_op_rect.value().IsEmpty())
122 continue;
123
124 op_rect = local_op_rect.value();
125 }
126
127 const SkMatrix& ctm = canvas->getTotalMatrix();
128 if (op->IsPaintOpWithFlags()) {
129 AddImageFromFlags(op_rect,
130 static_cast<const PaintOpWithFlags*>(op)->flags, ctm);
131 }
132
133 PaintOpType op_type = static_cast<PaintOpType>(op->type);
134 if (op_type == PaintOpType::DrawImage) {
135 auto* image_op = static_cast<DrawImageOp*>(op);
136 AddImage(
137 image_op->image, image_op->flags.useDarkModeForImage(),
138 SkRect::MakeIWH(image_op->image.width(), image_op->image.height()),
139 op_rect, ctm, image_op->flags.getFilterQuality());
140 } else if (op_type == PaintOpType::DrawImageRect) {
141 auto* image_rect_op = static_cast<DrawImageRectOp*>(op);
142 SkMatrix matrix = ctm;
143 matrix.postConcat(SkMatrix::MakeRectToRect(image_rect_op->src,
144 image_rect_op->dst,
145 SkMatrix::kFill_ScaleToFit));
146 AddImage(image_rect_op->image,
147 image_rect_op->flags.useDarkModeForImage(), image_rect_op->src,
148 op_rect, matrix, image_rect_op->flags.getFilterQuality());
149 } else if (op_type == PaintOpType::DrawRecord) {
150 GatherDiscardableImages(
151 static_cast<const DrawRecordOp*>(op)->record.get(),
152 top_level_op_rect, canvas);
153 }
154 }
155 }
156
AddImageFromFlags(const gfx::Rect & op_rect,const PaintFlags & flags,const SkMatrix & ctm)157 void AddImageFromFlags(const gfx::Rect& op_rect,
158 const PaintFlags& flags,
159 const SkMatrix& ctm) {
160 // TODO(prashant.n): Add dark mode support for images from shaders/filters.
161 AddImageFromShader(op_rect, flags.getShader(), ctm,
162 flags.getFilterQuality());
163 AddImageFromFilter(op_rect, flags.getImageFilter().get());
164 }
165
AddImageFromShader(const gfx::Rect & op_rect,const PaintShader * shader,const SkMatrix & ctm,SkFilterQuality filter_quality)166 void AddImageFromShader(const gfx::Rect& op_rect,
167 const PaintShader* shader,
168 const SkMatrix& ctm,
169 SkFilterQuality filter_quality) {
170 if (!shader || !shader->has_discardable_images())
171 return;
172
173 if (shader->shader_type() == PaintShader::Type::kImage) {
174 const PaintImage& paint_image = shader->paint_image();
175 SkMatrix matrix = ctm;
176 matrix.postConcat(shader->GetLocalMatrix());
177 // TODO(prashant.n): Add dark mode support for images from shader.
178 AddImage(paint_image, false,
179 SkRect::MakeWH(paint_image.width(), paint_image.height()),
180 op_rect, matrix, filter_quality);
181 return;
182 }
183
184 if (shader->shader_type() == PaintShader::Type::kPaintRecord) {
185 // For record backed shaders, only analyze them if they have animated
186 // images.
187 if (shader->image_analysis_state() ==
188 ImageAnalysisState::kNoAnimatedImages) {
189 return;
190 }
191
192 SkRect scaled_tile_rect;
193 if (!shader->GetRasterizationTileRect(ctm, &scaled_tile_rect)) {
194 return;
195 }
196
197 SkNoDrawCanvas canvas(scaled_tile_rect.width(),
198 scaled_tile_rect.height());
199 canvas.setMatrix(SkMatrix::MakeRectToRect(
200 shader->tile(), scaled_tile_rect, SkMatrix::kFill_ScaleToFit));
201 base::AutoReset<bool> auto_reset(&only_gather_animated_images_, true);
202 size_t prev_image_set_size = image_set_.size();
203 GatherDiscardableImages(shader->paint_record().get(), &op_rect, &canvas);
204
205 // We only track animated images for PaintShaders. If we added any entry
206 // to the |image_set_|, this shader any has animated images.
207 // Note that it is thread-safe to set the |has_animated_images| bit on
208 // PaintShader here since the analysis is done on the main thread, before
209 // the PaintOpBuffer is used for rasterization.
210 DCHECK_GE(image_set_.size(), prev_image_set_size);
211 const bool has_animated_images = image_set_.size() > prev_image_set_size;
212 const_cast<PaintShader*>(shader)->set_has_animated_images(
213 has_animated_images);
214 }
215 }
216
AddImageFromFilter(const gfx::Rect & op_rect,const PaintFilter * filter)217 void AddImageFromFilter(const gfx::Rect& op_rect, const PaintFilter* filter) {
218 // Only analyze filters if they have animated images.
219 if (!filter || !filter->has_discardable_images() ||
220 filter->image_analysis_state() ==
221 ImageAnalysisState::kNoAnimatedImages) {
222 return;
223 }
224
225 base::AutoReset<bool> auto_reset(&only_gather_animated_images_, true);
226 size_t prev_image_set_size = image_set_.size();
227 ImageGatheringProvider image_provider(this, op_rect);
228 filter->SnapshotWithImages(&image_provider);
229
230 DCHECK_GE(image_set_.size(), prev_image_set_size);
231 const bool has_animated_images = image_set_.size() > prev_image_set_size;
232 const_cast<PaintFilter*>(filter)->set_has_animated_images(
233 has_animated_images);
234 }
235
AddImage(PaintImage paint_image,bool use_dark_mode,const SkRect & src_rect,const gfx::Rect & image_rect,const SkMatrix & matrix,SkFilterQuality filter_quality)236 void AddImage(PaintImage paint_image,
237 bool use_dark_mode,
238 const SkRect& src_rect,
239 const gfx::Rect& image_rect,
240 const SkMatrix& matrix,
241 SkFilterQuality filter_quality) {
242 if (paint_image.IsTextureBacked())
243 return;
244
245 SkIRect src_irect;
246 src_rect.roundOut(&src_irect);
247
248 if (paint_image.IsPaintWorklet()) {
249 paint_worklet_inputs_.push_back(std::make_pair(
250 paint_image.paint_worklet_input(), paint_image.stable_id()));
251 } else {
252 const auto image_color_usage = paint_image.GetContentColorUsage();
253 content_color_usage_ = std::max(content_color_usage_, image_color_usage);
254
255 if (paint_image.is_high_bit_depth())
256 contains_hbd_images_ = true;
257 }
258
259 auto& rects = image_id_to_rects_[paint_image.stable_id()];
260 if (rects->size() >= kMaxRectsSize)
261 rects->back().Union(image_rect);
262 else
263 rects->push_back(image_rect);
264
265 if (paint_image.IsLazyGenerated()) {
266 auto decoding_mode_it = decoding_mode_map_.find(paint_image.stable_id());
267 // Use the decoding mode if we don't have one yet, otherwise use the more
268 // conservative one of the two existing ones.
269 if (decoding_mode_it == decoding_mode_map_.end()) {
270 decoding_mode_map_[paint_image.stable_id()] =
271 paint_image.decoding_mode();
272 } else {
273 decoding_mode_it->second = PaintImage::GetConservative(
274 decoding_mode_it->second, paint_image.decoding_mode());
275 }
276 }
277
278 if (paint_image.ShouldAnimate()) {
279 animated_images_metadata_.emplace_back(
280 paint_image.stable_id(), paint_image.completion_state(),
281 paint_image.GetFrameMetadata(), paint_image.repetition_count(),
282 paint_image.reset_animation_sequence_id());
283 }
284
285 bool add_image = true;
286 if (paint_image.IsPaintWorklet()) {
287 // PaintWorklet-backed images don't go through the image decode pipeline
288 // (they are painted pre-raster from LayerTreeHostImpl), so do not need to
289 // be added to the |image_set_|.
290 add_image = false;
291 } else if (only_gather_animated_images_) {
292 // If we are iterating images in a record shader, only track them if they
293 // are animated. We defer decoding of images in record shaders to skia,
294 // but we still need to track animated images to invalidate and advance
295 // the animation in cc.
296 add_image = paint_image.ShouldAnimate();
297 }
298
299 if (add_image) {
300 image_set_.emplace_back(DrawImage(std::move(paint_image), use_dark_mode,
301 src_irect, filter_quality, matrix),
302 image_rect);
303 }
304 }
305
306 std::vector<std::pair<DrawImage, gfx::Rect>> image_set_;
307 base::flat_map<PaintImage::Id, DiscardableImageMap::Rects> image_id_to_rects_;
308 std::vector<DiscardableImageMap::AnimatedImageMetadata>
309 animated_images_metadata_;
310 std::vector<DiscardableImageMap::PaintWorkletInputWithImageId>
311 paint_worklet_inputs_;
312 PaintImageIdFlatSet paint_worklet_image_ids_;
313 base::flat_map<PaintImage::Id, PaintImage::DecodingMode> decoding_mode_map_;
314 bool only_gather_animated_images_ = false;
315
316 gfx::ContentColorUsage content_color_usage_ = gfx::ContentColorUsage::kSRGB;
317 bool contains_hbd_images_ = false;
318 };
319
320 } // namespace
321
322 DiscardableImageMap::DiscardableImageMap() = default;
323 DiscardableImageMap::~DiscardableImageMap() = default;
324
Generate(const PaintOpBuffer * paint_op_buffer,const gfx::Rect & bounds)325 void DiscardableImageMap::Generate(const PaintOpBuffer* paint_op_buffer,
326 const gfx::Rect& bounds) {
327 TRACE_EVENT0("cc", "DiscardableImageMap::Generate");
328
329 if (!paint_op_buffer->HasDiscardableImages())
330 return;
331
332 DiscardableImageGenerator generator(bounds.right(), bounds.bottom(),
333 paint_op_buffer);
334 image_id_to_rects_ = generator.TakeImageIdToRectsMap();
335 animated_images_metadata_ = generator.TakeAnimatedImagesMetadata();
336 paint_worklet_inputs_ = generator.TakePaintWorkletInputs();
337 decoding_mode_map_ = generator.TakeDecodingModeMap();
338 contains_hbd_images_ = generator.contains_hbd_images();
339 content_color_usage_ = generator.content_color_usage();
340 auto images = generator.TakeImages();
341 images_rtree_.Build(
342 images,
343 [](const std::vector<std::pair<DrawImage, gfx::Rect>>& items,
344 size_t index) { return items[index].second; },
345 [](const std::vector<std::pair<DrawImage, gfx::Rect>>& items,
346 size_t index) { return items[index].first; });
347 }
348
349 base::flat_map<PaintImage::Id, PaintImage::DecodingMode>
TakeDecodingModeMap()350 DiscardableImageMap::TakeDecodingModeMap() {
351 return std::move(decoding_mode_map_);
352 }
353
GetDiscardableImagesInRect(const gfx::Rect & rect,std::vector<const DrawImage * > * images) const354 void DiscardableImageMap::GetDiscardableImagesInRect(
355 const gfx::Rect& rect,
356 std::vector<const DrawImage*>* images) const {
357 images_rtree_.SearchRefs(rect, images);
358 }
359
GetRectsForImage(PaintImage::Id image_id) const360 const DiscardableImageMap::Rects& DiscardableImageMap::GetRectsForImage(
361 PaintImage::Id image_id) const {
362 static const base::NoDestructor<Rects> kEmptyRects;
363 auto it = image_id_to_rects_.find(image_id);
364 return it == image_id_to_rects_.end() ? *kEmptyRects : it->second;
365 }
366
Reset()367 void DiscardableImageMap::Reset() {
368 image_id_to_rects_.clear();
369 image_id_to_rects_.shrink_to_fit();
370 images_rtree_.Reset();
371 }
372
AnimatedImageMetadata(PaintImage::Id paint_image_id,PaintImage::CompletionState completion_state,std::vector<FrameMetadata> frames,int repetition_count,PaintImage::AnimationSequenceId reset_animation_sequence_id)373 DiscardableImageMap::AnimatedImageMetadata::AnimatedImageMetadata(
374 PaintImage::Id paint_image_id,
375 PaintImage::CompletionState completion_state,
376 std::vector<FrameMetadata> frames,
377 int repetition_count,
378 PaintImage::AnimationSequenceId reset_animation_sequence_id)
379 : paint_image_id(paint_image_id),
380 completion_state(completion_state),
381 frames(std::move(frames)),
382 repetition_count(repetition_count),
383 reset_animation_sequence_id(reset_animation_sequence_id) {}
384
385 DiscardableImageMap::AnimatedImageMetadata::~AnimatedImageMetadata() = default;
386
387 DiscardableImageMap::AnimatedImageMetadata::AnimatedImageMetadata(
388 const AnimatedImageMetadata& other) = default;
389
390 } // namespace cc
391