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