123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- // Copyright 2014 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "cc/paint/display_item_list.h"
- #include <stddef.h>
- #include <map>
- #include <string>
- #include "base/trace_event/trace_event.h"
- #include "base/trace_event/traced_value.h"
- #include "cc/base/math_util.h"
- #include "cc/debug/picture_debug_util.h"
- #include "cc/paint/solid_color_analyzer.h"
- #include "third_party/skia/include/core/SkCanvas.h"
- #include "third_party/skia/include/core/SkPictureRecorder.h"
- #include "ui/gfx/geometry/rect.h"
- #include "ui/gfx/geometry/rect_conversions.h"
- #include "ui/gfx/geometry/rect_f.h"
- #include "ui/gfx/geometry/skia_conversions.h"
- namespace cc {
- namespace {
- bool GetCanvasClipBounds(SkCanvas* canvas, gfx::Rect* clip_bounds) {
- SkRect canvas_clip_bounds;
- if (!canvas->getLocalClipBounds(&canvas_clip_bounds))
- return false;
- *clip_bounds = ToEnclosingRect(gfx::SkRectToRectF(canvas_clip_bounds));
- return true;
- }
- template <typename Function>
- void IterateTextContent(const PaintOpBuffer& buffer,
- const Function& yield,
- const gfx::Rect& rect) {
- if (!buffer.has_draw_text_ops())
- return;
- for (auto* op : PaintOpBuffer::Iterator(&buffer)) {
- if (op->GetType() == PaintOpType::DrawTextBlob) {
- yield(static_cast<DrawTextBlobOp*>(op), rect);
- } else if (op->GetType() == PaintOpType::DrawRecord) {
- IterateTextContent(*static_cast<DrawRecordOp*>(op)->record.get(), yield,
- rect);
- }
- }
- }
- template <typename Function>
- void IterateTextContentByOffsets(const PaintOpBuffer& buffer,
- const std::vector<size_t>& offsets,
- const std::vector<gfx::Rect>& rects,
- const Function& yield) {
- DCHECK(buffer.has_draw_text_ops());
- DCHECK_EQ(rects.size(), offsets.size());
- size_t index = 0;
- for (auto* op : PaintOpBuffer::OffsetIterator(&buffer, &offsets)) {
- if (op->GetType() == PaintOpType::DrawTextBlob) {
- yield(static_cast<DrawTextBlobOp*>(op), rects[index]);
- } else if (op->GetType() == PaintOpType::DrawRecord) {
- IterateTextContent(*static_cast<DrawRecordOp*>(op)->record.get(), yield,
- rects[index]);
- }
- ++index;
- }
- }
- } // namespace
- DisplayItemList::DisplayItemList(UsageHint usage_hint)
- : usage_hint_(usage_hint) {
- if (usage_hint_ == kTopLevelDisplayItemList) {
- visual_rects_.reserve(1024);
- offsets_.reserve(1024);
- paired_begin_stack_.reserve(32);
- }
- }
- DisplayItemList::~DisplayItemList() = default;
- void DisplayItemList::Raster(SkCanvas* canvas,
- ImageProvider* image_provider) const {
- DCHECK(usage_hint_ == kTopLevelDisplayItemList);
- gfx::Rect canvas_playback_rect;
- if (!GetCanvasClipBounds(canvas, &canvas_playback_rect))
- return;
- std::vector<size_t> offsets;
- rtree_.Search(canvas_playback_rect, &offsets);
- paint_op_buffer_.Playback(canvas, PlaybackParams(image_provider), &offsets);
- }
- void DisplayItemList::CaptureContent(const gfx::Rect& rect,
- std::vector<NodeInfo>* content) const {
- if (!paint_op_buffer_.has_draw_text_ops())
- return;
- std::vector<size_t> offsets;
- std::vector<gfx::Rect> rects;
- rtree_.Search(rect, &offsets, &rects);
- IterateTextContentByOffsets(
- paint_op_buffer_, offsets, rects,
- [content](const DrawTextBlobOp* op, const gfx::Rect& rect) {
- // Only union the rect if the current is the same as the last one.
- if (!content->empty() && content->back().node_id == op->node_id)
- content->back().visual_rect.Union(rect);
- else
- content->emplace_back(op->node_id, rect);
- });
- }
- double DisplayItemList::AreaOfDrawText(const gfx::Rect& rect) const {
- if (!paint_op_buffer_.has_draw_text_ops())
- return 0;
- std::vector<size_t> offsets;
- std::vector<gfx::Rect> rects;
- rtree_.Search(rect, &offsets, &rects);
- DCHECK_EQ(offsets.size(), rects.size());
- double area = 0;
- size_t index = 0;
- for (auto* op : PaintOpBuffer::OffsetIterator(&paint_op_buffer_, &offsets)) {
- if (op->GetType() == PaintOpType::DrawTextBlob ||
- // Don't walk into the record because the visual rect is already the
- // bounding box of the sub paint operations. This works for most paint
- // results for text generated by blink.
- (op->GetType() == PaintOpType::DrawRecord &&
- static_cast<DrawRecordOp*>(op)->record->has_draw_text_ops())) {
- area += static_cast<double>(rects[index].width()) * rects[index].height();
- }
- ++index;
- }
- return area;
- }
- void DisplayItemList::EndPaintOfPairedEnd() {
- #if DCHECK_IS_ON()
- DCHECK(IsPainting());
- DCHECK_LT(current_range_start_, paint_op_buffer_.size());
- current_range_start_ = kNotPainting;
- #endif
- if (usage_hint_ == kToBeReleasedAsPaintOpBuffer)
- return;
- DCHECK(paired_begin_stack_.size());
- size_t last_begin_index = paired_begin_stack_.back().first_index;
- size_t last_begin_count = paired_begin_stack_.back().count;
- DCHECK_GT(last_begin_count, 0u);
- // Copy the visual rect at |last_begin_index| to all indices that constitute
- // the begin item. Note that because we possibly reallocate the
- // |visual_rects_| buffer below, we need an actual copy instead of a const
- // reference which can become dangling.
- auto visual_rect = visual_rects_[last_begin_index];
- for (size_t i = 1; i < last_begin_count; ++i)
- visual_rects_[i + last_begin_index] = visual_rect;
- paired_begin_stack_.pop_back();
- // Copy the visual rect of the matching begin item to the end item(s).
- visual_rects_.resize(paint_op_buffer_.size(), visual_rect);
- // The block that ended needs to be included in the bounds of the enclosing
- // block.
- GrowCurrentBeginItemVisualRect(visual_rect);
- }
- void DisplayItemList::Finalize() {
- TRACE_EVENT0(TRACE_DISABLED_BY_DEFAULT("cc.debug"),
- "DisplayItemList::Finalize");
- #if DCHECK_IS_ON()
- // If this fails a call to StartPaint() was not ended.
- DCHECK(!IsPainting());
- // If this fails we had more calls to EndPaintOfPairedBegin() than
- // to EndPaintOfPairedEnd().
- DCHECK(paired_begin_stack_.empty());
- DCHECK_EQ(visual_rects_.size(), offsets_.size());
- #endif
- if (usage_hint_ == kTopLevelDisplayItemList) {
- rtree_.Build(visual_rects_,
- [](const std::vector<gfx::Rect>& rects, size_t index) {
- return rects[index];
- },
- [this](const std::vector<gfx::Rect>& rects, size_t index) {
- // Ignore the given rects, since the payload comes from
- // offsets. However, the indices match, so we can just index
- // into offsets.
- return offsets_[index];
- });
- }
- paint_op_buffer_.ShrinkToFit();
- visual_rects_.clear();
- visual_rects_.shrink_to_fit();
- offsets_.clear();
- offsets_.shrink_to_fit();
- paired_begin_stack_.shrink_to_fit();
- }
- void DisplayItemList::EmitTraceSnapshot() const {
- bool include_items;
- TRACE_EVENT_CATEGORY_GROUP_ENABLED(
- TRACE_DISABLED_BY_DEFAULT("cc.debug.display_items"), &include_items);
- TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID(
- TRACE_DISABLED_BY_DEFAULT("cc.debug.display_items") ","
- TRACE_DISABLED_BY_DEFAULT("cc.debug.picture") ","
- TRACE_DISABLED_BY_DEFAULT("devtools.timeline.picture"),
- "cc::DisplayItemList", TRACE_ID_LOCAL(this),
- CreateTracedValue(include_items));
- }
- std::string DisplayItemList::ToString() const {
- base::trace_event::TracedValueJSON value;
- AddToValue(&value, true);
- return value.ToFormattedJSON();
- }
- std::unique_ptr<base::trace_event::TracedValue>
- DisplayItemList::CreateTracedValue(bool include_items) const {
- auto state = std::make_unique<base::trace_event::TracedValue>();
- AddToValue(state.get(), include_items);
- return state;
- }
- void DisplayItemList::AddToValue(base::trace_event::TracedValue* state,
- bool include_items) const {
- state->BeginDictionary("params");
- gfx::Rect bounds;
- if (rtree_.has_valid_bounds()) {
- bounds = rtree_.GetBoundsOrDie();
- } else {
- // For tracing code, just use the entire positive quadrant if the |rtree_|
- // has invalid bounds.
- bounds = gfx::Rect(INT_MAX, INT_MAX);
- }
- if (include_items) {
- state->BeginArray("items");
- PlaybackParams params(nullptr, SkM44());
- std::map<size_t, gfx::Rect> visual_rects = rtree_.GetAllBoundsForTracing();
- for (const PaintOp* op : PaintOpBuffer::Iterator(&paint_op_buffer_)) {
- state->BeginDictionary();
- state->SetString("name", PaintOpTypeToString(op->GetType()));
- MathUtil::AddToTracedValue(
- "visual_rect",
- visual_rects[paint_op_buffer_.GetOpOffsetForTracing(op)], state);
- SkPictureRecorder recorder;
- SkCanvas* canvas = recorder.beginRecording(gfx::RectToSkRect(bounds));
- op->Raster(canvas, params);
- sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
- if (picture->approximateOpCount()) {
- std::string b64_picture;
- PictureDebugUtil::SerializeAsBase64(picture.get(), &b64_picture);
- state->SetString("skp64", b64_picture);
- }
- state->EndDictionary();
- }
- state->EndArray(); // "items".
- }
- MathUtil::AddToTracedValue("layer_rect", bounds, state);
- state->EndDictionary(); // "params".
- {
- SkPictureRecorder recorder;
- SkCanvas* canvas = recorder.beginRecording(gfx::RectToSkRect(bounds));
- canvas->translate(-bounds.x(), -bounds.y());
- canvas->clipRect(gfx::RectToSkRect(bounds));
- Raster(canvas);
- sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
- std::string b64_picture;
- PictureDebugUtil::SerializeAsBase64(picture.get(), &b64_picture);
- state->SetString("skp64", b64_picture);
- }
- }
- void DisplayItemList::GenerateDiscardableImagesMetadata() {
- DCHECK(usage_hint_ == kTopLevelDisplayItemList);
- gfx::Rect bounds;
- if (rtree_.has_valid_bounds()) {
- bounds = rtree_.GetBoundsOrDie();
- } else {
- // Bounds are only used to size an SkNoDrawCanvas, pass INT_MAX.
- bounds = gfx::Rect(INT_MAX, INT_MAX);
- }
- image_map_.Generate(&paint_op_buffer_, bounds);
- }
- void DisplayItemList::Reset() {
- #if DCHECK_IS_ON()
- DCHECK(!IsPainting());
- DCHECK(paired_begin_stack_.empty());
- #endif
- rtree_.Reset();
- image_map_.Reset();
- paint_op_buffer_.Reset();
- visual_rects_.clear();
- visual_rects_.shrink_to_fit();
- offsets_.clear();
- offsets_.shrink_to_fit();
- paired_begin_stack_.clear();
- paired_begin_stack_.shrink_to_fit();
- }
- sk_sp<PaintRecord> DisplayItemList::ReleaseAsRecord() {
- sk_sp<PaintRecord> record =
- sk_make_sp<PaintOpBuffer>(std::move(paint_op_buffer_));
- Reset();
- return record;
- }
- bool DisplayItemList::GetColorIfSolidInRect(const gfx::Rect& rect,
- SkColor4f* color,
- int max_ops_to_analyze) {
- DCHECK(usage_hint_ == kTopLevelDisplayItemList);
- std::vector<size_t>* offsets_to_use = nullptr;
- std::vector<size_t> offsets;
- if (rtree_.has_valid_bounds() && !rect.Contains(rtree_.GetBoundsOrDie())) {
- rtree_.Search(rect, &offsets);
- offsets_to_use = &offsets;
- }
- absl::optional<SkColor4f> solid_color =
- SolidColorAnalyzer::DetermineIfSolidColor(
- &paint_op_buffer_, rect, max_ops_to_analyze, offsets_to_use);
- if (solid_color) {
- *color = *solid_color;
- return true;
- }
- return false;
- }
- namespace {
- absl::optional<DisplayItemList::DirectlyCompositedImageResult>
- DirectlyCompositedImageResultForPaintOpBuffer(const PaintOpBuffer& op_buffer) {
- // A PaintOpBuffer for an image may have 1 (a DrawImageRect or a DrawRecord
- // that recursively contains a PaintOpBuffer for an image) or 4 paint
- // operations:
- // (1) Save
- // (2) Translate which applies an offset of the image in the layer
- // or Concat with a transformation rotating the image by +/-90 degrees for
- // image orientation
- // (3) DrawImageRect or DrawRecord (see the 1 operation case above)
- // (4) Restore
- // The following algorithm also supports Translate and Concat in the same
- // PaintOpBuffer (i.e. 5 operations).
- constexpr size_t kMaxDrawImageOps = 5;
- if (op_buffer.size() > kMaxDrawImageOps)
- return absl::nullopt;
- bool transpose_image_size = false;
- absl::optional<DisplayItemList::DirectlyCompositedImageResult> result;
- for (auto* op : PaintOpBuffer::Iterator(&op_buffer)) {
- switch (op->GetType()) {
- case PaintOpType::Save:
- case PaintOpType::Restore:
- case PaintOpType::Translate:
- break;
- case PaintOpType::Concat: {
- // We only expect a single transformation. If we see another one, then
- // this image won't be eligible for directly compositing.
- if (transpose_image_size)
- return absl::nullopt;
- // The transformation must be before the DrawImageRect operation.
- if (result)
- return absl::nullopt;
- const ConcatOp* concat_op = static_cast<const ConcatOp*>(op);
- if (!MathUtil::SkM44Preserves2DAxisAlignment(concat_op->matrix))
- return absl::nullopt;
- // If the image has been rotated +/-90 degrees we'll need to transpose
- // the width and height dimensions to account for the same transform
- // applying when the layer bounds were calculated. Since we already
- // know that the transformation preserves axis alignment, we only
- // need to confirm that this is not a scaling operation.
- transpose_image_size = (concat_op->matrix.rc(0, 0) == 0);
- break;
- }
- case PaintOpType::DrawImageRect: {
- if (result)
- return absl::nullopt;
- const auto* draw_image_rect_op =
- static_cast<const DrawImageRectOp*>(op);
- const SkRect& src = draw_image_rect_op->src;
- const SkRect& dst = draw_image_rect_op->dst;
- if (src.isEmpty() || dst.isEmpty())
- return absl::nullopt;
- result.emplace();
- result->default_raster_scale = gfx::Vector2dF(
- src.width() / dst.width(), src.height() / dst.height());
- // Ensure the layer will use nearest neighbor when drawn by the display
- // compositor, if required.
- result->nearest_neighbor =
- draw_image_rect_op->flags.getFilterQuality() ==
- PaintFlags::FilterQuality::kNone;
- break;
- }
- case PaintOpType::DrawRecord:
- if (result)
- return absl::nullopt;
- result = DirectlyCompositedImageResultForPaintOpBuffer(
- *static_cast<const DrawRecordOp*>(op)->record);
- if (!result)
- return absl::nullopt;
- break;
- default:
- // Disqualify the layer as a directly composited image if any other
- // paint op is detected.
- return absl::nullopt;
- }
- }
- if (result && transpose_image_size)
- result->default_raster_scale.Transpose();
- return result;
- }
- } // anonymous namespace
- absl::optional<DisplayItemList::DirectlyCompositedImageResult>
- DisplayItemList::GetDirectlyCompositedImageResult() const {
- return DirectlyCompositedImageResultForPaintOpBuffer(paint_op_buffer_);
- }
- } // namespace cc
|