1 // Copyright 2014 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/display_item_list.h"
6 
7 #include <stddef.h>
8 
9 #include <map>
10 #include <string>
11 
12 #include "base/trace_event/trace_event.h"
13 #include "base/trace_event/traced_value.h"
14 #include "cc/base/math_util.h"
15 #include "cc/debug/picture_debug_util.h"
16 #include "cc/paint/solid_color_analyzer.h"
17 #include "third_party/skia/include/core/SkCanvas.h"
18 #include "third_party/skia/include/core/SkPictureRecorder.h"
19 #include "ui/gfx/geometry/rect.h"
20 #include "ui/gfx/geometry/rect_conversions.h"
21 #include "ui/gfx/geometry/rect_f.h"
22 #include "ui/gfx/skia_util.h"
23 
24 namespace cc {
25 
26 namespace {
27 
GetCanvasClipBounds(SkCanvas * canvas,gfx::Rect * clip_bounds)28 bool GetCanvasClipBounds(SkCanvas* canvas, gfx::Rect* clip_bounds) {
29   SkRect canvas_clip_bounds;
30   if (!canvas->getLocalClipBounds(&canvas_clip_bounds))
31     return false;
32   *clip_bounds = ToEnclosingRect(gfx::SkRectToRectF(canvas_clip_bounds));
33   return true;
34 }
35 
36 template <typename Function>
IterateTextContent(const PaintOpBuffer & buffer,const Function & yield,const gfx::Rect & rect)37 void IterateTextContent(const PaintOpBuffer& buffer,
38                         const Function& yield,
39                         const gfx::Rect& rect) {
40   if (!buffer.has_draw_text_ops())
41     return;
42   for (auto* op : PaintOpBuffer::Iterator(&buffer)) {
43     if (op->GetType() == PaintOpType::DrawTextBlob) {
44       yield(static_cast<DrawTextBlobOp*>(op), rect);
45     } else if (op->GetType() == PaintOpType::DrawRecord) {
46       IterateTextContent(*static_cast<DrawRecordOp*>(op)->record.get(), yield,
47                          rect);
48     }
49   }
50 }
51 
52 template <typename Function>
IterateTextContentByOffsets(const PaintOpBuffer & buffer,const std::vector<size_t> & offsets,const std::vector<gfx::Rect> & rects,const Function & yield)53 void IterateTextContentByOffsets(const PaintOpBuffer& buffer,
54                                  const std::vector<size_t>& offsets,
55                                  const std::vector<gfx::Rect>& rects,
56                                  const Function& yield) {
57   DCHECK(buffer.has_draw_text_ops());
58   DCHECK_EQ(rects.size(), offsets.size());
59   size_t index = 0;
60   for (auto* op : PaintOpBuffer::OffsetIterator(&buffer, &offsets)) {
61     if (op->GetType() == PaintOpType::DrawTextBlob) {
62       yield(static_cast<DrawTextBlobOp*>(op), rects[index]);
63     } else if (op->GetType() == PaintOpType::DrawRecord) {
64       IterateTextContent(*static_cast<DrawRecordOp*>(op)->record.get(), yield,
65                          rects[index]);
66     }
67     ++index;
68   }
69 }
70 
RotationEquivalentToAxisFlip(const SkMatrix & matrix)71 bool RotationEquivalentToAxisFlip(const SkMatrix& matrix) {
72   float skew_x = matrix.getSkewX();
73   float skew_y = matrix.getSkewY();
74   return ((skew_x == 1.f || skew_x == -1.f) &&
75           (skew_y == 1.f || skew_y == -1.f));
76 }
77 
78 }  // namespace
79 
DisplayItemList(UsageHint usage_hint)80 DisplayItemList::DisplayItemList(UsageHint usage_hint)
81     : usage_hint_(usage_hint) {
82   if (usage_hint_ == kTopLevelDisplayItemList) {
83     visual_rects_.reserve(1024);
84     offsets_.reserve(1024);
85     paired_begin_stack_.reserve(32);
86   }
87 }
88 
89 DisplayItemList::~DisplayItemList() = default;
90 
Raster(SkCanvas * canvas,ImageProvider * image_provider) const91 void DisplayItemList::Raster(SkCanvas* canvas,
92                              ImageProvider* image_provider) const {
93   DCHECK(usage_hint_ == kTopLevelDisplayItemList);
94   gfx::Rect canvas_playback_rect;
95   if (!GetCanvasClipBounds(canvas, &canvas_playback_rect))
96     return;
97 
98   std::vector<size_t> offsets;
99   rtree_.Search(canvas_playback_rect, &offsets);
100   paint_op_buffer_.Playback(canvas, PlaybackParams(image_provider), &offsets);
101 }
102 
CaptureContent(const gfx::Rect & rect,std::vector<NodeInfo> * content) const103 void DisplayItemList::CaptureContent(const gfx::Rect& rect,
104                                      std::vector<NodeInfo>* content) const {
105   if (!paint_op_buffer_.has_draw_text_ops())
106     return;
107   std::vector<size_t> offsets;
108   std::vector<gfx::Rect> rects;
109   rtree_.Search(rect, &offsets, &rects);
110   IterateTextContentByOffsets(
111       paint_op_buffer_, offsets, rects,
112       [content](const DrawTextBlobOp* op, const gfx::Rect& rect) {
113         // Only union the rect if the current is the same as the last one.
114         if (!content->empty() && content->back().node_id == op->node_id)
115           content->back().visual_rect.Union(rect);
116         else
117           content->emplace_back(op->node_id, rect);
118       });
119 }
120 
AreaOfDrawText(const gfx::Rect & rect) const121 double DisplayItemList::AreaOfDrawText(const gfx::Rect& rect) const {
122   if (!paint_op_buffer_.has_draw_text_ops())
123     return 0;
124   std::vector<size_t> offsets;
125   std::vector<gfx::Rect> rects;
126   rtree_.Search(rect, &offsets, &rects);
127   DCHECK_EQ(offsets.size(), rects.size());
128 
129   double area = 0;
130   size_t index = 0;
131   for (auto* op : PaintOpBuffer::OffsetIterator(&paint_op_buffer_, &offsets)) {
132     if (op->GetType() == PaintOpType::DrawTextBlob ||
133         // Don't walk into the record because the visual rect is already the
134         // bounding box of the sub paint operations. This works for most paint
135         // results for text generated by blink.
136         (op->GetType() == PaintOpType::DrawRecord &&
137          static_cast<DrawRecordOp*>(op)->record->has_draw_text_ops())) {
138       area += static_cast<double>(rects[index].width()) * rects[index].height();
139     }
140     ++index;
141   }
142   return area;
143 }
144 
EndPaintOfPairedEnd()145 void DisplayItemList::EndPaintOfPairedEnd() {
146 #if DCHECK_IS_ON()
147   DCHECK(IsPainting());
148   DCHECK_LT(current_range_start_, paint_op_buffer_.size());
149   current_range_start_ = kNotPainting;
150 #endif
151   if (usage_hint_ == kToBeReleasedAsPaintOpBuffer)
152     return;
153 
154   DCHECK(paired_begin_stack_.size());
155   size_t last_begin_index = paired_begin_stack_.back().first_index;
156   size_t last_begin_count = paired_begin_stack_.back().count;
157   DCHECK_GT(last_begin_count, 0u);
158 
159   // Copy the visual rect at |last_begin_index| to all indices that constitute
160   // the begin item. Note that because we possibly reallocate the
161   // |visual_rects_| buffer below, we need an actual copy instead of a const
162   // reference which can become dangling.
163   auto visual_rect = visual_rects_[last_begin_index];
164   for (size_t i = 1; i < last_begin_count; ++i)
165     visual_rects_[i + last_begin_index] = visual_rect;
166   paired_begin_stack_.pop_back();
167 
168   // Copy the visual rect of the matching begin item to the end item(s).
169   visual_rects_.resize(paint_op_buffer_.size(), visual_rect);
170 
171   // The block that ended needs to be included in the bounds of the enclosing
172   // block.
173   GrowCurrentBeginItemVisualRect(visual_rect);
174 }
175 
Finalize()176 void DisplayItemList::Finalize() {
177   TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
178                "DisplayItemList::Finalize");
179 #if DCHECK_IS_ON()
180   // If this fails a call to StartPaint() was not ended.
181   DCHECK(!IsPainting());
182   // If this fails we had more calls to EndPaintOfPairedBegin() than
183   // to EndPaintOfPairedEnd().
184   DCHECK(paired_begin_stack_.empty());
185   DCHECK_EQ(visual_rects_.size(), offsets_.size());
186 #endif
187 
188   if (usage_hint_ == kTopLevelDisplayItemList) {
189     rtree_.Build(visual_rects_,
190                  [](const std::vector<gfx::Rect>& rects, size_t index) {
191                    return rects[index];
192                  },
193                  [this](const std::vector<gfx::Rect>& rects, size_t index) {
194                    // Ignore the given rects, since the payload comes from
195                    // offsets. However, the indices match, so we can just index
196                    // into offsets.
197                    return offsets_[index];
198                  });
199   }
200   paint_op_buffer_.ShrinkToFit();
201   visual_rects_.clear();
202   visual_rects_.shrink_to_fit();
203   offsets_.clear();
204   offsets_.shrink_to_fit();
205   paired_begin_stack_.shrink_to_fit();
206 }
207 
BytesUsed() const208 size_t DisplayItemList::BytesUsed() const {
209   // TODO(jbroman): Does anything else owned by this class substantially
210   // contribute to memory usage?
211   // TODO(vmpstr): Probably DiscardableImageMap is worth counting here.
212   return sizeof(*this) + paint_op_buffer_.bytes_used();
213 }
214 
EmitTraceSnapshot() const215 void DisplayItemList::EmitTraceSnapshot() const {
216   bool include_items;
217   TRACE_EVENT_CATEGORY_GROUP_ENABLED(
218       TRACE_DISABLED_BY_DEFAULT("cc.debug.display_items"), &include_items);
219   TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
220       TRACE_DISABLED_BY_DEFAULT("cc.debug.display_items") ","
221       TRACE_DISABLED_BY_DEFAULT("cc.debug.picture") ","
222       TRACE_DISABLED_BY_DEFAULT("devtools.timeline.picture"),
223       "cc::DisplayItemList", TRACE_ID_LOCAL(this),
224       CreateTracedValue(include_items));
225 }
226 
ToString() const227 std::string DisplayItemList::ToString() const {
228   base::trace_event::TracedValueJSON value;
229   AddToValue(&value, true);
230   return value.ToFormattedJSON();
231 }
232 
233 std::unique_ptr<base::trace_event::TracedValue>
CreateTracedValue(bool include_items) const234 DisplayItemList::CreateTracedValue(bool include_items) const {
235   auto state = std::make_unique<base::trace_event::TracedValue>();
236   AddToValue(state.get(), include_items);
237   return state;
238 }
239 
AddToValue(base::trace_event::TracedValue * state,bool include_items) const240 void DisplayItemList::AddToValue(base::trace_event::TracedValue* state,
241                                  bool include_items) const {
242   state->BeginDictionary("params");
243 
244   gfx::Rect bounds;
245   if (rtree_.has_valid_bounds()) {
246     bounds = rtree_.GetBoundsOrDie();
247   } else {
248     // For tracing code, just use the entire positive quadrant if the |rtree_|
249     // has invalid bounds.
250     bounds = gfx::Rect(INT_MAX, INT_MAX);
251   }
252 
253   if (include_items) {
254     state->BeginArray("items");
255 
256     PlaybackParams params(nullptr, SkMatrix::I());
257     std::map<size_t, gfx::Rect> visual_rects = rtree_.GetAllBoundsForTracing();
258     for (const PaintOp* op : PaintOpBuffer::Iterator(&paint_op_buffer_)) {
259       state->BeginDictionary();
260       state->SetString("name", PaintOpTypeToString(op->GetType()));
261 
262       MathUtil::AddToTracedValue(
263           "visual_rect",
264           visual_rects[paint_op_buffer_.GetOpOffsetForTracing(op)], state);
265 
266       SkPictureRecorder recorder;
267       SkCanvas* canvas = recorder.beginRecording(gfx::RectToSkRect(bounds));
268       op->Raster(canvas, params);
269       sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
270 
271       if (picture->approximateOpCount()) {
272         std::string b64_picture;
273         PictureDebugUtil::SerializeAsBase64(picture.get(), &b64_picture);
274         state->SetString("skp64", b64_picture);
275       }
276 
277       state->EndDictionary();
278     }
279 
280     state->EndArray();  // "items".
281   }
282 
283   MathUtil::AddToTracedValue("layer_rect", bounds, state);
284   state->EndDictionary();  // "params".
285 
286   {
287     SkPictureRecorder recorder;
288     SkCanvas* canvas = recorder.beginRecording(gfx::RectToSkRect(bounds));
289     canvas->translate(-bounds.x(), -bounds.y());
290     canvas->clipRect(gfx::RectToSkRect(bounds));
291     Raster(canvas);
292     sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
293 
294     std::string b64_picture;
295     PictureDebugUtil::SerializeAsBase64(picture.get(), &b64_picture);
296     state->SetString("skp64", b64_picture);
297   }
298 }
299 
GenerateDiscardableImagesMetadata()300 void DisplayItemList::GenerateDiscardableImagesMetadata() {
301   DCHECK(usage_hint_ == kTopLevelDisplayItemList);
302 
303   gfx::Rect bounds;
304   if (rtree_.has_valid_bounds()) {
305     bounds = rtree_.GetBoundsOrDie();
306   } else {
307     // Bounds are only used to size an SkNoDrawCanvas, pass INT_MAX.
308     bounds = gfx::Rect(INT_MAX, INT_MAX);
309   }
310 
311   image_map_.Generate(&paint_op_buffer_, bounds);
312 }
313 
Reset()314 void DisplayItemList::Reset() {
315 #if DCHECK_IS_ON()
316   DCHECK(!IsPainting());
317   DCHECK(paired_begin_stack_.empty());
318 #endif
319 
320   rtree_.Reset();
321   image_map_.Reset();
322   paint_op_buffer_.Reset();
323   visual_rects_.clear();
324   visual_rects_.shrink_to_fit();
325   offsets_.clear();
326   offsets_.shrink_to_fit();
327   paired_begin_stack_.clear();
328   paired_begin_stack_.shrink_to_fit();
329 }
330 
ReleaseAsRecord()331 sk_sp<PaintRecord> DisplayItemList::ReleaseAsRecord() {
332   sk_sp<PaintRecord> record =
333       sk_make_sp<PaintOpBuffer>(std::move(paint_op_buffer_));
334 
335   Reset();
336   return record;
337 }
338 
GetColorIfSolidInRect(const gfx::Rect & rect,SkColor * color,int max_ops_to_analyze)339 bool DisplayItemList::GetColorIfSolidInRect(const gfx::Rect& rect,
340                                             SkColor* color,
341                                             int max_ops_to_analyze) {
342   DCHECK(usage_hint_ == kTopLevelDisplayItemList);
343   std::vector<size_t>* offsets_to_use = nullptr;
344   std::vector<size_t> offsets;
345   if (rtree_.has_valid_bounds() && !rect.Contains(rtree_.GetBoundsOrDie())) {
346     rtree_.Search(rect, &offsets);
347     offsets_to_use = &offsets;
348   }
349 
350   base::Optional<SkColor> solid_color =
351       SolidColorAnalyzer::DetermineIfSolidColor(
352           &paint_op_buffer_, rect, max_ops_to_analyze, offsets_to_use);
353   if (solid_color) {
354     *color = *solid_color;
355     return true;
356   }
357   return false;
358 }
359 
360 base::Optional<DisplayItemList::DirectlyCompositedImageResult>
GetDirectlyCompositedImageResult(gfx::Size containing_layer_bounds) const361 DisplayItemList::GetDirectlyCompositedImageResult(
362     gfx::Size containing_layer_bounds) const {
363   const PaintOpBuffer* op_buffer = nullptr;
364   if (paint_op_buffer_.size() == 1) {
365     // The actual ops are wrapped in DrawRecord if they were previously
366     // recorded.
367     if (paint_op_buffer_.GetFirstOp()->GetType() == PaintOpType::DrawRecord) {
368       const DrawRecordOp* draw_record =
369           static_cast<const DrawRecordOp*>(paint_op_buffer_.GetFirstOp());
370       op_buffer = draw_record->record.get();
371     } else {
372       op_buffer = &paint_op_buffer_;
373     }
374   } else {
375     return base::nullopt;
376   }
377 
378   const DrawImageRectOp* draw_image_rect_op = nullptr;
379   bool transpose_image_size = false;
380   constexpr size_t kNumDrawImageForOrientationOps = 10;
381   if (op_buffer->size() == 1 &&
382       op_buffer->GetFirstOp()->GetType() == PaintOpType::DrawImageRect) {
383     draw_image_rect_op =
384         static_cast<const DrawImageRectOp*>(op_buffer->GetFirstOp());
385   } else if (op_buffer->size() < kNumDrawImageForOrientationOps) {
386     // Images that respect orientation will have 5 paint operations:
387     //  (1) Save
388     //  (2) Translate
389     //  (3) Concat (rotation matrix)
390     //  (4) DrawImageRect
391     //  (5) Restore
392     // Detect these the paint op buffer and disqualify the layer as a directly
393     // composited image if any other paint op is detected.
394     for (auto* op : PaintOpBuffer::Iterator(op_buffer)) {
395       switch (op->GetType()) {
396         case PaintOpType::Save:
397         case PaintOpType::Restore:
398           break;
399         case PaintOpType::Translate: {
400           const TranslateOp* translate = static_cast<const TranslateOp*>(op);
401           if (translate->dx != 0 || translate->dy != 0)
402             return base::nullopt;
403           break;
404         }
405         case PaintOpType::Concat: {
406           // We only expect a single rotation. If we see another one, then this
407           // image won't be eligible for directly compositing.
408           if (transpose_image_size)
409             return base::nullopt;
410 
411           const ConcatOp* concat_op = static_cast<const ConcatOp*>(op);
412           if (concat_op->matrix.hasPerspective() ||
413               !concat_op->matrix.preservesAxisAlignment())
414             return base::nullopt;
415 
416           // If the rotation is not an axis flip, we'll need to transpose the
417           // width and height dimensions to account for the same transform
418           // applying when the layer bounds were calculated.
419           transpose_image_size =
420               RotationEquivalentToAxisFlip(concat_op->matrix);
421           break;
422         }
423         case PaintOpType::DrawImageRect:
424           if (draw_image_rect_op)
425             return base::nullopt;
426           draw_image_rect_op = static_cast<const DrawImageRectOp*>(op);
427           break;
428         default:
429           return base::nullopt;
430       }
431     }
432   }
433 
434   if (!draw_image_rect_op)
435     return base::nullopt;
436 
437   // The src rect must match the image size exactly, i.e. the entire image
438   // must be drawn.
439   const SkRect& src = draw_image_rect_op->src;
440   if (src.fLeft != 0 || src.fTop != 0 ||
441       src.fRight != draw_image_rect_op->image.width() ||
442       src.fBottom != draw_image_rect_op->image.height())
443     return base::nullopt;
444 
445   // The DrawImageRect op's destination rect must match the layer bounds
446   // exactly. Note that the layer bounds have already taken into account image
447   // orientation so transpose the dst width/height before comparing, if
448   // appropriate.
449   const SkRect& dst = draw_image_rect_op->dst;
450   int dst_width = transpose_image_size ? dst.fBottom : dst.fRight;
451   int dst_height = transpose_image_size ? dst.fRight : dst.fBottom;
452   if (dst.fLeft != 0 || dst.fTop != 0 ||
453       dst_width != containing_layer_bounds.width() ||
454       dst_height != containing_layer_bounds.height())
455     return base::nullopt;
456 
457   int width = transpose_image_size ? draw_image_rect_op->image.height()
458                                    : draw_image_rect_op->image.width();
459   int height = transpose_image_size ? draw_image_rect_op->image.width()
460                                     : draw_image_rect_op->image.height();
461   DirectlyCompositedImageResult result;
462   result.intrinsic_image_size = gfx::Size(width, height);
463   // Ensure the layer will use nearest neighbor when drawn by the display
464   // compositor, if required.
465   result.nearest_neighbor =
466       draw_image_rect_op->flags.getFilterQuality() == kNone_SkFilterQuality;
467   return result;
468 }
469 
470 }  // namespace cc
471