blob: 83599482f53461dd77ef6952fa45231f5ecdb68c [file] [log] [blame]
// 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 <string>
#include "base/memory/ptr_util.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "cc/base/math_util.h"
#include "cc/base/render_surface_filters.h"
#include "cc/debug/picture_debug_util.h"
#include "cc/paint/clip_display_item.h"
#include "cc/paint/clip_path_display_item.h"
#include "cc/paint/compositing_display_item.h"
#include "cc/paint/drawing_display_item.h"
#include "cc/paint/filter_display_item.h"
#include "cc/paint/float_clip_display_item.h"
#include "cc/paint/largest_display_item.h"
#include "cc/paint/transform_display_item.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkImageFilter.h"
#include "third_party/skia/include/core/SkPaint.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/skia_util.h"
namespace cc {
namespace {
// We don't perform per-layer solid color analysis when there are too many skia
// operations.
const int kOpCountThatIsOkToAnalyze = 10;
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;
}
const int kDefaultNumDisplayItemsToReserve = 100;
NOINLINE DISABLE_CFI_PERF void RasterItem(const DisplayItem& base_item,
SkCanvas* canvas,
SkPicture::AbortCallback* callback) {
switch (base_item.type) {
case DisplayItem::CLIP: {
const auto& item = static_cast<const ClipDisplayItem&>(base_item);
canvas->save();
canvas->clipRect(gfx::RectToSkRect(item.clip_rect), item.antialias);
for (const auto& rrect : item.rounded_clip_rects) {
if (rrect.isRect()) {
canvas->clipRect(rrect.rect(), item.antialias);
} else {
canvas->clipRRect(rrect, item.antialias);
}
}
break;
}
case DisplayItem::END_CLIP:
canvas->restore();
break;
case DisplayItem::CLIP_PATH: {
const auto& item = static_cast<const ClipPathDisplayItem&>(base_item);
canvas->save();
canvas->clipPath(item.clip_path, item.antialias);
break;
}
case DisplayItem::END_CLIP_PATH:
canvas->restore();
break;
case DisplayItem::COMPOSITING: {
const auto& item = static_cast<const CompositingDisplayItem&>(base_item);
SkPaint paint;
paint.setBlendMode(item.xfermode);
paint.setAlpha(item.alpha);
paint.setColorFilter(item.color_filter);
const SkRect* bounds = item.has_bounds ? &item.bounds : nullptr;
if (item.lcd_text_requires_opaque_layer)
canvas->saveLayer(bounds, &paint);
else
canvas->saveLayerPreserveLCDTextRequests(bounds, &paint);
break;
}
case DisplayItem::END_COMPOSITING:
canvas->restore();
break;
case DisplayItem::DRAWING: {
const auto& item = static_cast<const DrawingDisplayItem&>(base_item);
if (canvas->quickReject(item.picture->cullRect()))
break;
// SkPicture always does a wrapping save/restore on the canvas, so it is
// not necessary here.
item.picture->playback(canvas, callback);
break;
}
case DisplayItem::FLOAT_CLIP: {
const auto& item = static_cast<const FloatClipDisplayItem&>(base_item);
canvas->save();
canvas->clipRect(gfx::RectFToSkRect(item.clip_rect));
break;
}
case DisplayItem::END_FLOAT_CLIP:
canvas->restore();
break;
case DisplayItem::FILTER: {
const auto& item = static_cast<const FilterDisplayItem&>(base_item);
canvas->save();
canvas->translate(item.origin.x(), item.origin.y());
sk_sp<SkImageFilter> image_filter =
RenderSurfaceFilters::BuildImageFilter(item.filters,
item.bounds.size());
SkRect boundaries = RectFToSkRect(item.bounds);
boundaries.offset(-item.origin.x(), -item.origin.y());
SkPaint paint;
paint.setBlendMode(SkBlendMode::kSrcOver);
paint.setImageFilter(std::move(image_filter));
canvas->saveLayer(&boundaries, &paint);
canvas->translate(-item.origin.x(), -item.origin.y());
break;
}
case DisplayItem::END_FILTER:
canvas->restore();
canvas->restore();
break;
case DisplayItem::TRANSFORM: {
const auto& item = static_cast<const TransformDisplayItem&>(base_item);
canvas->save();
if (!item.transform.IsIdentity())
canvas->concat(item.transform.matrix());
break;
}
case DisplayItem::END_TRANSFORM:
canvas->restore();
break;
}
}
} // namespace
DisplayItemList::DisplayItemList()
: items_(LargestDisplayItemSize(),
LargestDisplayItemSize() * kDefaultNumDisplayItemsToReserve) {}
DisplayItemList::~DisplayItemList() = default;
void DisplayItemList::Raster(SkCanvas* canvas,
SkPicture::AbortCallback* callback,
const gfx::Rect& canvas_target_playback_rect,
float contents_scale) const {
canvas->save();
if (!canvas_target_playback_rect.IsEmpty()) {
// canvas_target_playback_rect is specified in device space. We can't
// use clipRect because canvas CTM will be applied on it. Use clipRegion
// instead because it ignores canvas CTM.
SkRegion device_clip;
device_clip.setRect(gfx::RectToSkIRect(canvas_target_playback_rect));
canvas->clipRegion(device_clip);
}
canvas->scale(contents_scale, contents_scale);
Raster(canvas, callback);
canvas->restore();
}
void DisplayItemList::Raster(SkCanvas* canvas,
SkPicture::AbortCallback* callback) const {
gfx::Rect canvas_playback_rect;
if (!GetCanvasClipBounds(canvas, &canvas_playback_rect))
return;
std::vector<size_t> indices;
rtree_.Search(canvas_playback_rect, &indices);
for (size_t index : indices) {
RasterItem(items_[index], canvas, callback);
// We use a callback during solid color analysis on the compositor thread to
// break out early. Since we're handling a sequence of pictures via rtree
// query results ourselves, we have to respect the callback and early out.
if (callback && callback->abort())
break;
}
}
void DisplayItemList::GrowCurrentBeginItemVisualRect(
const gfx::Rect& visual_rect) {
if (!begin_item_indices_.empty())
visual_rects_[begin_item_indices_.back()].Union(visual_rect);
}
void DisplayItemList::Finalize() {
TRACE_EVENT0("cc", "DisplayItemList::Finalize");
DCHECK(items_.size() == visual_rects_.size())
<< "items.size() " << items_.size() << " visual_rects.size() "
<< visual_rects_.size();
rtree_.Build(visual_rects_);
if (!retain_visual_rects_)
// This clears both the vector and the vector's capacity, since
// visual_rects won't be used anymore.
std::vector<gfx::Rect>().swap(visual_rects_);
}
bool DisplayItemList::IsSuitableForGpuRasterization() const {
// TODO(wkorman): This is more permissive than Picture's implementation, since
// none of the items might individually trigger a veto even though they
// collectively have enough "bad" operations that a corresponding Picture
// would get vetoed. See crbug.com/513016.
return all_items_are_suitable_for_gpu_rasterization_;
}
int DisplayItemList::ApproximateOpCount() const {
return approximate_op_count_;
}
size_t DisplayItemList::ApproximateMemoryUsage() const {
size_t memory_usage = sizeof(*this);
size_t external_memory_usage = 0;
for (const auto& item : items_) {
size_t bytes = 0;
switch (item.type) {
case DisplayItem::CLIP:
bytes = static_cast<const ClipDisplayItem&>(item).ExternalMemoryUsage();
break;
case DisplayItem::CLIP_PATH:
bytes =
static_cast<const ClipPathDisplayItem&>(item).ExternalMemoryUsage();
break;
case DisplayItem::COMPOSITING:
bytes = static_cast<const CompositingDisplayItem&>(item)
.ExternalMemoryUsage();
break;
case DisplayItem::DRAWING:
bytes =
static_cast<const DrawingDisplayItem&>(item).ExternalMemoryUsage();
break;
case DisplayItem::FLOAT_CLIP:
bytes = static_cast<const FloatClipDisplayItem&>(item)
.ExternalMemoryUsage();
break;
case DisplayItem::FILTER:
bytes =
static_cast<const FilterDisplayItem&>(item).ExternalMemoryUsage();
break;
case DisplayItem::TRANSFORM:
bytes = static_cast<const TransformDisplayItem&>(item)
.ExternalMemoryUsage();
break;
case DisplayItem::END_CLIP:
case DisplayItem::END_CLIP_PATH:
case DisplayItem::END_COMPOSITING:
case DisplayItem::END_FLOAT_CLIP:
case DisplayItem::END_FILTER:
case DisplayItem::END_TRANSFORM:
break;
}
external_memory_usage += bytes;
}
// Memory outside this class due to |items_|.
memory_usage += items_.GetCapacityInBytes() + external_memory_usage;
// TODO(jbroman): Does anything else owned by this class substantially
// contribute to memory usage?
// TODO(vmpstr): Probably DiscardableImageMap is worth counting here.
return memory_usage;
}
bool DisplayItemList::ShouldBeAnalyzedForSolidColor() const {
return ApproximateOpCount() <= kOpCountThatIsOkToAnalyze;
}
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", this, CreateTracedValue(include_items));
}
std::unique_ptr<base::trace_event::TracedValue>
DisplayItemList::CreateTracedValue(bool include_items) const {
auto state = base::MakeUnique<base::trace_event::TracedValue>();
state->BeginDictionary("params");
if (include_items) {
state->BeginArray("items");
auto visual_rects_it = visual_rects_.begin();
for (const DisplayItem& base_item : items_) {
gfx::Rect visual_rect;
if (visual_rects_it != visual_rects_.end()) {
visual_rect = *visual_rects_it;
++visual_rects_it;
}
switch (base_item.type) {
case DisplayItem::CLIP: {
const auto& item = static_cast<const ClipDisplayItem&>(base_item);
std::string output =
base::StringPrintf("ClipDisplayItem rect: [%s] visualRect: [%s]",
item.clip_rect.ToString().c_str(),
visual_rect.ToString().c_str());
for (const SkRRect& rounded_rect : item.rounded_clip_rects) {
base::StringAppendF(
&output, " rounded_rect: [rect: [%s]",
gfx::SkRectToRectF(rounded_rect.rect()).ToString().c_str());
base::StringAppendF(&output, " radii: [");
SkVector upper_left_radius =
rounded_rect.radii(SkRRect::kUpperLeft_Corner);
base::StringAppendF(&output, "[%f,%f],", upper_left_radius.x(),
upper_left_radius.y());
SkVector upper_right_radius =
rounded_rect.radii(SkRRect::kUpperRight_Corner);
base::StringAppendF(&output, " [%f,%f],", upper_right_radius.x(),
upper_right_radius.y());
SkVector lower_right_radius =
rounded_rect.radii(SkRRect::kLowerRight_Corner);
base::StringAppendF(&output, " [%f,%f],", lower_right_radius.x(),
lower_right_radius.y());
SkVector lower_left_radius =
rounded_rect.radii(SkRRect::kLowerLeft_Corner);
base::StringAppendF(&output, " [%f,%f]]", lower_left_radius.x(),
lower_left_radius.y());
}
state->AppendString(output);
break;
}
case DisplayItem::END_CLIP:
state->AppendString(
base::StringPrintf("EndClipDisplayItem visualRect: [%s]",
visual_rect.ToString().c_str()));
break;
case DisplayItem::CLIP_PATH: {
const auto& item = static_cast<const ClipPathDisplayItem&>(base_item);
state->AppendString(base::StringPrintf(
"ClipPathDisplayItem length: %d visualRect: [%s]",
item.clip_path.countPoints(), visual_rect.ToString().c_str()));
break;
}
case DisplayItem::END_CLIP_PATH:
state->AppendString(
base::StringPrintf("EndClipPathDisplayItem visualRect: [%s]",
visual_rect.ToString().c_str()));
break;
case DisplayItem::COMPOSITING: {
const auto& item =
static_cast<const CompositingDisplayItem&>(base_item);
std::string output = base::StringPrintf(
"CompositingDisplayItem alpha: %d, xfermode: %d, visualRect: "
"[%s]",
item.alpha, static_cast<int>(item.xfermode),
visual_rect.ToString().c_str());
if (item.has_bounds) {
base::StringAppendF(
&output, ", bounds: [%s]",
gfx::SkRectToRectF(item.bounds).ToString().c_str());
}
state->AppendString(output);
break;
}
case DisplayItem::END_COMPOSITING:
state->AppendString(
base::StringPrintf("EndCompositingDisplayItem visualRect: [%s]",
visual_rect.ToString().c_str()));
break;
case DisplayItem::DRAWING: {
const auto& item = static_cast<const DrawingDisplayItem&>(base_item);
state->BeginDictionary();
state->SetString("name", "DrawingDisplayItem");
state->BeginArray("visualRect");
state->AppendInteger(visual_rect.x());
state->AppendInteger(visual_rect.y());
state->AppendInteger(visual_rect.width());
state->AppendInteger(visual_rect.height());
state->EndArray();
state->BeginArray("cullRect");
state->AppendInteger(item.picture->cullRect().x());
state->AppendInteger(item.picture->cullRect().y());
state->AppendInteger(item.picture->cullRect().width());
state->AppendInteger(item.picture->cullRect().height());
state->EndArray();
std::string b64_picture;
PictureDebugUtil::SerializeAsBase64(ToSkPicture(item.picture).get(),
&b64_picture);
state->SetString("skp64", b64_picture);
state->EndDictionary();
break;
}
case DisplayItem::FILTER: {
const auto& item = static_cast<const FilterDisplayItem&>(base_item);
state->AppendString(base::StringPrintf(
"FilterDisplayItem bounds: [%s] visualRect: [%s]",
item.bounds.ToString().c_str(), visual_rect.ToString().c_str()));
break;
}
case DisplayItem::END_FILTER:
state->AppendString(
base::StringPrintf("EndFilterDisplayItem visualRect: [%s]",
visual_rect.ToString().c_str()));
break;
case DisplayItem::FLOAT_CLIP: {
const auto& item =
static_cast<const FloatClipDisplayItem&>(base_item);
state->AppendString(base::StringPrintf(
"FloatClipDisplayItem rect: [%s] visualRect: [%s]",
item.clip_rect.ToString().c_str(),
visual_rect.ToString().c_str()));
break;
}
case DisplayItem::END_FLOAT_CLIP:
state->AppendString(
base::StringPrintf("EndFloatClipDisplayItem visualRect: [%s]",
visual_rect.ToString().c_str()));
break;
case DisplayItem::TRANSFORM: {
const auto& item =
static_cast<const TransformDisplayItem&>(base_item);
state->AppendString(base::StringPrintf(
"TransformDisplayItem transform: [%s] visualRect: [%s]",
item.transform.ToString().c_str(),
visual_rect.ToString().c_str()));
break;
}
case DisplayItem::END_TRANSFORM:
state->AppendString(
base::StringPrintf("EndTransformDisplayItem visualRect: [%s]",
visual_rect.ToString().c_str()));
break;
}
}
state->EndArray(); // "items".
}
MathUtil::AddToTracedValue("layer_rect", rtree_.GetBounds(), state.get());
state->EndDictionary(); // "params".
{
SkPictureRecorder recorder;
gfx::Rect bounds = rtree_.GetBounds();
SkCanvas* canvas = recorder.beginRecording(bounds.width(), bounds.height());
canvas->translate(-bounds.x(), -bounds.y());
canvas->clipRect(gfx::RectToSkRect(bounds));
Raster(canvas, nullptr, gfx::Rect(), 1.f);
sk_sp<SkPicture> picture = recorder.finishRecordingAsPicture();
std::string b64_picture;
PictureDebugUtil::SerializeAsBase64(picture.get(), &b64_picture);
state->SetString("skp64", b64_picture);
}
return state;
}
void DisplayItemList::GenerateDiscardableImagesMetadata() {
// This should be only called once.
DCHECK(image_map_.empty());
gfx::Rect bounds = rtree_.GetBounds();
DiscardableImageMap::ScopedMetadataGenerator generator(
&image_map_, gfx::Size(bounds.right(), bounds.bottom()));
auto* canvas = generator.canvas();
for (const auto& item : items_)
RasterItem(item, canvas, nullptr);
}
void DisplayItemList::GetDiscardableImagesInRect(
const gfx::Rect& rect,
float contents_scale,
const gfx::ColorSpace& target_color_space,
std::vector<DrawImage>* images) {
image_map_.GetDiscardableImagesInRect(rect, contents_scale,
target_color_space, images);
}
gfx::Rect DisplayItemList::GetRectForImage(ImageId image_id) const {
return image_map_.GetRectForImage(image_id);
}
} // namespace cc