diff options
author | danakj <danakj@chromium.org> | 2015-05-18 13:22:29 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-05-18 20:22:35 +0000 |
commit | 92015685c3dc2ef005e58c975269f532468e68b8 (patch) | |
tree | 94d71b2784e5a7e640cba6883d8c954cbabbca3c /cc/playback | |
parent | e3417c1e06cb80a5d1493e66a7f872297bd94e8f (diff) | |
download | chromium_src-92015685c3dc2ef005e58c975269f532468e68b8.zip chromium_src-92015685c3dc2ef005e58c975269f532468e68b8.tar.gz chromium_src-92015685c3dc2ef005e58c975269f532468e68b8.tar.bz2 |
cc: Move files out of cc/resources/.
This moves files into cc/playback/ cc/raster/ cc/tiles/ and cc/output/
and is based on the proposal found in
https://docs.google.com/spreadsheets/d/1wmPOmV9uqd9zNJ5l2TGePH7_vlSvPeXbC-Y3SeGJ_rc/edit#gid=0
R=enne, vmpstr
TBR=piman
BUG=488755
Review URL: https://codereview.chromium.org/1144693002
Cr-Commit-Position: refs/heads/master@{#330396}
Diffstat (limited to 'cc/playback')
44 files changed, 7760 insertions, 0 deletions
diff --git a/cc/playback/clip_display_item.cc b/cc/playback/clip_display_item.cc new file mode 100644 index 0000000..a297874 --- /dev/null +++ b/cc/playback/clip_display_item.cc @@ -0,0 +1,95 @@ +// 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/playback/clip_display_item.h" + +#include <string> + +#include "base/strings/stringprintf.h" +#include "base/trace_event/trace_event_argument.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "ui/gfx/skia_util.h" + +namespace cc { + +ClipDisplayItem::ClipDisplayItem() { +} + +ClipDisplayItem::~ClipDisplayItem() { +} + +void ClipDisplayItem::SetNew(gfx::Rect clip_rect, + const std::vector<SkRRect>& rounded_clip_rects) { + clip_rect_ = clip_rect; + rounded_clip_rects_ = rounded_clip_rects; + + size_t memory_usage = sizeof(gfx::Rect); + for (size_t i = 0; i < rounded_clip_rects_.size(); ++i) { + memory_usage += sizeof(rounded_clip_rects_[i]); + } + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 1 /* op_count */, + memory_usage); +} + +void ClipDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + canvas->save(); + canvas->clipRect(SkRect::MakeXYWH(clip_rect_.x(), clip_rect_.y(), + clip_rect_.width(), clip_rect_.height())); + for (size_t i = 0; i < rounded_clip_rects_.size(); ++i) { + if (rounded_clip_rects_[i].isRect()) { + canvas->clipRect(rounded_clip_rects_[i].rect()); + } else { + bool antialiased = true; + canvas->clipRRect(rounded_clip_rects_[i], SkRegion::kIntersect_Op, + antialiased); + } + } +} + +void ClipDisplayItem::AsValueInto(base::trace_event::TracedValue* array) const { + std::string value = base::StringPrintf("ClipDisplayItem rect: [%s]", + clip_rect_.ToString().c_str()); + for (const SkRRect& rounded_rect : rounded_clip_rects_) { + base::StringAppendF( + &value, " rounded_rect: [rect: [%s]", + gfx::SkRectToRectF(rounded_rect.rect()).ToString().c_str()); + base::StringAppendF(&value, " radii: ["); + SkVector upper_left_radius = rounded_rect.radii(SkRRect::kUpperLeft_Corner); + base::StringAppendF(&value, "[%f,%f],", upper_left_radius.x(), + upper_left_radius.y()); + SkVector upper_right_radius = + rounded_rect.radii(SkRRect::kUpperRight_Corner); + base::StringAppendF(&value, " [%f,%f],", upper_right_radius.x(), + upper_right_radius.y()); + SkVector lower_right_radius = + rounded_rect.radii(SkRRect::kLowerRight_Corner); + base::StringAppendF(&value, " [%f,%f],", lower_right_radius.x(), + lower_right_radius.y()); + SkVector lower_left_radius = rounded_rect.radii(SkRRect::kLowerLeft_Corner); + base::StringAppendF(&value, " [%f,%f]]", lower_left_radius.x(), + lower_left_radius.y()); + } + array->AppendString(value); +} + +EndClipDisplayItem::EndClipDisplayItem() { + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 0 /* op_count */, + 0 /* memory_usage */); +} + +EndClipDisplayItem::~EndClipDisplayItem() { +} + +void EndClipDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + canvas->restore(); +} + +void EndClipDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->AppendString("EndClipDisplayItem"); +} + +} // namespace cc diff --git a/cc/playback/clip_display_item.h b/cc/playback/clip_display_item.h new file mode 100644 index 0000000..a4acc9f --- /dev/null +++ b/cc/playback/clip_display_item.h @@ -0,0 +1,48 @@ +// 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. + +#ifndef CC_PLAYBACK_CLIP_DISPLAY_ITEM_H_ +#define CC_PLAYBACK_CLIP_DISPLAY_ITEM_H_ + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/playback/display_item.h" +#include "third_party/skia/include/core/SkRRect.h" +#include "ui/gfx/geometry/rect.h" + +class SkCanvas; +class SkDrawPictureCallback; + +namespace cc { + +class CC_EXPORT ClipDisplayItem : public DisplayItem { + public: + ClipDisplayItem(); + ~ClipDisplayItem() override; + + void SetNew(gfx::Rect clip_rect, + const std::vector<SkRRect>& rounded_clip_rects); + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; + + private: + gfx::Rect clip_rect_; + std::vector<SkRRect> rounded_clip_rects_; +}; + +class CC_EXPORT EndClipDisplayItem : public DisplayItem { + public: + EndClipDisplayItem(); + ~EndClipDisplayItem() override; + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; +}; + +} // namespace cc + +#endif // CC_PLAYBACK_CLIP_DISPLAY_ITEM_H_ diff --git a/cc/playback/clip_path_display_item.cc b/cc/playback/clip_path_display_item.cc new file mode 100644 index 0000000..7be8922 --- /dev/null +++ b/cc/playback/clip_path_display_item.cc @@ -0,0 +1,61 @@ +// Copyright 2015 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/playback/clip_path_display_item.h" + +#include "base/strings/stringprintf.h" +#include "base/trace_event/trace_event_argument.h" +#include "third_party/skia/include/core/SkCanvas.h" + +namespace cc { + +ClipPathDisplayItem::ClipPathDisplayItem() { +} + +ClipPathDisplayItem::~ClipPathDisplayItem() { +} + +void ClipPathDisplayItem::SetNew(const SkPath& clip_path, + SkRegion::Op clip_op, + bool antialias) { + clip_path_ = clip_path; + clip_op_ = clip_op; + antialias_ = antialias; + + size_t memory_usage = sizeof(SkPath) + sizeof(SkRegion::Op) + sizeof(bool); + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 1 /* op_count */, + memory_usage); +} + +void ClipPathDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + canvas->save(); + canvas->clipPath(clip_path_, clip_op_, antialias_); +} + +void ClipPathDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->AppendString(base::StringPrintf("ClipPathDisplayItem length: %d", + clip_path_.countPoints())); +} + +EndClipPathDisplayItem::EndClipPathDisplayItem() { + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 0 /* op_count */, + 0 /* memory_usage */); +} + +EndClipPathDisplayItem::~EndClipPathDisplayItem() { +} + +void EndClipPathDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + canvas->restore(); +} + +void EndClipPathDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->AppendString("EndClipPathDisplayItem"); +} + +} // namespace cc diff --git a/cc/playback/clip_path_display_item.h b/cc/playback/clip_path_display_item.h new file mode 100644 index 0000000..19d4a7c --- /dev/null +++ b/cc/playback/clip_path_display_item.h @@ -0,0 +1,50 @@ +// Copyright 2015 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. + +#ifndef CC_PLAYBACK_CLIP_PATH_DISPLAY_ITEM_H_ +#define CC_PLAYBACK_CLIP_PATH_DISPLAY_ITEM_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/playback/display_item.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkRegion.h" + +class SkCanvas; +class SkDrawPictureCallback; + +namespace cc { + +class CC_EXPORT ClipPathDisplayItem : public DisplayItem { + public: + ClipPathDisplayItem(); + ~ClipPathDisplayItem() override; + + void SetNew(const SkPath& path, SkRegion::Op clip_op, bool antialias); + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; + + private: + SkPath clip_path_; + SkRegion::Op clip_op_; + bool antialias_; +}; + +class CC_EXPORT EndClipPathDisplayItem : public DisplayItem { + public: + EndClipPathDisplayItem(); + ~EndClipPathDisplayItem() override; + + static scoped_ptr<EndClipPathDisplayItem> Create() { + return make_scoped_ptr(new EndClipPathDisplayItem()); + } + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; +}; + +} // namespace cc + +#endif // CC_PLAYBACK_CLIP_PATH_DISPLAY_ITEM_H_ diff --git a/cc/playback/compositing_display_item.cc b/cc/playback/compositing_display_item.cc new file mode 100644 index 0000000..bee1fb1 --- /dev/null +++ b/cc/playback/compositing_display_item.cc @@ -0,0 +1,78 @@ +// Copyright 2015 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/playback/compositing_display_item.h" + +#include "base/strings/stringprintf.h" +#include "base/trace_event/trace_event_argument.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkXfermode.h" +#include "ui/gfx/skia_util.h" + +namespace cc { + +CompositingDisplayItem::CompositingDisplayItem() { +} + +CompositingDisplayItem::~CompositingDisplayItem() { +} + +void CompositingDisplayItem::SetNew(uint8_t alpha, + SkXfermode::Mode xfermode, + SkRect* bounds, + skia::RefPtr<SkColorFilter> cf) { + alpha_ = alpha; + xfermode_ = xfermode; + has_bounds_ = !!bounds; + if (bounds) + bounds_ = SkRect(*bounds); + color_filter_ = cf; + + // TODO(pdr): Include color_filter's memory here. + size_t memory_usage = + sizeof(float) + sizeof(bool) + sizeof(SkRect) + sizeof(SkXfermode::Mode); + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 1 /* op_count */, + memory_usage); +} + +void CompositingDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + SkPaint paint; + paint.setXfermodeMode(xfermode_); + paint.setAlpha(alpha_); + paint.setColorFilter(color_filter_.get()); + canvas->saveLayer(has_bounds_ ? &bounds_ : nullptr, &paint); +} + +void CompositingDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->AppendString(base::StringPrintf( + "CompositingDisplayItem alpha: %d, xfermode: %d", alpha_, xfermode_)); + if (has_bounds_) + array->AppendString(base::StringPrintf( + ", bounds: [%f, %f, %f, %f]", static_cast<float>(bounds_.x()), + static_cast<float>(bounds_.y()), static_cast<float>(bounds_.width()), + static_cast<float>(bounds_.height()))); +} + +EndCompositingDisplayItem::EndCompositingDisplayItem() { + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 0 /* op_count */, + 0 /* memory_usage */); +} + +EndCompositingDisplayItem::~EndCompositingDisplayItem() { +} + +void EndCompositingDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + canvas->restore(); +} + +void EndCompositingDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->AppendString("EndCompositingDisplayItem"); +} + +} // namespace cc diff --git a/cc/playback/compositing_display_item.h b/cc/playback/compositing_display_item.h new file mode 100644 index 0000000..d2af8d5 --- /dev/null +++ b/cc/playback/compositing_display_item.h @@ -0,0 +1,58 @@ +// Copyright 2015 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. + +#ifndef CC_PLAYBACK_COMPOSITING_DISPLAY_ITEM_H_ +#define CC_PLAYBACK_COMPOSITING_DISPLAY_ITEM_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/playback/display_item.h" +#include "skia/ext/refptr.h" +#include "third_party/skia/include/core/SkColorFilter.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/core/SkXfermode.h" +#include "ui/gfx/geometry/rect_f.h" + +class SkCanvas; +class SkDrawPictureCallback; + +namespace cc { + +class CC_EXPORT CompositingDisplayItem : public DisplayItem { + public: + CompositingDisplayItem(); + ~CompositingDisplayItem() override; + + void SetNew(uint8_t alpha, + SkXfermode::Mode xfermode, + SkRect* bounds, + skia::RefPtr<SkColorFilter> color_filter); + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; + + private: + uint8_t alpha_; + SkXfermode::Mode xfermode_; + bool has_bounds_; + SkRect bounds_; + skia::RefPtr<SkColorFilter> color_filter_; +}; + +class CC_EXPORT EndCompositingDisplayItem : public DisplayItem { + public: + EndCompositingDisplayItem(); + ~EndCompositingDisplayItem() override; + + static scoped_ptr<EndCompositingDisplayItem> Create() { + return make_scoped_ptr(new EndCompositingDisplayItem()); + } + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; +}; + +} // namespace cc + +#endif // CC_PLAYBACK_COMPOSITING_DISPLAY_ITEM_H_ diff --git a/cc/playback/display_item.cc b/cc/playback/display_item.cc new file mode 100644 index 0000000..cbfee03 --- /dev/null +++ b/cc/playback/display_item.cc @@ -0,0 +1,12 @@ +// 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/playback/display_item.h" + +namespace cc { + +DisplayItem::DisplayItem() { +} + +} // namespace cc diff --git a/cc/playback/display_item.h b/cc/playback/display_item.h new file mode 100644 index 0000000..2eedb91 --- /dev/null +++ b/cc/playback/display_item.h @@ -0,0 +1,51 @@ +// 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. + +#ifndef CC_PLAYBACK_DISPLAY_ITEM_H_ +#define CC_PLAYBACK_DISPLAY_ITEM_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/debug/traced_value.h" +#include "ui/gfx/geometry/rect.h" + +class SkCanvas; +class SkDrawPictureCallback; + +namespace cc { + +class CC_EXPORT DisplayItem { + public: + virtual ~DisplayItem() {} + + void SetNew(bool is_suitable_for_gpu_rasterization, + int approximate_op_count, + size_t picture_memory_usage) { + is_suitable_for_gpu_rasterization_ = is_suitable_for_gpu_rasterization; + approximate_op_count_ = approximate_op_count; + picture_memory_usage_ = + picture_memory_usage + sizeof(bool) + sizeof(int) + sizeof(size_t); + } + + virtual void Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const = 0; + virtual void AsValueInto(base::trace_event::TracedValue* array) const = 0; + + bool is_suitable_for_gpu_rasterization() const { + return is_suitable_for_gpu_rasterization_; + } + int approximate_op_count() const { return approximate_op_count_; } + size_t picture_memory_usage() const { return picture_memory_usage_; } + + protected: + DisplayItem(); + + bool is_suitable_for_gpu_rasterization_; + int approximate_op_count_; + size_t picture_memory_usage_; +}; + +} // namespace cc + +#endif // CC_PLAYBACK_DISPLAY_ITEM_H_ diff --git a/cc/playback/display_item_list.cc b/cc/playback/display_item_list.cc new file mode 100644 index 0000000..656ade1 --- /dev/null +++ b/cc/playback/display_item_list.cc @@ -0,0 +1,229 @@ +// 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/playback/display_item_list.h" + +#include <string> + +#include "base/trace_event/trace_event.h" +#include "base/trace_event/trace_event_argument.h" +#include "cc/base/math_util.h" +#include "cc/debug/picture_debug_util.h" +#include "cc/debug/traced_picture.h" +#include "cc/debug/traced_value.h" +#include "cc/playback/largest_display_item.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkDrawPictureCallback.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" +#include "third_party/skia/include/utils/SkPictureUtils.h" +#include "ui/gfx/skia_util.h" + +namespace cc { + +namespace { + +bool PictureTracingEnabled() { + bool tracing_enabled; + TRACE_EVENT_CATEGORY_GROUP_ENABLED( + TRACE_DISABLED_BY_DEFAULT("cc.debug.picture") "," + TRACE_DISABLED_BY_DEFAULT("devtools.timeline.picture"), + &tracing_enabled); + return tracing_enabled; +} + +const int kDefaultNumDisplayItemsToReserve = 100; + +} // namespace + +DisplayItemList::DisplayItemList(gfx::Rect layer_rect, + bool use_cached_picture, + bool retain_individual_display_items) + : items_(LargestDisplayItemSize(), kDefaultNumDisplayItemsToReserve), + use_cached_picture_(use_cached_picture), + retain_individual_display_items_(retain_individual_display_items), + layer_rect_(layer_rect), + is_suitable_for_gpu_rasterization_(true), + approximate_op_count_(0), + picture_memory_usage_(0) { +#if DCHECK_IS_ON() + needs_process_ = false; +#endif + if (use_cached_picture_) { + SkRTreeFactory factory; + recorder_.reset(new SkPictureRecorder()); + canvas_ = skia::SharePtr(recorder_->beginRecording( + layer_rect_.width(), layer_rect_.height(), &factory)); + canvas_->translate(-layer_rect_.x(), -layer_rect_.y()); + canvas_->clipRect(gfx::RectToSkRect(layer_rect_)); + } +} + +DisplayItemList::DisplayItemList(gfx::Rect layer_rect, bool use_cached_picture) + : DisplayItemList(layer_rect, + use_cached_picture, + !use_cached_picture || PictureTracingEnabled()) { +} + +scoped_refptr<DisplayItemList> DisplayItemList::Create( + gfx::Rect layer_rect, + bool use_cached_picture) { + return make_scoped_refptr( + new DisplayItemList(layer_rect, use_cached_picture)); +} + +DisplayItemList::~DisplayItemList() { +} + +void DisplayItemList::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback, + float contents_scale) const { + DCHECK(ProcessAppendedItemsCalled()); + if (!use_cached_picture_) { + canvas->save(); + canvas->scale(contents_scale, contents_scale); + for (auto* item : items_) + item->Raster(canvas, callback); + canvas->restore(); + } else { + DCHECK(picture_); + + canvas->save(); + canvas->scale(contents_scale, contents_scale); + canvas->translate(layer_rect_.x(), layer_rect_.y()); + if (callback) { + // If we have a callback, we need to call |draw()|, |drawPicture()| + // doesn't take a callback. This is used by |AnalysisCanvas| to early + // out. + picture_->playback(canvas, callback); + } else { + // Prefer to call |drawPicture()| on the canvas since it could place the + // entire picture on the canvas instead of parsing the skia operations. + canvas->drawPicture(picture_.get()); + } + canvas->restore(); + } +} + +void DisplayItemList::ProcessAppendedItemsOnTheFly() { + if (retain_individual_display_items_) + return; + if (items_.size() >= kDefaultNumDisplayItemsToReserve) { + ProcessAppendedItems(); + // This function exists to keep the |items_| from growing indefinitely if + // we're not going to store them anyway. So the items better be deleted + // after |items_| grows too large and we process it. + DCHECK(items_.empty()); + } +} + +void DisplayItemList::ProcessAppendedItems() { +#if DCHECK_IS_ON() + needs_process_ = false; +#endif + for (DisplayItem* item : items_) { + is_suitable_for_gpu_rasterization_ &= + item->is_suitable_for_gpu_rasterization(); + approximate_op_count_ += item->approximate_op_count(); + + if (use_cached_picture_) { + DCHECK(canvas_); + item->Raster(canvas_.get(), NULL); + } + + if (retain_individual_display_items_) { + // Warning: this double-counts SkPicture data if use_cached_picture_ is + // also true. + picture_memory_usage_ += item->picture_memory_usage(); + } + } + + if (!retain_individual_display_items_) + items_.clear(); +} + +void DisplayItemList::CreateAndCacheSkPicture() { + DCHECK(ProcessAppendedItemsCalled()); + // Convert to an SkPicture for faster rasterization. + DCHECK(use_cached_picture_); + DCHECK(!picture_); + picture_ = skia::AdoptRef(recorder_->endRecordingAsPicture()); + DCHECK(picture_); + picture_memory_usage_ += SkPictureUtils::ApproximateBytesUsed(picture_.get()); + recorder_.reset(); + canvas_.clear(); +} + +bool DisplayItemList::IsSuitableForGpuRasterization() const { + DCHECK(ProcessAppendedItemsCalled()); + // 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. + return is_suitable_for_gpu_rasterization_; +} + +int DisplayItemList::ApproximateOpCount() const { + DCHECK(ProcessAppendedItemsCalled()); + return approximate_op_count_; +} + +size_t DisplayItemList::PictureMemoryUsage() const { + DCHECK(ProcessAppendedItemsCalled()); + // We double-count in this case. Produce zero to avoid being misleading. + if (use_cached_picture_ && retain_individual_display_items_) + return 0; + + DCHECK_IMPLIES(use_cached_picture_, picture_); + return picture_memory_usage_; +} + +scoped_refptr<base::trace_event::ConvertableToTraceFormat> +DisplayItemList::AsValue() const { + DCHECK(ProcessAppendedItemsCalled()); + scoped_refptr<base::trace_event::TracedValue> state = + new base::trace_event::TracedValue(); + + state->SetInteger("length", items_.size()); + state->BeginArray("params.items"); + for (const DisplayItem* item : items_) { + item->AsValueInto(state.get()); + } + state->EndArray(); + state->SetValue("params.layer_rect", MathUtil::AsValue(layer_rect_)); + + SkPictureRecorder recorder; + SkCanvas* canvas = + recorder.beginRecording(layer_rect_.width(), layer_rect_.height()); + canvas->translate(-layer_rect_.x(), -layer_rect_.y()); + canvas->clipRect(gfx::RectToSkRect(layer_rect_)); + Raster(canvas, NULL, 1.f); + skia::RefPtr<SkPicture> picture = + skia::AdoptRef(recorder.endRecordingAsPicture()); + + std::string b64_picture; + PictureDebugUtil::SerializeAsBase64(picture.get(), &b64_picture); + state->SetString("skp64", b64_picture); + + return state; +} + +void DisplayItemList::EmitTraceSnapshot() const { + DCHECK(ProcessAppendedItemsCalled()); + TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( + TRACE_DISABLED_BY_DEFAULT("cc.debug.picture") "," + TRACE_DISABLED_BY_DEFAULT("devtools.timeline.picture"), + "cc::DisplayItemList", this, AsValue()); +} + +void DisplayItemList::GatherPixelRefs(const gfx::Size& grid_cell_size) { + DCHECK(ProcessAppendedItemsCalled()); + // This should be only called once, and only after CreateAndCacheSkPicture. + DCHECK(picture_); + DCHECK(!pixel_refs_); + pixel_refs_ = make_scoped_ptr(new PixelRefMap(grid_cell_size)); + if (!picture_->willPlayBackBitmaps()) + return; + + pixel_refs_->GatherPixelRefsFromPicture(picture_.get()); +} +} // namespace cc diff --git a/cc/playback/display_item_list.h b/cc/playback/display_item_list.h new file mode 100644 index 0000000..84b4168 --- /dev/null +++ b/cc/playback/display_item_list.h @@ -0,0 +1,100 @@ +// 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. + +#ifndef CC_PLAYBACK_DISPLAY_ITEM_LIST_H_ +#define CC_PLAYBACK_DISPLAY_ITEM_LIST_H_ + +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/trace_event/trace_event.h" +#include "cc/base/cc_export.h" +#include "cc/base/scoped_ptr_vector.h" +#include "cc/playback/display_item.h" +#include "cc/playback/pixel_ref_map.h" +// TODO(danakj): Move ListContainer out of cc/quads/ +#include "cc/quads/list_container.h" +#include "skia/ext/refptr.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "ui/gfx/geometry/rect.h" + +class SkCanvas; +class SkDrawPictureCallback; +class SkPictureRecorder; + +namespace cc { + +class CC_EXPORT DisplayItemList + : public base::RefCountedThreadSafe<DisplayItemList> { + public: + static scoped_refptr<DisplayItemList> Create(gfx::Rect layer_rect, + bool use_cached_picture); + + void Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback, + float contents_scale) const; + + template <typename DisplayItemType> + DisplayItemType* CreateAndAppendItem() { +#if DCHECK_IS_ON() + needs_process_ = true; +#endif + ProcessAppendedItemsOnTheFly(); + return items_.AllocateAndConstruct<DisplayItemType>(); + } + + void ProcessAppendedItems(); + void CreateAndCacheSkPicture(); + + bool IsSuitableForGpuRasterization() const; + int ApproximateOpCount() const; + size_t PictureMemoryUsage() const; + + scoped_refptr<base::trace_event::ConvertableToTraceFormat> AsValue() const; + + void EmitTraceSnapshot() const; + + void GatherPixelRefs(const gfx::Size& grid_cell_size); + + private: + DisplayItemList(gfx::Rect layer_rect, + bool use_cached_picture, + bool retain_individual_display_items); + DisplayItemList(gfx::Rect layer_rect, bool use_cached_picture); + ~DisplayItemList(); + + // While appending new items, if they are not being retained, this can process + // periodically to avoid retaining all the items and processing at the end. + void ProcessAppendedItemsOnTheFly(); +#if DCHECK_IS_ON() + bool ProcessAppendedItemsCalled() const { return !needs_process_; } + bool needs_process_; +#else + bool ProcessAppendedItemsCalled() const { return true; } +#endif + + ListContainer<DisplayItem> items_; + skia::RefPtr<SkPicture> picture_; + + scoped_ptr<SkPictureRecorder> recorder_; + skia::RefPtr<SkCanvas> canvas_; + bool use_cached_picture_; + bool retain_individual_display_items_; + + gfx::Rect layer_rect_; + bool is_suitable_for_gpu_rasterization_; + int approximate_op_count_; + size_t picture_memory_usage_; + + scoped_ptr<PixelRefMap> pixel_refs_; + + friend class base::RefCountedThreadSafe<DisplayItemList>; + friend class PixelRefMap::Iterator; + FRIEND_TEST_ALL_PREFIXES(DisplayItemListTest, PictureMemoryUsage); + DISALLOW_COPY_AND_ASSIGN(DisplayItemList); +}; + +} // namespace cc + +#endif // CC_PLAYBACK_DISPLAY_ITEM_LIST_H_ diff --git a/cc/playback/display_item_list_unittest.cc b/cc/playback/display_item_list_unittest.cc new file mode 100644 index 0000000..6a5bbf6 --- /dev/null +++ b/cc/playback/display_item_list_unittest.cc @@ -0,0 +1,340 @@ +// 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/playback/display_item_list.h" + +#include <vector> + +#include "cc/output/filter_operation.h" +#include "cc/output/filter_operations.h" +#include "cc/playback/clip_display_item.h" +#include "cc/playback/drawing_display_item.h" +#include "cc/playback/filter_display_item.h" +#include "cc/playback/transform_display_item.h" +#include "cc/test/skia_common.h" +#include "skia/ext/refptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" +#include "third_party/skia/include/effects/SkBitmapSource.h" +#include "third_party/skia/include/utils/SkPictureUtils.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/skia_util.h" + +namespace cc { + +TEST(DisplayItemListTest, SingleDrawingItem) { + gfx::Rect layer_rect(100, 100); + SkPictureRecorder recorder; + skia::RefPtr<SkCanvas> canvas; + skia::RefPtr<SkPicture> picture; + SkPaint blue_paint; + blue_paint.setColor(SK_ColorBLUE); + SkPaint red_paint; + red_paint.setColor(SK_ColorRED); + unsigned char pixels[4 * 100 * 100] = {0}; + const bool use_cached_picture = true; + scoped_refptr<DisplayItemList> list = + DisplayItemList::Create(layer_rect, use_cached_picture); + + gfx::PointF offset(8.f, 9.f); + gfx::RectF recording_rect(offset, layer_rect.size()); + canvas = skia::SharePtr( + recorder.beginRecording(gfx::RectFToSkRect(recording_rect))); + canvas->translate(offset.x(), offset.y()); + canvas->drawRectCoords(0.f, 0.f, 60.f, 60.f, red_paint); + canvas->drawRectCoords(50.f, 50.f, 75.f, 75.f, blue_paint); + picture = skia::AdoptRef(recorder.endRecordingAsPicture()); + auto* item = list->CreateAndAppendItem<DrawingDisplayItem>(); + item->SetNew(picture); + list->ProcessAppendedItems(); + list->CreateAndCacheSkPicture(); + DrawDisplayList(pixels, layer_rect, list); + + SkBitmap expected_bitmap; + unsigned char expected_pixels[4 * 100 * 100] = {0}; + SkImageInfo info = + SkImageInfo::MakeN32Premul(layer_rect.width(), layer_rect.height()); + expected_bitmap.installPixels(info, expected_pixels, info.minRowBytes()); + SkCanvas expected_canvas(expected_bitmap); + expected_canvas.clipRect(gfx::RectToSkRect(layer_rect)); + expected_canvas.drawRectCoords(0.f + offset.x(), 0.f + offset.y(), + 60.f + offset.x(), 60.f + offset.y(), + red_paint); + expected_canvas.drawRectCoords(50.f + offset.x(), 50.f + offset.y(), + 75.f + offset.x(), 75.f + offset.y(), + blue_paint); + + EXPECT_EQ(0, memcmp(pixels, expected_pixels, 4 * 100 * 100)); +} + +TEST(DisplayItemListTest, ClipItem) { + gfx::Rect layer_rect(100, 100); + SkPictureRecorder recorder; + skia::RefPtr<SkCanvas> canvas; + skia::RefPtr<SkPicture> picture; + SkPaint blue_paint; + blue_paint.setColor(SK_ColorBLUE); + SkPaint red_paint; + red_paint.setColor(SK_ColorRED); + unsigned char pixels[4 * 100 * 100] = {0}; + const bool use_cached_picture = true; + scoped_refptr<DisplayItemList> list = + DisplayItemList::Create(layer_rect, use_cached_picture); + + gfx::PointF first_offset(8.f, 9.f); + gfx::RectF first_recording_rect(first_offset, layer_rect.size()); + canvas = skia::SharePtr( + recorder.beginRecording(gfx::RectFToSkRect(first_recording_rect))); + canvas->translate(first_offset.x(), first_offset.y()); + canvas->drawRectCoords(0.f, 0.f, 60.f, 60.f, red_paint); + picture = skia::AdoptRef(recorder.endRecordingAsPicture()); + auto* item1 = list->CreateAndAppendItem<DrawingDisplayItem>(); + item1->SetNew(picture.Pass()); + + gfx::Rect clip_rect(60, 60, 10, 10); + auto* item2 = list->CreateAndAppendItem<ClipDisplayItem>(); + item2->SetNew(clip_rect, std::vector<SkRRect>()); + + gfx::PointF second_offset(2.f, 3.f); + gfx::RectF second_recording_rect(second_offset, layer_rect.size()); + canvas = skia::SharePtr( + recorder.beginRecording(gfx::RectFToSkRect(second_recording_rect))); + canvas->translate(second_offset.x(), second_offset.y()); + canvas->drawRectCoords(50.f, 50.f, 75.f, 75.f, blue_paint); + picture = skia::AdoptRef(recorder.endRecordingAsPicture()); + auto* item3 = list->CreateAndAppendItem<DrawingDisplayItem>(); + item3->SetNew(picture.Pass()); + + list->CreateAndAppendItem<EndClipDisplayItem>(); + list->ProcessAppendedItems(); + list->CreateAndCacheSkPicture(); + + DrawDisplayList(pixels, layer_rect, list); + + SkBitmap expected_bitmap; + unsigned char expected_pixels[4 * 100 * 100] = {0}; + SkImageInfo info = + SkImageInfo::MakeN32Premul(layer_rect.width(), layer_rect.height()); + expected_bitmap.installPixels(info, expected_pixels, info.minRowBytes()); + SkCanvas expected_canvas(expected_bitmap); + expected_canvas.clipRect(gfx::RectToSkRect(layer_rect)); + expected_canvas.drawRectCoords(0.f + first_offset.x(), 0.f + first_offset.y(), + 60.f + first_offset.x(), + 60.f + first_offset.y(), red_paint); + expected_canvas.clipRect(gfx::RectToSkRect(clip_rect)); + expected_canvas.drawRectCoords( + 50.f + second_offset.x(), 50.f + second_offset.y(), + 75.f + second_offset.x(), 75.f + second_offset.y(), blue_paint); + + EXPECT_EQ(0, memcmp(pixels, expected_pixels, 4 * 100 * 100)); +} + +TEST(DisplayItemListTest, TransformItem) { + gfx::Rect layer_rect(100, 100); + SkPictureRecorder recorder; + skia::RefPtr<SkCanvas> canvas; + skia::RefPtr<SkPicture> picture; + SkPaint blue_paint; + blue_paint.setColor(SK_ColorBLUE); + SkPaint red_paint; + red_paint.setColor(SK_ColorRED); + unsigned char pixels[4 * 100 * 100] = {0}; + const bool use_cached_picture = true; + scoped_refptr<DisplayItemList> list = + DisplayItemList::Create(layer_rect, use_cached_picture); + + gfx::PointF first_offset(8.f, 9.f); + gfx::RectF first_recording_rect(first_offset, layer_rect.size()); + canvas = skia::SharePtr( + recorder.beginRecording(gfx::RectFToSkRect(first_recording_rect))); + canvas->translate(first_offset.x(), first_offset.y()); + canvas->drawRectCoords(0.f, 0.f, 60.f, 60.f, red_paint); + picture = skia::AdoptRef(recorder.endRecordingAsPicture()); + auto* item1 = list->CreateAndAppendItem<DrawingDisplayItem>(); + item1->SetNew(picture); + + gfx::Transform transform; + transform.Rotate(45.0); + auto* item2 = list->CreateAndAppendItem<TransformDisplayItem>(); + item2->SetNew(transform); + + gfx::PointF second_offset(2.f, 3.f); + gfx::RectF second_recording_rect(second_offset, layer_rect.size()); + canvas = skia::SharePtr( + recorder.beginRecording(gfx::RectFToSkRect(second_recording_rect))); + canvas->translate(second_offset.x(), second_offset.y()); + canvas->drawRectCoords(50.f, 50.f, 75.f, 75.f, blue_paint); + picture = skia::AdoptRef(recorder.endRecordingAsPicture()); + auto* item3 = list->CreateAndAppendItem<DrawingDisplayItem>(); + item3->SetNew(picture); + + list->CreateAndAppendItem<EndTransformDisplayItem>(); + list->ProcessAppendedItems(); + list->CreateAndCacheSkPicture(); + + DrawDisplayList(pixels, layer_rect, list); + + SkBitmap expected_bitmap; + unsigned char expected_pixels[4 * 100 * 100] = {0}; + SkImageInfo info = + SkImageInfo::MakeN32Premul(layer_rect.width(), layer_rect.height()); + expected_bitmap.installPixels(info, expected_pixels, info.minRowBytes()); + SkCanvas expected_canvas(expected_bitmap); + expected_canvas.clipRect(gfx::RectToSkRect(layer_rect)); + expected_canvas.drawRectCoords(0.f + first_offset.x(), 0.f + first_offset.y(), + 60.f + first_offset.x(), + 60.f + first_offset.y(), red_paint); + expected_canvas.setMatrix(transform.matrix()); + expected_canvas.drawRectCoords( + 50.f + second_offset.x(), 50.f + second_offset.y(), + 75.f + second_offset.x(), 75.f + second_offset.y(), blue_paint); + + EXPECT_EQ(0, memcmp(pixels, expected_pixels, 4 * 100 * 100)); +} + +TEST(DisplayItemListTest, FilterItem) { + gfx::Rect layer_rect(100, 100); + FilterOperations filters; + unsigned char pixels[4 * 100 * 100] = {0}; + const bool use_cached_picture = true; + scoped_refptr<DisplayItemList> list = + DisplayItemList::Create(layer_rect, use_cached_picture); + + SkBitmap source_bitmap; + source_bitmap.allocN32Pixels(50, 50); + SkCanvas source_canvas(source_bitmap); + source_canvas.clear(SkColorSetRGB(128, 128, 128)); + + // For most SkImageFilters, the |dst| bounds computed by computeFastBounds are + // dependent on the provided |src| bounds. This means, for example, that + // translating |src| results in a corresponding translation of |dst|. But this + // is not the case for all SkImageFilters; for some of them (e.g. + // SkBitmapSource), the computation of |dst| in computeFastBounds doesn't + // involve |src| at all. Incorrectly assuming such a relationship (e.g. by + // translating |dst| after it is computed by computeFastBounds, rather than + // translating |src| before it provided to computedFastBounds) can cause + // incorrect clipping of filter output. To test for this, we include an + // SkBitmapSource filter in |filters|. Here, |src| is |filter_bounds|, defined + // below. + skia::RefPtr<SkImageFilter> image_filter = + skia::AdoptRef(SkBitmapSource::Create(source_bitmap)); + filters.Append(FilterOperation::CreateReferenceFilter(image_filter)); + filters.Append(FilterOperation::CreateBrightnessFilter(0.5f)); + gfx::RectF filter_bounds(10.f, 10.f, 50.f, 50.f); + auto* item = list->CreateAndAppendItem<FilterDisplayItem>(); + item->SetNew(filters, filter_bounds); + list->CreateAndAppendItem<EndFilterDisplayItem>(); + list->ProcessAppendedItems(); + list->CreateAndCacheSkPicture(); + + DrawDisplayList(pixels, layer_rect, list); + + SkBitmap expected_bitmap; + unsigned char expected_pixels[4 * 100 * 100] = {0}; + SkPaint paint; + paint.setColor(SkColorSetRGB(64, 64, 64)); + SkImageInfo info = + SkImageInfo::MakeN32Premul(layer_rect.width(), layer_rect.height()); + expected_bitmap.installPixels(info, expected_pixels, info.minRowBytes()); + SkCanvas expected_canvas(expected_bitmap); + expected_canvas.drawRect(RectFToSkRect(filter_bounds), paint); + + EXPECT_EQ(0, memcmp(pixels, expected_pixels, 4 * 100 * 100)); +} + +TEST(DisplayItemListTest, CompactingItems) { + gfx::Rect layer_rect(100, 100); + SkPictureRecorder recorder; + skia::RefPtr<SkCanvas> canvas; + skia::RefPtr<SkPicture> picture; + SkPaint blue_paint; + blue_paint.setColor(SK_ColorBLUE); + SkPaint red_paint; + red_paint.setColor(SK_ColorRED); + unsigned char pixels[4 * 100 * 100] = {0}; + + gfx::PointF offset(8.f, 9.f); + gfx::RectF recording_rect(offset, layer_rect.size()); + + bool use_cached_picture = false; + scoped_refptr<DisplayItemList> list_without_caching = + DisplayItemList::Create(layer_rect, use_cached_picture); + + canvas = skia::SharePtr( + recorder.beginRecording(gfx::RectFToSkRect(recording_rect))); + canvas->translate(offset.x(), offset.y()); + canvas->drawRectCoords(0.f, 0.f, 60.f, 60.f, red_paint); + canvas->drawRectCoords(50.f, 50.f, 75.f, 75.f, blue_paint); + picture = skia::AdoptRef(recorder.endRecordingAsPicture()); + auto* item1 = list_without_caching->CreateAndAppendItem<DrawingDisplayItem>(); + item1->SetNew(picture); + list_without_caching->ProcessAppendedItems(); + DrawDisplayList(pixels, layer_rect, list_without_caching); + + unsigned char expected_pixels[4 * 100 * 100] = {0}; + use_cached_picture = true; + scoped_refptr<DisplayItemList> list_with_caching = + DisplayItemList::Create(layer_rect, use_cached_picture); + auto* item2 = list_with_caching->CreateAndAppendItem<DrawingDisplayItem>(); + item2->SetNew(picture); + list_with_caching->ProcessAppendedItems(); + list_with_caching->CreateAndCacheSkPicture(); + DrawDisplayList(expected_pixels, layer_rect, list_with_caching); + + EXPECT_EQ(0, memcmp(pixels, expected_pixels, 4 * 100 * 100)); +} + +TEST(DisplayItemListTest, PictureMemoryUsage) { + scoped_refptr<DisplayItemList> list; + size_t memory_usage; + + // Make an SkPicture whose size is known. + gfx::Rect layer_rect(100, 100); + SkPictureRecorder recorder; + SkPaint blue_paint; + blue_paint.setColor(SK_ColorBLUE); + SkCanvas* canvas = recorder.beginRecording(gfx::RectFToSkRect(layer_rect)); + for (int i = 0; i < 100; i++) + canvas->drawPaint(blue_paint); + skia::RefPtr<SkPicture> picture = + skia::AdoptRef(recorder.endRecordingAsPicture()); + size_t picture_size = SkPictureUtils::ApproximateBytesUsed(picture.get()); + ASSERT_GE(picture_size, 100 * sizeof(SkPaint)); + ASSERT_LE(picture_size, 200 * sizeof(SkPaint)); + + // Using a cached picture, we should get about the right size. + list = DisplayItemList::Create(layer_rect, true); + auto* item = list->CreateAndAppendItem<DrawingDisplayItem>(); + item->SetNew(picture); + list->ProcessAppendedItems(); + list->CreateAndCacheSkPicture(); + memory_usage = list->PictureMemoryUsage(); + EXPECT_GE(memory_usage, picture_size); + EXPECT_LE(memory_usage, 2 * picture_size); + + // Using no cached picture, we should still get the right size. + list = DisplayItemList::Create(layer_rect, false); + item = list->CreateAndAppendItem<DrawingDisplayItem>(); + item->SetNew(picture); + list->ProcessAppendedItems(); + memory_usage = list->PictureMemoryUsage(); + EXPECT_GE(memory_usage, picture_size); + EXPECT_LE(memory_usage, 2 * picture_size); + + // To avoid double counting, we expect zero size to be computed if both the + // picture and items are retained (currently this only happens due to certain + // categories being traced). + list = new DisplayItemList(layer_rect, true, true); + item = list->CreateAndAppendItem<DrawingDisplayItem>(); + item->SetNew(picture); + list->ProcessAppendedItems(); + list->CreateAndCacheSkPicture(); + memory_usage = list->PictureMemoryUsage(); + EXPECT_EQ(static_cast<size_t>(0), memory_usage); +} + +} // namespace cc diff --git a/cc/playback/display_list_raster_source.cc b/cc/playback/display_list_raster_source.cc new file mode 100644 index 0000000..0dcd025 --- /dev/null +++ b/cc/playback/display_list_raster_source.cc @@ -0,0 +1,235 @@ +// 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/playback/display_list_raster_source.h" + +#include "base/trace_event/trace_event.h" +#include "cc/base/region.h" +#include "cc/debug/debug_colors.h" +#include "cc/playback/display_item_list.h" +#include "cc/playback/raster_source_helper.h" +#include "skia/ext/analysis_canvas.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" +#include "ui/gfx/geometry/rect_conversions.h" + +namespace { + +#ifdef NDEBUG +const bool kDefaultClearCanvasSetting = false; +#else +const bool kDefaultClearCanvasSetting = true; +#endif + +} // namespace + +namespace cc { + +scoped_refptr<DisplayListRasterSource> +DisplayListRasterSource::CreateFromDisplayListRecordingSource( + const DisplayListRecordingSource* other, + bool can_use_lcd_text) { + return make_scoped_refptr( + new DisplayListRasterSource(other, can_use_lcd_text)); +} + +DisplayListRasterSource::DisplayListRasterSource() + : background_color_(SK_ColorTRANSPARENT), + requires_clear_(true), + can_use_lcd_text_(true), + is_solid_color_(false), + solid_color_(SK_ColorTRANSPARENT), + clear_canvas_with_debug_color_(kDefaultClearCanvasSetting), + slow_down_raster_scale_factor_for_debug_(0), + should_attempt_to_use_distance_field_text_(false) { +} + +DisplayListRasterSource::DisplayListRasterSource( + const DisplayListRecordingSource* other, + bool can_use_lcd_text) + : display_list_(other->display_list_), + background_color_(other->background_color_), + requires_clear_(other->requires_clear_), + can_use_lcd_text_(can_use_lcd_text), + is_solid_color_(other->is_solid_color_), + solid_color_(other->solid_color_), + recorded_viewport_(other->recorded_viewport_), + size_(other->size_), + clear_canvas_with_debug_color_(kDefaultClearCanvasSetting), + slow_down_raster_scale_factor_for_debug_( + other->slow_down_raster_scale_factor_for_debug_), + should_attempt_to_use_distance_field_text_(false) { +} + +DisplayListRasterSource::DisplayListRasterSource( + const DisplayListRasterSource* other, + bool can_use_lcd_text) + : display_list_(other->display_list_), + background_color_(other->background_color_), + requires_clear_(other->requires_clear_), + can_use_lcd_text_(can_use_lcd_text), + is_solid_color_(other->is_solid_color_), + solid_color_(other->solid_color_), + recorded_viewport_(other->recorded_viewport_), + size_(other->size_), + clear_canvas_with_debug_color_(kDefaultClearCanvasSetting), + slow_down_raster_scale_factor_for_debug_( + other->slow_down_raster_scale_factor_for_debug_), + should_attempt_to_use_distance_field_text_( + other->should_attempt_to_use_distance_field_text_) { +} + +DisplayListRasterSource::~DisplayListRasterSource() { +} + +void DisplayListRasterSource::PlaybackToSharedCanvas( + SkCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const { + RasterCommon(canvas, NULL, canvas_rect, contents_scale); +} + +void DisplayListRasterSource::RasterForAnalysis(skia::AnalysisCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const { + RasterCommon(canvas, canvas, canvas_rect, contents_scale); +} + +void DisplayListRasterSource::PlaybackToCanvas(SkCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const { + RasterSourceHelper::PrepareForPlaybackToCanvas( + canvas, canvas_rect, gfx::Rect(size_), contents_scale, background_color_, + clear_canvas_with_debug_color_, requires_clear_); + + RasterCommon(canvas, NULL, canvas_rect, contents_scale); +} + +void DisplayListRasterSource::RasterCommon(SkCanvas* canvas, + SkDrawPictureCallback* callback, + const gfx::Rect& canvas_rect, + float contents_scale) const { + canvas->translate(-canvas_rect.x(), -canvas_rect.y()); + gfx::Rect content_rect = + gfx::ToEnclosingRect(gfx::ScaleRect(gfx::Rect(size_), contents_scale)); + content_rect.Intersect(canvas_rect); + + canvas->clipRect(gfx::RectToSkRect(content_rect), SkRegion::kIntersect_Op); + + DCHECK(display_list_.get()); + display_list_->Raster(canvas, callback, contents_scale); +} + +skia::RefPtr<SkPicture> DisplayListRasterSource::GetFlattenedPicture() { + TRACE_EVENT0("cc", "DisplayListRasterSource::GetFlattenedPicture"); + + gfx::Rect display_list_rect(size_); + SkPictureRecorder recorder; + SkCanvas* canvas = recorder.beginRecording(display_list_rect.width(), + display_list_rect.height()); + if (!display_list_rect.IsEmpty()) + PlaybackToCanvas(canvas, display_list_rect, 1.0); + skia::RefPtr<SkPicture> picture = + skia::AdoptRef(recorder.endRecordingAsPicture()); + + return picture; +} + +size_t DisplayListRasterSource::GetPictureMemoryUsage() const { + if (!display_list_) + return 0; + return display_list_->PictureMemoryUsage(); +} + +void DisplayListRasterSource::PerformSolidColorAnalysis( + const gfx::Rect& content_rect, + float contents_scale, + RasterSource::SolidColorAnalysis* analysis) const { + DCHECK(analysis); + TRACE_EVENT0("cc", "DisplayListRasterSource::PerformSolidColorAnalysis"); + + gfx::Rect layer_rect = + gfx::ScaleToEnclosingRect(content_rect, 1.0f / contents_scale); + + layer_rect.Intersect(gfx::Rect(size_)); + skia::AnalysisCanvas canvas(layer_rect.width(), layer_rect.height()); + RasterForAnalysis(&canvas, layer_rect, 1.0f); + analysis->is_solid_color = canvas.GetColorIfSolid(&analysis->solid_color); +} + +void DisplayListRasterSource::GatherPixelRefs( + const gfx::Rect& content_rect, + float contents_scale, + std::vector<SkPixelRef*>* pixel_refs) const { + DCHECK_EQ(0u, pixel_refs->size()); + + gfx::Rect layer_rect = + gfx::ScaleToEnclosingRect(content_rect, 1.0f / contents_scale); + + PixelRefMap::Iterator iterator(layer_rect, display_list_.get()); + while (iterator) { + pixel_refs->push_back(*iterator); + ++iterator; + } +} + +bool DisplayListRasterSource::CoversRect(const gfx::Rect& content_rect, + float contents_scale) const { + if (size_.IsEmpty()) + return false; + gfx::Rect layer_rect = + gfx::ScaleToEnclosingRect(content_rect, 1.f / contents_scale); + layer_rect.Intersect(gfx::Rect(size_)); + + return recorded_viewport_.Contains(layer_rect); +} + +gfx::Size DisplayListRasterSource::GetSize() const { + return size_; +} + +bool DisplayListRasterSource::IsSolidColor() const { + return is_solid_color_; +} + +SkColor DisplayListRasterSource::GetSolidColor() const { + DCHECK(IsSolidColor()); + return solid_color_; +} + +bool DisplayListRasterSource::HasRecordings() const { + return !!display_list_.get(); +} + +void DisplayListRasterSource::SetShouldAttemptToUseDistanceFieldText() { + should_attempt_to_use_distance_field_text_ = true; +} + +bool DisplayListRasterSource::ShouldAttemptToUseDistanceFieldText() const { + return should_attempt_to_use_distance_field_text_; +} + +void DisplayListRasterSource::AsValueInto( + base::trace_event::TracedValue* array) const { + if (display_list_.get()) + TracedValue::AppendIDRef(display_list_.get(), array); +} + +void DisplayListRasterSource::DidBeginTracing() { + if (display_list_.get()) + display_list_->EmitTraceSnapshot(); +} + +bool DisplayListRasterSource::CanUseLCDText() const { + return can_use_lcd_text_; +} + +scoped_refptr<RasterSource> DisplayListRasterSource::CreateCloneWithoutLCDText() + const { + bool can_use_lcd_text = false; + return scoped_refptr<RasterSource>( + new DisplayListRasterSource(this, can_use_lcd_text)); +} + +} // namespace cc diff --git a/cc/playback/display_list_raster_source.h b/cc/playback/display_list_raster_source.h new file mode 100644 index 0000000..e9010e5 --- /dev/null +++ b/cc/playback/display_list_raster_source.h @@ -0,0 +1,98 @@ +// 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. + +#ifndef CC_PLAYBACK_DISPLAY_LIST_RASTER_SOURCE_H_ +#define CC_PLAYBACK_DISPLAY_LIST_RASTER_SOURCE_H_ + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/debug/rendering_stats_instrumentation.h" +#include "cc/playback/display_list_recording_source.h" +#include "cc/playback/raster_source.h" +#include "skia/ext/analysis_canvas.h" +#include "skia/ext/refptr.h" +#include "third_party/skia/include/core/SkPicture.h" + +namespace cc { +class DisplayItemList; + +class CC_EXPORT DisplayListRasterSource : public RasterSource { + public: + static scoped_refptr<DisplayListRasterSource> + CreateFromDisplayListRecordingSource(const DisplayListRecordingSource* other, + bool can_use_lcd_text); + + // RasterSource overrides. + void PlaybackToCanvas(SkCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const override; + void PlaybackToSharedCanvas(SkCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const override; + void PerformSolidColorAnalysis( + const gfx::Rect& content_rect, + float contents_scale, + RasterSource::SolidColorAnalysis* analysis) const override; + bool IsSolidColor() const override; + SkColor GetSolidColor() const override; + gfx::Size GetSize() const override; + void GatherPixelRefs(const gfx::Rect& content_rect, + float contents_scale, + std::vector<SkPixelRef*>* pixel_refs) const override; + bool CoversRect(const gfx::Rect& content_rect, + float contents_scale) const override; + bool HasRecordings() const override; + void SetShouldAttemptToUseDistanceFieldText() override; + bool ShouldAttemptToUseDistanceFieldText() const override; + void DidBeginTracing() override; + void AsValueInto(base::trace_event::TracedValue* array) const override; + skia::RefPtr<SkPicture> GetFlattenedPicture() override; + size_t GetPictureMemoryUsage() const override; + bool CanUseLCDText() const override; + scoped_refptr<RasterSource> CreateCloneWithoutLCDText() const override; + + protected: + DisplayListRasterSource(); + DisplayListRasterSource(const DisplayListRecordingSource* other, + bool can_use_lcd_text); + DisplayListRasterSource(const DisplayListRasterSource* other, + bool can_use_lcd_text); + ~DisplayListRasterSource() override; + + // These members are const as this raster source may be in use on another + // thread and so should not be touched after construction. + const scoped_refptr<DisplayItemList> display_list_; + const SkColor background_color_; + const bool requires_clear_; + const bool can_use_lcd_text_; + const bool is_solid_color_; + const SkColor solid_color_; + const gfx::Rect recorded_viewport_; + const gfx::Size size_; + const bool clear_canvas_with_debug_color_; + const int slow_down_raster_scale_factor_for_debug_; + // TODO(enne/vmiura): this has a read/write race between raster and compositor + // threads with multi-threaded Ganesh. Make this const or remove it. + bool should_attempt_to_use_distance_field_text_; + + private: + // Called when analyzing a tile. We can use AnalysisCanvas as + // SkDrawPictureCallback, which allows us to early out from analysis. + void RasterForAnalysis(skia::AnalysisCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const; + + void RasterCommon(SkCanvas* canvas, + SkDrawPictureCallback* callback, + const gfx::Rect& canvas_rect, + float contents_scale) const; + + DISALLOW_COPY_AND_ASSIGN(DisplayListRasterSource); +}; + +} // namespace cc + +#endif // CC_PLAYBACK_DISPLAY_LIST_RASTER_SOURCE_H_ diff --git a/cc/playback/display_list_recording_source.cc b/cc/playback/display_list_recording_source.cc new file mode 100644 index 0000000..9d6e31f --- /dev/null +++ b/cc/playback/display_list_recording_source.cc @@ -0,0 +1,192 @@ +// 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/playback/display_list_recording_source.h" + +#include <algorithm> + +#include "cc/base/region.h" +#include "cc/layers/content_layer_client.h" +#include "cc/playback/display_item_list.h" +#include "cc/playback/display_list_raster_source.h" +#include "skia/ext/analysis_canvas.h" + +namespace { + +// Layout pixel buffer around the visible layer rect to record. Any base +// picture that intersects the visible layer rect expanded by this distance +// will be recorded. +const int kPixelDistanceToRecord = 8000; +// We don't perform solid color analysis on images that have more than 10 skia +// operations. +const int kOpCountThatIsOkToAnalyze = 10; + +} // namespace + +namespace cc { + +DisplayListRecordingSource::DisplayListRecordingSource( + const gfx::Size& grid_cell_size, + bool use_cached_picture) + : use_cached_picture_(use_cached_picture), + slow_down_raster_scale_factor_for_debug_(0), + gather_pixel_refs_(false), + requires_clear_(false), + is_solid_color_(false), + solid_color_(SK_ColorTRANSPARENT), + background_color_(SK_ColorTRANSPARENT), + pixel_record_distance_(kPixelDistanceToRecord), + grid_cell_size_(grid_cell_size), + is_suitable_for_gpu_rasterization_(true) { +} + +DisplayListRecordingSource::~DisplayListRecordingSource() { +} + +bool DisplayListRecordingSource::UpdateAndExpandInvalidation( + ContentLayerClient* painter, + Region* invalidation, + const gfx::Size& layer_size, + const gfx::Rect& visible_layer_rect, + int frame_number, + RecordingMode recording_mode) { + bool updated = false; + + if (size_ != layer_size) { + size_ = layer_size; + updated = true; + } + + gfx::Rect old_recorded_viewport = recorded_viewport_; + recorded_viewport_ = visible_layer_rect; + recorded_viewport_.Inset(-pixel_record_distance_, -pixel_record_distance_); + recorded_viewport_.Intersect(gfx::Rect(GetSize())); + + if (recorded_viewport_ != old_recorded_viewport) { + // Invalidate newly-exposed and no-longer-exposed areas. + Region newly_exposed_region(recorded_viewport_); + newly_exposed_region.Subtract(old_recorded_viewport); + invalidation->Union(newly_exposed_region); + + Region no_longer_exposed_region(old_recorded_viewport); + no_longer_exposed_region.Subtract(recorded_viewport_); + invalidation->Union(no_longer_exposed_region); + + updated = true; + } + + if (!updated && !invalidation->Intersects(recorded_viewport_)) + return false; + + ContentLayerClient::PaintingControlSetting painting_control = + ContentLayerClient::PAINTING_BEHAVIOR_NORMAL; + + switch (recording_mode) { + case RECORD_NORMALLY: + // Already setup for normal recording. + break; + case RECORD_WITH_PAINTING_DISABLED: + painting_control = ContentLayerClient::DISPLAY_LIST_PAINTING_DISABLED; + break; + case RECORD_WITH_CACHING_DISABLED: + painting_control = ContentLayerClient::DISPLAY_LIST_CACHING_DISABLED; + break; + case RECORD_WITH_CONSTRUCTION_DISABLED: + painting_control = ContentLayerClient::DISPLAY_LIST_CONSTRUCTION_DISABLED; + break; + default: + // case RecordingSource::RECORD_WITH_SK_NULL_CANVAS should not be reached + NOTREACHED(); + } + + int repeat_count = 1; + if (slow_down_raster_scale_factor_for_debug_ > 1) { + repeat_count = slow_down_raster_scale_factor_for_debug_; + painting_control = ContentLayerClient::DISPLAY_LIST_CACHING_DISABLED; + } + for (int i = 0; i < repeat_count; ++i) { + display_list_ = + DisplayItemList::Create(recorded_viewport_, use_cached_picture_); + painter->PaintContentsToDisplayList(display_list_.get(), recorded_viewport_, + painting_control); + } + display_list_->ProcessAppendedItems(); + if (use_cached_picture_) + display_list_->CreateAndCacheSkPicture(); + + is_suitable_for_gpu_rasterization_ = + display_list_->IsSuitableForGpuRasterization(); + DetermineIfSolidColor(); + display_list_->EmitTraceSnapshot(); + if (gather_pixel_refs_) + display_list_->GatherPixelRefs(grid_cell_size_); + + return true; +} + +gfx::Size DisplayListRecordingSource::GetSize() const { + return size_; +} + +void DisplayListRecordingSource::SetEmptyBounds() { + size_ = gfx::Size(); + Clear(); +} + +void DisplayListRecordingSource::SetSlowdownRasterScaleFactor(int factor) { + slow_down_raster_scale_factor_for_debug_ = factor; +} + +void DisplayListRecordingSource::SetGatherPixelRefs(bool gather_pixel_refs) { + gather_pixel_refs_ = gather_pixel_refs; +} + +void DisplayListRecordingSource::SetBackgroundColor(SkColor background_color) { + background_color_ = background_color; +} + +void DisplayListRecordingSource::SetRequiresClear(bool requires_clear) { + requires_clear_ = requires_clear; +} + +void DisplayListRecordingSource::SetUnsuitableForGpuRasterizationForTesting() { + is_suitable_for_gpu_rasterization_ = false; +} + +bool DisplayListRecordingSource::IsSuitableForGpuRasterization() const { + return is_suitable_for_gpu_rasterization_; +} + +scoped_refptr<RasterSource> DisplayListRecordingSource::CreateRasterSource( + bool can_use_lcd_text) const { + return scoped_refptr<RasterSource>( + DisplayListRasterSource::CreateFromDisplayListRecordingSource( + this, can_use_lcd_text)); +} + +gfx::Size DisplayListRecordingSource::GetTileGridSizeForTesting() const { + return gfx::Size(); +} + +void DisplayListRecordingSource::DetermineIfSolidColor() { + DCHECK(display_list_.get()); + is_solid_color_ = false; + solid_color_ = SK_ColorTRANSPARENT; + + if (display_list_->ApproximateOpCount() > kOpCountThatIsOkToAnalyze) + return; + + gfx::Size layer_size = GetSize(); + skia::AnalysisCanvas canvas(layer_size.width(), layer_size.height()); + display_list_->Raster(&canvas, nullptr, 1.f); + is_solid_color_ = canvas.GetColorIfSolid(&solid_color_); +} + +void DisplayListRecordingSource::Clear() { + recorded_viewport_ = gfx::Rect(); + display_list_ = NULL; + is_solid_color_ = false; +} + +} // namespace cc diff --git a/cc/playback/display_list_recording_source.h b/cc/playback/display_list_recording_source.h new file mode 100644 index 0000000..8d652fa --- /dev/null +++ b/cc/playback/display_list_recording_source.h @@ -0,0 +1,70 @@ +// 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. + +#ifndef CC_PLAYBACK_DISPLAY_LIST_RECORDING_SOURCE_H_ +#define CC_PLAYBACK_DISPLAY_LIST_RECORDING_SOURCE_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/playback/recording_source.h" + +namespace cc { +class DisplayItemList; +class DisplayListRasterSource; + +class CC_EXPORT DisplayListRecordingSource : public RecordingSource { + public: + DisplayListRecordingSource(const gfx::Size& grid_cell_size, + bool use_cached_picture); + ~DisplayListRecordingSource() override; + + // RecordingSource overrides. + bool UpdateAndExpandInvalidation(ContentLayerClient* painter, + Region* invalidation, + const gfx::Size& layer_size, + const gfx::Rect& visible_layer_rect, + int frame_number, + RecordingMode recording_mode) override; + scoped_refptr<RasterSource> CreateRasterSource( + bool can_use_lcd_text) const override; + gfx::Size GetSize() const final; + void SetEmptyBounds() override; + void SetSlowdownRasterScaleFactor(int factor) override; + void SetGatherPixelRefs(bool gather_pixel_refs) override; + void SetBackgroundColor(SkColor background_color) override; + void SetRequiresClear(bool requires_clear) override; + bool IsSuitableForGpuRasterization() const override; + void SetUnsuitableForGpuRasterizationForTesting() override; + gfx::Size GetTileGridSizeForTesting() const override; + + protected: + void Clear(); + + const bool use_cached_picture_; + + gfx::Rect recorded_viewport_; + gfx::Size size_; + int slow_down_raster_scale_factor_for_debug_; + bool gather_pixel_refs_; + bool requires_clear_; + bool is_solid_color_; + SkColor solid_color_; + SkColor background_color_; + int pixel_record_distance_; + gfx::Size grid_cell_size_; + + scoped_refptr<DisplayItemList> display_list_; + + private: + friend class DisplayListRasterSource; + + void DetermineIfSolidColor(); + + bool is_suitable_for_gpu_rasterization_; + + DISALLOW_COPY_AND_ASSIGN(DisplayListRecordingSource); +}; + +} // namespace cc + +#endif // CC_PLAYBACK_DISPLAY_LIST_RECORDING_SOURCE_H_ diff --git a/cc/playback/display_list_recording_source_unittest.cc b/cc/playback/display_list_recording_source_unittest.cc new file mode 100644 index 0000000..0d7ec7c --- /dev/null +++ b/cc/playback/display_list_recording_source_unittest.cc @@ -0,0 +1,165 @@ +// Copyright 2015 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 <vector> + +#include "cc/playback/display_list_raster_source.h" +#include "cc/test/fake_display_list_recording_source.h" +#include "cc/test/skia_common.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cc { +namespace { + +TEST(DisplayListRecordingSourceTest, DiscardablePixelRefsWithTransform) { + gfx::Size grid_cell_size(128, 128); + gfx::Rect recorded_viewport(0, 0, 256, 256); + + scoped_ptr<FakeDisplayListRecordingSource> recording_source = + FakeDisplayListRecordingSource::CreateRecordingSource(recorded_viewport); + recording_source->SetGridCellSize(grid_cell_size); + SkBitmap discardable_bitmap[2][2]; + gfx::Transform identity_transform; + CreateBitmap(gfx::Size(32, 32), "discardable", &discardable_bitmap[0][0]); + // Translate transform is equivalent to moving using point. + gfx::Transform translate_transform; + translate_transform.Translate(0, 130); + CreateBitmap(gfx::Size(32, 32), "discardable", &discardable_bitmap[1][0]); + // This moves the bitmap to center of viewport and rotate, this would make + // this bitmap in all four tile grids. + gfx::Transform rotate_transform; + rotate_transform.Translate(112, 112); + rotate_transform.Rotate(45); + CreateBitmap(gfx::Size(32, 32), "discardable", &discardable_bitmap[1][1]); + + recording_source->add_draw_bitmap_with_transform(discardable_bitmap[0][0], + identity_transform); + recording_source->add_draw_bitmap_with_transform(discardable_bitmap[1][0], + translate_transform); + recording_source->add_draw_bitmap_with_transform(discardable_bitmap[1][1], + rotate_transform); + recording_source->SetGatherPixelRefs(true); + recording_source->Rerecord(); + + bool can_use_lcd_text = true; + scoped_refptr<DisplayListRasterSource> raster_source = + DisplayListRasterSource::CreateFromDisplayListRecordingSource( + recording_source.get(), can_use_lcd_text); + + // Tile sized iterators. These should find only one pixel ref. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 128, 128), 1.0, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(2u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 256, 256), 2.0, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(2u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 64, 64), 0.5, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(2u, pixel_refs.size()); + } + + // Shifted tile sized iterators. These should find only one pixel ref. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(140, 140, 128, 128), 1.0, + &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(280, 280, 256, 256), 2.0, + &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(70, 70, 64, 64), 0.5, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + + // The rotated bitmap would still be in the top right tile. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(140, 0, 128, 128), 1.0, + &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + + // Layer sized iterators. These should find all 6 pixel refs, including 1 + // pixel ref bitmap[0][0], 1 pixel ref for bitmap[1][0], and 4 pixel refs for + // bitmap[1][1]. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 256, 256), 1.0, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + // Top left tile with bitmap[0][0] and bitmap[1][1]. + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[1][1].pixelRef()); + // Top right tile with bitmap[1][1]. + EXPECT_TRUE(pixel_refs[2] == discardable_bitmap[1][1].pixelRef()); + // Bottom left tile with bitmap[1][0] and bitmap[1][1]. + EXPECT_TRUE(pixel_refs[3] == discardable_bitmap[1][0].pixelRef()); + EXPECT_TRUE(pixel_refs[4] == discardable_bitmap[1][1].pixelRef()); + // Bottom right tile with bitmap[1][1]. + EXPECT_TRUE(pixel_refs[5] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(6u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 512, 512), 2.0, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + // Top left tile with bitmap[0][0] and bitmap[1][1]. + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[1][1].pixelRef()); + // Top right tile with bitmap[1][1]. + EXPECT_TRUE(pixel_refs[2] == discardable_bitmap[1][1].pixelRef()); + // Bottom left tile with bitmap[1][0] and bitmap[1][1]. + EXPECT_TRUE(pixel_refs[3] == discardable_bitmap[1][0].pixelRef()); + EXPECT_TRUE(pixel_refs[4] == discardable_bitmap[1][1].pixelRef()); + // Bottom right tile with bitmap[1][1]. + EXPECT_TRUE(pixel_refs[5] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(6u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 128, 128), 0.5, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + // Top left tile with bitmap[0][0] and bitmap[1][1]. + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[1][1].pixelRef()); + // Top right tile with bitmap[1][1]. + EXPECT_TRUE(pixel_refs[2] == discardable_bitmap[1][1].pixelRef()); + // Bottom left tile with bitmap[1][0] and bitmap[1][1]. + EXPECT_TRUE(pixel_refs[3] == discardable_bitmap[1][0].pixelRef()); + EXPECT_TRUE(pixel_refs[4] == discardable_bitmap[1][1].pixelRef()); + // Bottom right tile with bitmap[1][1]. + EXPECT_TRUE(pixel_refs[5] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(6u, pixel_refs.size()); + } +} + +} // namespace +} // namespace cc diff --git a/cc/playback/drawing_display_item.cc b/cc/playback/drawing_display_item.cc new file mode 100644 index 0000000..51cd42e --- /dev/null +++ b/cc/playback/drawing_display_item.cc @@ -0,0 +1,62 @@ +// 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/playback/drawing_display_item.h" + +#include <string> + +#include "base/strings/stringprintf.h" +#include "base/trace_event/trace_event_argument.h" +#include "cc/debug/picture_debug_util.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkDrawPictureCallback.h" +#include "third_party/skia/include/core/SkMatrix.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "third_party/skia/include/utils/SkPictureUtils.h" + +namespace cc { + +DrawingDisplayItem::DrawingDisplayItem() { +} + +DrawingDisplayItem::~DrawingDisplayItem() { +} + +void DrawingDisplayItem::SetNew(skia::RefPtr<SkPicture> picture) { + picture_ = picture.Pass(); + DisplayItem::SetNew(picture_->suitableForGpuRasterization(NULL), + picture_->approximateOpCount(), + SkPictureUtils::ApproximateBytesUsed(picture_.get())); +} + +void DrawingDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + // SkPicture always does a wrapping save/restore on the canvas, so it is not + // necessary here. + if (callback) + picture_->playback(canvas, callback); + else + canvas->drawPicture(picture_.get()); +} + +void DrawingDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->BeginDictionary(); + array->SetString("name", "DrawingDisplayItem"); + array->SetString( + "cullRect", + base::StringPrintf("[%f,%f,%f,%f]", picture_->cullRect().x(), + picture_->cullRect().y(), picture_->cullRect().width(), + picture_->cullRect().height())); + std::string b64_picture; + PictureDebugUtil::SerializeAsBase64(picture_.get(), &b64_picture); + array->SetString("skp64", b64_picture); + array->EndDictionary(); +} + +void DrawingDisplayItem::CloneTo(DrawingDisplayItem* item) const { + item->SetNew(picture_); +} + +} // namespace cc diff --git a/cc/playback/drawing_display_item.h b/cc/playback/drawing_display_item.h new file mode 100644 index 0000000..81ddbce --- /dev/null +++ b/cc/playback/drawing_display_item.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef CC_PLAYBACK_DRAWING_DISPLAY_ITEM_H_ +#define CC_PLAYBACK_DRAWING_DISPLAY_ITEM_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/playback/display_item.h" +#include "skia/ext/refptr.h" +#include "ui/gfx/geometry/point_f.h" + +class SkCanvas; +class SkDrawPictureCallback; +class SkPicture; + +namespace cc { + +class CC_EXPORT DrawingDisplayItem : public DisplayItem { + public: + DrawingDisplayItem(); + ~DrawingDisplayItem() override; + + void SetNew(skia::RefPtr<SkPicture> picture); + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; + + void CloneTo(DrawingDisplayItem* item) const; + + private: + skia::RefPtr<SkPicture> picture_; +}; + +} // namespace cc + +#endif // CC_PLAYBACK_DRAWING_DISPLAY_ITEM_H_ diff --git a/cc/playback/filter_display_item.cc b/cc/playback/filter_display_item.cc new file mode 100644 index 0000000..8e849cf --- /dev/null +++ b/cc/playback/filter_display_item.cc @@ -0,0 +1,81 @@ +// 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/playback/filter_display_item.h" + +#include "base/strings/stringprintf.h" +#include "base/trace_event/trace_event_argument.h" +#include "cc/output/render_surface_filters.h" +#include "skia/ext/refptr.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/SkXfermode.h" +#include "ui/gfx/skia_util.h" + +namespace cc { + +FilterDisplayItem::FilterDisplayItem() { +} + +FilterDisplayItem::~FilterDisplayItem() { +} + +void FilterDisplayItem::SetNew(const FilterOperations& filters, + const gfx::RectF& bounds) { + filters_ = filters; + bounds_ = bounds; + + size_t memory_usage = + sizeof(skia::RefPtr<SkImageFilter>) + sizeof(gfx::RectF); + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 1 /* op_count */, + memory_usage); +} + +void FilterDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + canvas->save(); + canvas->translate(bounds_.x(), bounds_.y()); + + skia::RefPtr<SkImageFilter> image_filter = + RenderSurfaceFilters::BuildImageFilter( + filters_, gfx::SizeF(bounds_.width(), bounds_.height())); + SkRect boundaries; + image_filter->computeFastBounds( + SkRect::MakeWH(bounds_.width(), bounds_.height()), &boundaries); + + SkPaint paint; + paint.setXfermodeMode(SkXfermode::kSrcOver_Mode); + paint.setImageFilter(image_filter.get()); + canvas->saveLayer(&boundaries, &paint); + + canvas->translate(-bounds_.x(), -bounds_.y()); +} + +void FilterDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->AppendString(base::StringPrintf("FilterDisplayItem bounds: [%s]", + bounds_.ToString().c_str())); +} + +EndFilterDisplayItem::EndFilterDisplayItem() { + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 0 /* op_count */, + 0 /* memory_usage */); +} + +EndFilterDisplayItem::~EndFilterDisplayItem() { +} + +void EndFilterDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + canvas->restore(); + canvas->restore(); +} + +void EndFilterDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->AppendString("EndFilterDisplayItem"); +} + +} // namespace cc diff --git a/cc/playback/filter_display_item.h b/cc/playback/filter_display_item.h new file mode 100644 index 0000000..a999b38 --- /dev/null +++ b/cc/playback/filter_display_item.h @@ -0,0 +1,49 @@ +// 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. + +#ifndef CC_PLAYBACK_FILTER_DISPLAY_ITEM_H_ +#define CC_PLAYBACK_FILTER_DISPLAY_ITEM_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/output/filter_operations.h" +#include "cc/playback/display_item.h" +#include "ui/gfx/geometry/rect_f.h" + +class SkCanvas; +class SkDrawPictureCallback; + +namespace cc { + +class CC_EXPORT FilterDisplayItem : public DisplayItem { + public: + FilterDisplayItem(); + ~FilterDisplayItem() override; + + void SetNew(const FilterOperations& filters, const gfx::RectF& bounds); + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; + + private: + FilterOperations filters_; + gfx::RectF bounds_; +}; + +class CC_EXPORT EndFilterDisplayItem : public DisplayItem { + public: + EndFilterDisplayItem(); + ~EndFilterDisplayItem() override; + + static scoped_ptr<EndFilterDisplayItem> Create() { + return make_scoped_ptr(new EndFilterDisplayItem()); + } + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; +}; + +} // namespace cc + +#endif // CC_PLAYBACK_FILTER_DISPLAY_ITEM_H_ diff --git a/cc/playback/float_clip_display_item.cc b/cc/playback/float_clip_display_item.cc new file mode 100644 index 0000000..0f368a6 --- /dev/null +++ b/cc/playback/float_clip_display_item.cc @@ -0,0 +1,58 @@ +// Copyright 2015 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/playback/float_clip_display_item.h" + +#include "base/strings/stringprintf.h" +#include "base/trace_event/trace_event_argument.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "ui/gfx/skia_util.h" + +namespace cc { + +FloatClipDisplayItem::FloatClipDisplayItem() { +} + +FloatClipDisplayItem::~FloatClipDisplayItem() { +} + +void FloatClipDisplayItem::SetNew(const gfx::RectF& clip_rect) { + clip_rect_ = clip_rect; + + size_t memory_usage = sizeof(gfx::RectF); + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 1 /* op_count */, + memory_usage); +} + +void FloatClipDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + canvas->save(); + canvas->clipRect(gfx::RectFToSkRect(clip_rect_)); +} + +void FloatClipDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->AppendString(base::StringPrintf("FloatClipDisplayItem rect: [%s]", + clip_rect_.ToString().c_str())); +} + +EndFloatClipDisplayItem::EndFloatClipDisplayItem() { + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 0 /* op_count */, + 0 /* memory_usage */); +} + +EndFloatClipDisplayItem::~EndFloatClipDisplayItem() { +} + +void EndFloatClipDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + canvas->restore(); +} + +void EndFloatClipDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->AppendString("EndFloatClipDisplayItem"); +} + +} // namespace cc diff --git a/cc/playback/float_clip_display_item.h b/cc/playback/float_clip_display_item.h new file mode 100644 index 0000000..2812189 --- /dev/null +++ b/cc/playback/float_clip_display_item.h @@ -0,0 +1,49 @@ +// Copyright 2015 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. + +#ifndef CC_PLAYBACK_FLOAT_CLIP_DISPLAY_ITEM_H_ +#define CC_PLAYBACK_FLOAT_CLIP_DISPLAY_ITEM_H_ + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/playback/display_item.h" +#include "ui/gfx/geometry/rect_f.h" + +class SkCanvas; +class SkDrawPictureCallback; + +namespace cc { + +class CC_EXPORT FloatClipDisplayItem : public DisplayItem { + public: + FloatClipDisplayItem(); + ~FloatClipDisplayItem() override; + + void SetNew(const gfx::RectF& clip_rect); + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; + + private: + gfx::RectF clip_rect_; +}; + +class CC_EXPORT EndFloatClipDisplayItem : public DisplayItem { + public: + EndFloatClipDisplayItem(); + ~EndFloatClipDisplayItem() override; + + static scoped_ptr<EndFloatClipDisplayItem> Create() { + return make_scoped_ptr(new EndFloatClipDisplayItem()); + } + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; +}; + +} // namespace cc + +#endif // CC_PLAYBACK_FLOAT_CLIP_DISPLAY_ITEM_H_ diff --git a/cc/playback/largest_display_item.cc b/cc/playback/largest_display_item.cc new file mode 100644 index 0000000..0ecd3d0 --- /dev/null +++ b/cc/playback/largest_display_item.cc @@ -0,0 +1,71 @@ +// Copyright 2015 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/playback/largest_display_item.h" + +#include <algorithm> + +#include "cc/playback/clip_display_item.h" +#include "cc/playback/clip_path_display_item.h" +#include "cc/playback/compositing_display_item.h" +#include "cc/playback/drawing_display_item.h" +#include "cc/playback/filter_display_item.h" +#include "cc/playback/float_clip_display_item.h" +#include "cc/playback/transform_display_item.h" + +#include "third_party/skia/include/core/SkPicture.h" + +namespace { +const size_t kLargestDisplayItemSize = sizeof(cc::TransformDisplayItem); +} // namespace + +namespace cc { + +size_t LargestDisplayItemSize() { + // Use compile assert to make sure largest is actually larger than all other + // type of display_items. + static_assert(sizeof(ClipDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. ClipDisplayItem" + " is currently largest."); + static_assert(sizeof(EndClipDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. EndClipDisplayItem" + " is currently largest."); + static_assert(sizeof(ClipPathDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. ClipPathDisplayItem" + " is currently largest."); + static_assert(sizeof(EndClipPathDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. EndClipPathDisplayItem" + " is currently largest."); + static_assert(sizeof(CompositingDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. CompositingDisplayItem" + " is currently largest."); + static_assert(sizeof(EndCompositingDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. EndCompositingDisplayItem" + " is currently largest."); + static_assert(sizeof(DrawingDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. DrawingDisplayItem" + " is currently largest."); + static_assert(sizeof(FilterDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. FilterDisplayItem" + " is currently largest."); + static_assert(sizeof(EndFilterDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. EndFilterDisplayItem" + " is currently largest."); + static_assert(sizeof(FloatClipDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. FloatClipDisplayItem" + " is currently largest."); + static_assert(sizeof(EndFloatClipDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. EndFloatClipDisplayItem" + " is currently largest."); + static_assert(sizeof(TransformDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. TransformDisplayItem" + " is currently largest."); + static_assert(sizeof(EndTransformDisplayItem) <= kLargestDisplayItemSize, + "Largest Draw Quad size needs update. EndTransformDisplayItem" + " is currently largest."); + + return kLargestDisplayItemSize; +} + +} // namespace cc diff --git a/cc/playback/largest_display_item.h b/cc/playback/largest_display_item.h new file mode 100644 index 0000000..cce5671 --- /dev/null +++ b/cc/playback/largest_display_item.h @@ -0,0 +1,17 @@ +// Copyright 2015 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. + +#ifndef CC_PLAYBACK_LARGEST_DISPLAY_ITEM_H_ +#define CC_PLAYBACK_LARGEST_DISPLAY_ITEM_H_ + +#include "base/basictypes.h" +#include "cc/base/cc_export.h" + +namespace cc { + +CC_EXPORT size_t LargestDisplayItemSize(); + +} // namespace cc + +#endif // CC_PLAYBACK_LARGEST_DISPLAY_ITEM_H_ diff --git a/cc/playback/picture.cc b/cc/playback/picture.cc new file mode 100644 index 0000000..4537bf7 --- /dev/null +++ b/cc/playback/picture.cc @@ -0,0 +1,340 @@ +// Copyright 2012 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/playback/picture.h" + +#include <set> +#include <string> + +#include "base/base64.h" +#include "base/trace_event/trace_event.h" +#include "base/trace_event/trace_event_argument.h" +#include "base/values.h" +#include "cc/base/math_util.h" +#include "cc/base/util.h" +#include "cc/debug/picture_debug_util.h" +#include "cc/debug/traced_picture.h" +#include "cc/debug/traced_value.h" +#include "cc/layers/content_layer_client.h" +#include "skia/ext/pixel_ref_utils.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkDrawPictureCallback.h" +#include "third_party/skia/include/core/SkPaint.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" +#include "third_party/skia/include/core/SkStream.h" +#include "third_party/skia/include/utils/SkNullCanvas.h" +#include "third_party/skia/include/utils/SkPictureUtils.h" +#include "ui/gfx/codec/jpeg_codec.h" +#include "ui/gfx/codec/png_codec.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/skia_util.h" + +namespace cc { + +namespace { + +bool DecodeBitmap(const void* buffer, size_t size, SkBitmap* bm) { + const unsigned char* data = static_cast<const unsigned char *>(buffer); + + // Try PNG first. + if (gfx::PNGCodec::Decode(data, size, bm)) + return true; + + // Try JPEG. + scoped_ptr<SkBitmap> decoded_jpeg(gfx::JPEGCodec::Decode(data, size)); + if (decoded_jpeg) { + *bm = *decoded_jpeg; + return true; + } + return false; +} + +} // namespace + +scoped_refptr<Picture> Picture::Create( + const gfx::Rect& layer_rect, + ContentLayerClient* client, + const gfx::Size& tile_grid_size, + bool gather_pixel_refs, + RecordingSource::RecordingMode recording_mode) { + scoped_refptr<Picture> picture = + make_scoped_refptr(new Picture(layer_rect, tile_grid_size)); + + picture->Record(client, recording_mode); + if (gather_pixel_refs) + picture->GatherPixelRefs(); + + return picture; +} + +Picture::Picture(const gfx::Rect& layer_rect, const gfx::Size& tile_grid_size) + : layer_rect_(layer_rect), pixel_refs_(tile_grid_size) { + // Instead of recording a trace event for object creation here, we wait for + // the picture to be recorded in Picture::Record. +} + +scoped_refptr<Picture> Picture::CreateFromSkpValue(const base::Value* value) { + // Decode the picture from base64. + std::string encoded; + if (!value->GetAsString(&encoded)) + return NULL; + + std::string decoded; + base::Base64Decode(encoded, &decoded); + SkMemoryStream stream(decoded.data(), decoded.size()); + + // Read the picture. This creates an empty picture on failure. + SkPicture* skpicture = SkPicture::CreateFromStream(&stream, &DecodeBitmap); + if (skpicture == NULL) + return NULL; + + gfx::Rect layer_rect(gfx::SkIRectToRect(skpicture->cullRect().roundOut())); + return make_scoped_refptr(new Picture(skpicture, layer_rect)); +} + +scoped_refptr<Picture> Picture::CreateFromValue(const base::Value* raw_value) { + const base::DictionaryValue* value = NULL; + if (!raw_value->GetAsDictionary(&value)) + return NULL; + + // Decode the picture from base64. + std::string encoded; + if (!value->GetString("skp64", &encoded)) + return NULL; + + std::string decoded; + base::Base64Decode(encoded, &decoded); + SkMemoryStream stream(decoded.data(), decoded.size()); + + const base::Value* layer_rect_value = NULL; + if (!value->Get("params.layer_rect", &layer_rect_value)) + return NULL; + + gfx::Rect layer_rect; + if (!MathUtil::FromValue(layer_rect_value, &layer_rect)) + return NULL; + + // Read the picture. This creates an empty picture on failure. + SkPicture* skpicture = SkPicture::CreateFromStream(&stream, &DecodeBitmap); + if (skpicture == NULL) + return NULL; + + return make_scoped_refptr(new Picture(skpicture, layer_rect)); +} + +Picture::Picture(SkPicture* picture, const gfx::Rect& layer_rect) + : layer_rect_(layer_rect), + picture_(skia::AdoptRef(picture)), + pixel_refs_(layer_rect.size()) { +} + +Picture::Picture(const skia::RefPtr<SkPicture>& picture, + const gfx::Rect& layer_rect, + const PixelRefMap& pixel_refs) + : layer_rect_(layer_rect), picture_(picture), pixel_refs_(pixel_refs) { +} + +Picture::~Picture() { + TRACE_EVENT_OBJECT_DELETED_WITH_ID( + TRACE_DISABLED_BY_DEFAULT("cc.debug.picture"), "cc::Picture", this); +} + +bool Picture::IsSuitableForGpuRasterization(const char** reason) const { + DCHECK(picture_); + + // TODO(hendrikw): SkPicture::suitableForGpuRasterization takes a GrContext. + // Currently the GrContext isn't used, and should probably be removed from + // skia. + return picture_->suitableForGpuRasterization(nullptr, reason); +} + +int Picture::ApproximateOpCount() const { + DCHECK(picture_); + return picture_->approximateOpCount(); +} + +size_t Picture::ApproximateMemoryUsage() const { + DCHECK(picture_); + return SkPictureUtils::ApproximateBytesUsed(picture_.get()); +} + +bool Picture::HasText() const { + DCHECK(picture_); + return picture_->hasText(); +} + +void Picture::Record(ContentLayerClient* painter, + RecordingSource::RecordingMode recording_mode) { + TRACE_EVENT2("cc", + "Picture::Record", + "data", + AsTraceableRecordData(), + "recording_mode", + recording_mode); + + DCHECK(!picture_); + + SkRTreeFactory factory; + SkPictureRecorder recorder; + + skia::RefPtr<SkCanvas> canvas; + canvas = skia::SharePtr(recorder.beginRecording( + layer_rect_.width(), layer_rect_.height(), &factory, + SkPictureRecorder::kComputeSaveLayerInfo_RecordFlag)); + + ContentLayerClient::PaintingControlSetting painting_control = + ContentLayerClient::PAINTING_BEHAVIOR_NORMAL; + + switch (recording_mode) { + case RecordingSource::RECORD_NORMALLY: + // Already setup for normal recording. + break; + case RecordingSource::RECORD_WITH_SK_NULL_CANVAS: + canvas = skia::AdoptRef(SkCreateNullCanvas()); + break; + case RecordingSource::RECORD_WITH_PAINTING_DISABLED: + // We pass a disable flag through the paint calls when perfromance + // testing (the only time this case should ever arise) when we want to + // prevent the Blink GraphicsContext object from consuming any compute + // time. + canvas = skia::AdoptRef(SkCreateNullCanvas()); + painting_control = ContentLayerClient::DISPLAY_LIST_PAINTING_DISABLED; + break; + case RecordingSource::RECORD_WITH_CACHING_DISABLED: + // This mode should give the same results as RECORD_NORMALLY. + painting_control = ContentLayerClient::DISPLAY_LIST_CACHING_DISABLED; + break; + default: + // case RecordingSource::RECORD_WITH_CONSTRUCTION_DISABLED should + // not be reached + NOTREACHED(); + } + + canvas->save(); + canvas->translate(SkFloatToScalar(-layer_rect_.x()), + SkFloatToScalar(-layer_rect_.y())); + + canvas->clipRect(gfx::RectToSkRect(layer_rect_)); + + painter->PaintContents(canvas.get(), layer_rect_, painting_control); + + canvas->restore(); + picture_ = skia::AdoptRef(recorder.endRecordingAsPicture()); + DCHECK(picture_); + + EmitTraceSnapshot(); +} + +void Picture::GatherPixelRefs() { + TRACE_EVENT2("cc", "Picture::GatherPixelRefs", + "width", layer_rect_.width(), + "height", layer_rect_.height()); + + DCHECK(picture_); + DCHECK(pixel_refs_.empty()); + if (!WillPlayBackBitmaps()) + return; + + pixel_refs_.GatherPixelRefsFromPicture(picture_.get()); +} + +int Picture::Raster(SkCanvas* canvas, + SkPicture::AbortCallback* callback, + const Region& negated_content_region, + float contents_scale) const { + TRACE_EVENT_BEGIN1( + "cc", + "Picture::Raster", + "data", + AsTraceableRasterData(contents_scale)); + + DCHECK(picture_); + + canvas->save(); + + for (Region::Iterator it(negated_content_region); it.has_rect(); it.next()) + canvas->clipRect(gfx::RectToSkRect(it.rect()), SkRegion::kDifference_Op); + + canvas->scale(contents_scale, contents_scale); + canvas->translate(layer_rect_.x(), layer_rect_.y()); + if (callback) { + // If we have a callback, we need to call |draw()|, |drawPicture()| doesn't + // take a callback. This is used by |AnalysisCanvas| to early out. + picture_->playback(canvas, callback); + } else { + // Prefer to call |drawPicture()| on the canvas since it could place the + // entire picture on the canvas instead of parsing the skia operations. + canvas->drawPicture(picture_.get()); + } + SkIRect bounds; + canvas->getClipDeviceBounds(&bounds); + canvas->restore(); + TRACE_EVENT_END1( + "cc", "Picture::Raster", + "num_pixels_rasterized", bounds.width() * bounds.height()); + return bounds.width() * bounds.height(); +} + +void Picture::Replay(SkCanvas* canvas, SkPicture::AbortCallback* callback) { + TRACE_EVENT_BEGIN0("cc", "Picture::Replay"); + DCHECK(picture_); + picture_->playback(canvas, callback); + SkIRect bounds; + canvas->getClipDeviceBounds(&bounds); + TRACE_EVENT_END1("cc", "Picture::Replay", + "num_pixels_replayed", bounds.width() * bounds.height()); +} + +scoped_ptr<base::Value> Picture::AsValue() const { + // Encode the picture as base64. + scoped_ptr<base::DictionaryValue> res(new base::DictionaryValue()); + res->Set("params.layer_rect", MathUtil::AsValue(layer_rect_).release()); + std::string b64_picture; + PictureDebugUtil::SerializeAsBase64(picture_.get(), &b64_picture); + res->SetString("skp64", b64_picture); + return res.Pass(); +} + +void Picture::EmitTraceSnapshot() const { + TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( + TRACE_DISABLED_BY_DEFAULT("cc.debug.picture") "," + TRACE_DISABLED_BY_DEFAULT("devtools.timeline.picture"), + "cc::Picture", + this, + TracedPicture::AsTraceablePicture(this)); +} + +void Picture::EmitTraceSnapshotAlias(Picture* original) const { + TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID( + TRACE_DISABLED_BY_DEFAULT("cc.debug.picture") "," + TRACE_DISABLED_BY_DEFAULT("devtools.timeline.picture"), + "cc::Picture", + this, + TracedPicture::AsTraceablePictureAlias(original)); +} + +PixelRefMap::Iterator Picture::GetPixelRefMapIterator( + const gfx::Rect& layer_rect) const { + return PixelRefMap::Iterator(layer_rect, this); +} + +scoped_refptr<base::trace_event::ConvertableToTraceFormat> + Picture::AsTraceableRasterData(float scale) const { + scoped_refptr<base::trace_event::TracedValue> raster_data = + new base::trace_event::TracedValue(); + TracedValue::SetIDRef(this, raster_data.get(), "picture_id"); + raster_data->SetDouble("scale", scale); + return raster_data; +} + +scoped_refptr<base::trace_event::ConvertableToTraceFormat> + Picture::AsTraceableRecordData() const { + scoped_refptr<base::trace_event::TracedValue> record_data = + new base::trace_event::TracedValue(); + TracedValue::SetIDRef(this, record_data.get(), "picture_id"); + MathUtil::AddToTracedValue("layer_rect", layer_rect_, record_data.get()); + return record_data; +} + +} // namespace cc diff --git a/cc/playback/picture.h b/cc/playback/picture.h new file mode 100644 index 0000000..b19504b --- /dev/null +++ b/cc/playback/picture.h @@ -0,0 +1,119 @@ +// Copyright 2012 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. + +#ifndef CC_PLAYBACK_PICTURE_H_ +#define CC_PLAYBACK_PICTURE_H_ + +#include "base/basictypes.h" +#include "base/containers/hash_tables.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/trace_event/trace_event.h" +#include "cc/base/cc_export.h" +#include "cc/base/region.h" +#include "cc/playback/pixel_ref_map.h" +#include "cc/playback/recording_source.h" +#include "skia/ext/refptr.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "ui/gfx/geometry/rect.h" + +class SkPixelRef; + +namespace base { +class Value; +} + +namespace skia { +class AnalysisCanvas; +} + +namespace cc { + +class ContentLayerClient; + +class CC_EXPORT Picture + : public base::RefCountedThreadSafe<Picture> { + public: + static scoped_refptr<Picture> Create( + const gfx::Rect& layer_rect, + ContentLayerClient* client, + const gfx::Size& tile_grid_size, + bool gather_pixels_refs, + RecordingSource::RecordingMode recording_mode); + static scoped_refptr<Picture> CreateFromValue(const base::Value* value); + static scoped_refptr<Picture> CreateFromSkpValue(const base::Value* value); + + gfx::Rect LayerRect() const { return layer_rect_; } + + // Has Record() been called yet? + bool HasRecording() const { return picture_.get() != NULL; } + + bool IsSuitableForGpuRasterization(const char** reason) const; + int ApproximateOpCount() const; + size_t ApproximateMemoryUsage() const; + + bool HasText() const; + + // Apply this scale and raster the negated region into the canvas. + // |negated_content_region| specifies the region to be clipped out of the + // raster operation, i.e., the parts of the canvas which will not get drawn + // to. + int Raster(SkCanvas* canvas, + SkPicture::AbortCallback* callback, + const Region& negated_content_region, + float contents_scale) const; + + // Draw the picture directly into the given canvas, without applying any + // clip/scale/layer transformations. + void Replay(SkCanvas* canvas, SkPicture::AbortCallback* callback = NULL); + + scoped_ptr<base::Value> AsValue() const; + + void EmitTraceSnapshot() const; + void EmitTraceSnapshotAlias(Picture* original) const; + + bool WillPlayBackBitmaps() const { return picture_->willPlayBackBitmaps(); } + + PixelRefMap::Iterator GetPixelRefMapIterator( + const gfx::Rect& layer_rect) const; + + private: + Picture(const gfx::Rect& layer_rect, const gfx::Size& tile_grid_size); + // This constructor assumes SkPicture is already ref'd and transfers + // ownership to this picture. + Picture(const skia::RefPtr<SkPicture>&, + const gfx::Rect& layer_rect, + const PixelRefMap& pixel_refs); + // This constructor will call AdoptRef on the SkPicture. + Picture(SkPicture*, const gfx::Rect& layer_rect); + ~Picture(); + + // Record a paint operation. To be able to safely use this SkPicture for + // playback on a different thread this can only be called once. + void Record(ContentLayerClient* client, + RecordingSource::RecordingMode recording_mode); + + // Gather pixel refs from recording. + void GatherPixelRefs(); + + gfx::Rect layer_rect_; + skia::RefPtr<SkPicture> picture_; + + PixelRefMap pixel_refs_; + + scoped_refptr<base::trace_event::ConvertableToTraceFormat> + AsTraceableRasterData(float scale) const; + scoped_refptr<base::trace_event::ConvertableToTraceFormat> + AsTraceableRecordData() const; + + friend class base::RefCountedThreadSafe<Picture>; + friend class PixelRefMap::Iterator; + DISALLOW_COPY_AND_ASSIGN(Picture); +}; + +} // namespace cc + +#endif // CC_PLAYBACK_PICTURE_H_ diff --git a/cc/playback/picture_pile.cc b/cc/playback/picture_pile.cc new file mode 100644 index 0000000..b7b2822 --- /dev/null +++ b/cc/playback/picture_pile.cc @@ -0,0 +1,684 @@ +// Copyright 2012 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/playback/picture_pile.h" + +#include <algorithm> +#include <limits> +#include <vector> + +#include "cc/base/region.h" +#include "cc/playback/picture_pile_impl.h" +#include "skia/ext/analysis_canvas.h" + +namespace { +// Layout pixel buffer around the visible layer rect to record. Any base +// picture that intersects the visible layer rect expanded by this distance +// will be recorded. +const int kPixelDistanceToRecord = 8000; +// We don't perform solid color analysis on images that have more than 10 skia +// operations. +const int kOpCountThatIsOkToAnalyze = 10; + +// Dimensions of the tiles in this picture pile as well as the dimensions of +// the base picture in each tile. +const int kBasePictureSize = 512; + +// TODO(humper): The density threshold here is somewhat arbitrary; need a +// way to set // this from the command line so we can write a benchmark +// script and find a sweet spot. +const float kDensityThreshold = 0.5f; + +bool rect_sort_y(const gfx::Rect& r1, const gfx::Rect& r2) { + return r1.y() < r2.y() || (r1.y() == r2.y() && r1.x() < r2.x()); +} + +bool rect_sort_x(const gfx::Rect& r1, const gfx::Rect& r2) { + return r1.x() < r2.x() || (r1.x() == r2.x() && r1.y() < r2.y()); +} + +float PerformClustering(const std::vector<gfx::Rect>& tiles, + std::vector<gfx::Rect>* clustered_rects) { + // These variables track the record area and invalid area + // for the entire clustering + int total_record_area = 0; + int total_invalid_area = 0; + + // These variables track the record area and invalid area + // for the current cluster being constructed. + gfx::Rect cur_record_rect; + int cluster_record_area = 0, cluster_invalid_area = 0; + + for (std::vector<gfx::Rect>::const_iterator it = tiles.begin(); + it != tiles.end(); + it++) { + gfx::Rect invalid_tile = *it; + + // For each tile, we consider adding the invalid tile to the + // current record rectangle. Only add it if the amount of empty + // space created is below a density threshold. + int tile_area = invalid_tile.width() * invalid_tile.height(); + + gfx::Rect proposed_union = cur_record_rect; + proposed_union.Union(invalid_tile); + int proposed_area = proposed_union.width() * proposed_union.height(); + float proposed_density = + static_cast<float>(cluster_invalid_area + tile_area) / + static_cast<float>(proposed_area); + + if (proposed_density >= kDensityThreshold) { + // It's okay to add this invalid tile to the + // current recording rectangle. + cur_record_rect = proposed_union; + cluster_record_area = proposed_area; + cluster_invalid_area += tile_area; + total_invalid_area += tile_area; + } else { + // Adding this invalid tile to the current recording rectangle + // would exceed our badness threshold, so put the current rectangle + // in the list of recording rects, and start a new one. + clustered_rects->push_back(cur_record_rect); + total_record_area += cluster_record_area; + cur_record_rect = invalid_tile; + cluster_invalid_area = tile_area; + cluster_record_area = tile_area; + } + } + + DCHECK(!cur_record_rect.IsEmpty()); + clustered_rects->push_back(cur_record_rect); + total_record_area += cluster_record_area;; + + DCHECK_NE(total_record_area, 0); + + return static_cast<float>(total_invalid_area) / + static_cast<float>(total_record_area); +} + +void ClusterTiles(const std::vector<gfx::Rect>& invalid_tiles, + std::vector<gfx::Rect>* record_rects) { + TRACE_EVENT1("cc", "ClusterTiles", + "count", + invalid_tiles.size()); + if (invalid_tiles.size() <= 1) { + // Quickly handle the special case for common + // single-invalidation update, and also the less common + // case of no tiles passed in. + *record_rects = invalid_tiles; + return; + } + + // Sort the invalid tiles by y coordinate. + std::vector<gfx::Rect> invalid_tiles_vertical = invalid_tiles; + std::sort(invalid_tiles_vertical.begin(), + invalid_tiles_vertical.end(), + rect_sort_y); + + std::vector<gfx::Rect> vertical_clustering; + float vertical_density = + PerformClustering(invalid_tiles_vertical, &vertical_clustering); + + // If vertical density is optimal, then we can return early. + if (vertical_density == 1.f) { + *record_rects = vertical_clustering; + return; + } + + // Now try again with a horizontal sort, see which one is best + std::vector<gfx::Rect> invalid_tiles_horizontal = invalid_tiles; + std::sort(invalid_tiles_horizontal.begin(), + invalid_tiles_horizontal.end(), + rect_sort_x); + + std::vector<gfx::Rect> horizontal_clustering; + float horizontal_density = + PerformClustering(invalid_tiles_horizontal, &horizontal_clustering); + + if (vertical_density < horizontal_density) { + *record_rects = horizontal_clustering; + return; + } + + *record_rects = vertical_clustering; +} + +#ifdef NDEBUG +const bool kDefaultClearCanvasSetting = false; +#else +const bool kDefaultClearCanvasSetting = true; +#endif + +} // namespace + +namespace cc { + +PicturePile::PicturePile(float min_contents_scale, + const gfx::Size& tile_grid_size) + : min_contents_scale_(0), + slow_down_raster_scale_factor_for_debug_(0), + gather_pixel_refs_(false), + has_any_recordings_(false), + clear_canvas_with_debug_color_(kDefaultClearCanvasSetting), + requires_clear_(true), + is_solid_color_(false), + solid_color_(SK_ColorTRANSPARENT), + background_color_(SK_ColorTRANSPARENT), + pixel_record_distance_(kPixelDistanceToRecord), + is_suitable_for_gpu_rasterization_(true) { + tiling_.SetMaxTextureSize(gfx::Size(kBasePictureSize, kBasePictureSize)); + SetMinContentsScale(min_contents_scale); + SetTileGridSize(tile_grid_size); +} + +PicturePile::~PicturePile() { +} + +bool PicturePile::UpdateAndExpandInvalidation( + ContentLayerClient* painter, + Region* invalidation, + const gfx::Size& layer_size, + const gfx::Rect& visible_layer_rect, + int frame_number, + RecordingSource::RecordingMode recording_mode) { + gfx::Rect interest_rect = visible_layer_rect; + interest_rect.Inset(-pixel_record_distance_, -pixel_record_distance_); + recorded_viewport_ = interest_rect; + recorded_viewport_.Intersect(gfx::Rect(layer_size)); + + bool updated = ApplyInvalidationAndResize(interest_rect, invalidation, + layer_size, frame_number); + std::vector<gfx::Rect> invalid_tiles; + GetInvalidTileRects(interest_rect, &invalid_tiles); + std::vector<gfx::Rect> record_rects; + ClusterTiles(invalid_tiles, &record_rects); + + if (record_rects.empty()) + return updated; + + CreatePictures(painter, recording_mode, record_rects); + + DetermineIfSolidColor(); + + has_any_recordings_ = true; + DCHECK(CanRasterSlowTileCheck(recorded_viewport_)); + return true; +} + +bool PicturePile::ApplyInvalidationAndResize(const gfx::Rect& interest_rect, + Region* invalidation, + const gfx::Size& layer_size, + int frame_number) { + bool updated = false; + + Region synthetic_invalidation; + gfx::Size old_tiling_size = GetSize(); + if (old_tiling_size != layer_size) { + tiling_.SetTilingSize(layer_size); + updated = true; + } + + gfx::Rect interest_rect_over_tiles = + tiling_.ExpandRectToTileBounds(interest_rect); + + if (old_tiling_size != layer_size) { + gfx::Size min_tiling_size( + std::min(GetSize().width(), old_tiling_size.width()), + std::min(GetSize().height(), old_tiling_size.height())); + gfx::Size max_tiling_size( + std::max(GetSize().width(), old_tiling_size.width()), + std::max(GetSize().height(), old_tiling_size.height())); + + has_any_recordings_ = false; + + // Drop recordings that are outside the new or old layer bounds or that + // changed size. Newly exposed areas are considered invalidated. + // Previously exposed areas that are now outside of bounds also need to + // be invalidated, as they may become part of raster when scale < 1. + std::vector<PictureMapKey> to_erase; + int min_toss_x = tiling_.num_tiles_x(); + if (max_tiling_size.width() > min_tiling_size.width()) { + min_toss_x = + tiling_.FirstBorderTileXIndexFromSrcCoord(min_tiling_size.width()); + } + int min_toss_y = tiling_.num_tiles_y(); + if (max_tiling_size.height() > min_tiling_size.height()) { + min_toss_y = + tiling_.FirstBorderTileYIndexFromSrcCoord(min_tiling_size.height()); + } + for (const auto& key_picture_pair : picture_map_) { + const PictureMapKey& key = key_picture_pair.first; + if (key.first < min_toss_x && key.second < min_toss_y) { + has_any_recordings_ = true; + continue; + } + to_erase.push_back(key); + } + + for (size_t i = 0; i < to_erase.size(); ++i) + picture_map_.erase(to_erase[i]); + + // If a recording is dropped and not re-recorded below, invalidate that + // full recording to cause any raster tiles that would use it to be + // dropped. + // If the recording will be replaced below, invalidate newly exposed + // areas and previously exposed areas to force raster tiles that include the + // old recording to know there is new recording to display. + gfx::Rect min_tiling_rect_over_tiles = + tiling_.ExpandRectToTileBounds(gfx::Rect(min_tiling_size)); + if (min_toss_x < tiling_.num_tiles_x()) { + // The bounds which we want to invalidate are the tiles along the old + // edge of the pile when expanding, or the new edge of the pile when + // shrinking. In either case, it's the difference of the two, so we'll + // call this bounding box the DELTA EDGE RECT. + // + // In the picture below, the delta edge rect would be the bounding box of + // tiles {h,i,j}. |min_toss_x| would be equal to the horizontal index of + // the same tiles. + // + // min pile edge-v max pile edge-v + // ---------------+ - - - - - - - -+ + // mmppssvvyybbeeh|h . + // mmppssvvyybbeeh|h . + // nnqqttwwzzccffi|i . + // nnqqttwwzzccffi|i . + // oorruuxxaaddggj|j . + // oorruuxxaaddggj|j . + // ---------------+ - - - - - - - -+ <- min pile edge + // . + // - - - - - - - - - - - - - - - -+ <- max pile edge + // + // If you were to slide a vertical beam from the left edge of the + // delta edge rect toward the right, it would either hit the right edge + // of the delta edge rect, or the interest rect (expanded to the bounds + // of the tiles it touches). The same is true for a beam parallel to + // any of the four edges, sliding across the delta edge rect. We use + // the union of these four rectangles generated by these beams to + // determine which part of the delta edge rect is outside of the expanded + // interest rect. + // + // Case 1: Intersect rect is outside the delta edge rect. It can be + // either on the left or the right. The |left_rect| and |right_rect|, + // cover this case, one will be empty and one will cover the full + // delta edge rect. In the picture below, |left_rect| would cover the + // delta edge rect, and |right_rect| would be empty. + // +----------------------+ |^^^^^^^^^^^^^^^| + // |===> DELTA EDGE RECT | | | + // |===> | | INTEREST RECT | + // |===> | | | + // |===> | | | + // +----------------------+ |vvvvvvvvvvvvvvv| + // + // Case 2: Interest rect is inside the delta edge rect. It will always + // fill the entire delta edge rect horizontally since the old edge rect + // is a single tile wide, and the interest rect has been expanded to the + // bounds of the tiles it touches. In this case the |left_rect| and + // |right_rect| will be empty, but the case is handled by the |top_rect| + // and |bottom_rect|. In the picture below, neither the |top_rect| nor + // |bottom_rect| would empty, they would each cover the area of the old + // edge rect outside the expanded interest rect. + // +-----------------+ + // |:::::::::::::::::| + // |:::::::::::::::::| + // |vvvvvvvvvvvvvvvvv| + // | | + // +-----------------+ + // | INTEREST RECT | + // | | + // +-----------------+ + // | | + // | DELTA EDGE RECT | + // +-----------------+ + // + // Lastly, we need to consider tiles inside the expanded interest rect. + // For those tiles, we want to invalidate exactly the newly exposed + // pixels. In the picture below the tiles in the delta edge rect have + // been resized and the area covered by periods must be invalidated. The + // |exposed_rect| will cover exactly that area. + // v-min pile edge + // +---------+-------+ + // | ........| + // | ........| + // | DELTA EDGE.RECT.| + // | ........| + // | ........| + // | ........| + // | ........| + // | ........| + // | ........| + // +---------+-------+ + + int left = tiling_.TilePositionX(min_toss_x); + int right = left + tiling_.TileSizeX(min_toss_x); + int top = min_tiling_rect_over_tiles.y(); + int bottom = min_tiling_rect_over_tiles.bottom(); + + int left_until = std::min(interest_rect_over_tiles.x(), right); + int right_until = std::max(interest_rect_over_tiles.right(), left); + int top_until = std::min(interest_rect_over_tiles.y(), bottom); + int bottom_until = std::max(interest_rect_over_tiles.bottom(), top); + + int exposed_left = min_tiling_size.width(); + int exposed_left_until = max_tiling_size.width(); + int exposed_top = top; + int exposed_bottom = max_tiling_size.height(); + DCHECK_GE(exposed_left, left); + + gfx::Rect left_rect(left, top, left_until - left, bottom - top); + gfx::Rect right_rect(right_until, top, right - right_until, bottom - top); + gfx::Rect top_rect(left, top, right - left, top_until - top); + gfx::Rect bottom_rect( + left, bottom_until, right - left, bottom - bottom_until); + gfx::Rect exposed_rect(exposed_left, + exposed_top, + exposed_left_until - exposed_left, + exposed_bottom - exposed_top); + synthetic_invalidation.Union(left_rect); + synthetic_invalidation.Union(right_rect); + synthetic_invalidation.Union(top_rect); + synthetic_invalidation.Union(bottom_rect); + synthetic_invalidation.Union(exposed_rect); + } + if (min_toss_y < tiling_.num_tiles_y()) { + // The same thing occurs here as in the case above, but the invalidation + // rect is the bounding box around the bottom row of tiles in the min + // pile. This would be tiles {o,r,u,x,a,d,g,j} in the above picture. + + int top = tiling_.TilePositionY(min_toss_y); + int bottom = top + tiling_.TileSizeY(min_toss_y); + int left = min_tiling_rect_over_tiles.x(); + int right = min_tiling_rect_over_tiles.right(); + + int top_until = std::min(interest_rect_over_tiles.y(), bottom); + int bottom_until = std::max(interest_rect_over_tiles.bottom(), top); + int left_until = std::min(interest_rect_over_tiles.x(), right); + int right_until = std::max(interest_rect_over_tiles.right(), left); + + int exposed_top = min_tiling_size.height(); + int exposed_top_until = max_tiling_size.height(); + int exposed_left = left; + int exposed_right = max_tiling_size.width(); + DCHECK_GE(exposed_top, top); + + gfx::Rect left_rect(left, top, left_until - left, bottom - top); + gfx::Rect right_rect(right_until, top, right - right_until, bottom - top); + gfx::Rect top_rect(left, top, right - left, top_until - top); + gfx::Rect bottom_rect( + left, bottom_until, right - left, bottom - bottom_until); + gfx::Rect exposed_rect(exposed_left, + exposed_top, + exposed_right - exposed_left, + exposed_top_until - exposed_top); + synthetic_invalidation.Union(left_rect); + synthetic_invalidation.Union(right_rect); + synthetic_invalidation.Union(top_rect); + synthetic_invalidation.Union(bottom_rect); + synthetic_invalidation.Union(exposed_rect); + } + } + + // Detect cases where the full pile is invalidated, in this situation we + // can just drop/invalidate everything. + if (invalidation->Contains(gfx::Rect(old_tiling_size)) || + invalidation->Contains(gfx::Rect(GetSize()))) { + updated = !picture_map_.empty(); + picture_map_.clear(); + } else { + // Expand invalidation that is on tiles that aren't in the interest rect and + // will not be re-recorded below. These tiles are no longer valid and should + // be considerered fully invalid, so we can know to not keep around raster + // tiles that intersect with these recording tiles. + Region invalidation_expanded_to_full_tiles; + + for (Region::Iterator i(*invalidation); i.has_rect(); i.next()) { + gfx::Rect invalid_rect = i.rect(); + + // This rect covers the bounds (excluding borders) of all tiles whose + // bounds (including borders) touch the |interest_rect|. This matches + // the iteration of the |invalid_rect| below which includes borders when + // calling Invalidate() on pictures. + gfx::Rect invalid_rect_outside_interest_rect_tiles = + tiling_.ExpandRectToTileBounds(invalid_rect); + // We subtract the |interest_rect_over_tiles| which represents the bounds + // of tiles that will be re-recorded below. This matches the iteration of + // |interest_rect| below which includes borders. + // TODO(danakj): We should have a Rect-subtract-Rect-to-2-rects operator + // instead of using Rect::Subtract which gives you the bounding box of the + // subtraction. + invalid_rect_outside_interest_rect_tiles.Subtract( + interest_rect_over_tiles); + invalidation_expanded_to_full_tiles.Union( + invalid_rect_outside_interest_rect_tiles); + + // Split this inflated invalidation across tile boundaries and apply it + // to all tiles that it touches. + bool include_borders = true; + for (TilingData::Iterator iter(&tiling_, invalid_rect, include_borders); + iter; + ++iter) { + const PictureMapKey& key = iter.index(); + + PictureMap::iterator picture_it = picture_map_.find(key); + if (picture_it == picture_map_.end()) + continue; + + updated = true; + picture_map_.erase(key); + + // Invalidate drops the picture so the whole tile better be invalidated + // if it won't be re-recorded below. + DCHECK_IMPLIES(!tiling_.TileBounds(key.first, key.second) + .Intersects(interest_rect_over_tiles), + invalidation_expanded_to_full_tiles.Contains( + tiling_.TileBounds(key.first, key.second))); + } + } + invalidation->Union(invalidation_expanded_to_full_tiles); + } + + invalidation->Union(synthetic_invalidation); + return updated; +} + +void PicturePile::GetInvalidTileRects(const gfx::Rect& interest_rect, + std::vector<gfx::Rect>* invalid_tiles) { + // Make a list of all invalid tiles; we will attempt to + // cluster these into multiple invalidation regions. + bool include_borders = true; + for (TilingData::Iterator it(&tiling_, interest_rect, include_borders); it; + ++it) { + const PictureMapKey& key = it.index(); + if (picture_map_.find(key) == picture_map_.end()) + invalid_tiles->push_back(tiling_.TileBounds(key.first, key.second)); + } +} + +void PicturePile::CreatePictures(ContentLayerClient* painter, + RecordingSource::RecordingMode recording_mode, + const std::vector<gfx::Rect>& record_rects) { + for (const auto& record_rect : record_rects) { + gfx::Rect padded_record_rect = PadRect(record_rect); + + int repeat_count = std::max(1, slow_down_raster_scale_factor_for_debug_); + scoped_refptr<Picture> picture; + + for (int i = 0; i < repeat_count; i++) { + picture = Picture::Create(padded_record_rect, painter, tile_grid_size_, + gather_pixel_refs_, recording_mode); + // Note the '&&' with previous is-suitable state. + // This means that once a picture-pile becomes unsuitable for gpu + // rasterization due to some content, it will continue to be unsuitable + // even if that content is replaced by gpu-friendly content. + // This is an optimization to avoid iterating though all pictures in + // the pile after each invalidation. + if (is_suitable_for_gpu_rasterization_) { + const char* reason = nullptr; + is_suitable_for_gpu_rasterization_ &= + picture->IsSuitableForGpuRasterization(&reason); + + if (!is_suitable_for_gpu_rasterization_) { + TRACE_EVENT_INSTANT1("cc", "GPU Rasterization Veto", + TRACE_EVENT_SCOPE_THREAD, "reason", reason); + } + } + } + + bool found_tile_for_recorded_picture = false; + + bool include_borders = true; + for (TilingData::Iterator it(&tiling_, padded_record_rect, include_borders); + it; ++it) { + const PictureMapKey& key = it.index(); + gfx::Rect tile = PaddedRect(key); + if (padded_record_rect.Contains(tile)) { + picture_map_[key] = picture; + found_tile_for_recorded_picture = true; + } + } + DCHECK(found_tile_for_recorded_picture); + } +} + +scoped_refptr<RasterSource> PicturePile::CreateRasterSource( + bool can_use_lcd_text) const { + return scoped_refptr<RasterSource>( + PicturePileImpl::CreateFromPicturePile(this, can_use_lcd_text)); +} + +gfx::Size PicturePile::GetSize() const { + return tiling_.tiling_size(); +} + +void PicturePile::SetEmptyBounds() { + tiling_.SetTilingSize(gfx::Size()); + Clear(); +} + +void PicturePile::SetMinContentsScale(float min_contents_scale) { + DCHECK(min_contents_scale); + if (min_contents_scale_ == min_contents_scale) + return; + + // Picture contents are played back scaled. When the final contents scale is + // less than 1 (i.e. low res), then multiple recorded pixels will be used + // to raster one final pixel. To avoid splitting a final pixel across + // pictures (which would result in incorrect rasterization due to blending), a + // buffer margin is added so that any picture can be snapped to integral + // final pixels. + // + // For example, if a 1/4 contents scale is used, then that would be 3 buffer + // pixels, since that's the minimum number of pixels to add so that resulting + // content can be snapped to a four pixel aligned grid. + int buffer_pixels = static_cast<int>(ceil(1 / min_contents_scale) - 1); + buffer_pixels = std::max(0, buffer_pixels); + SetBufferPixels(buffer_pixels); + min_contents_scale_ = min_contents_scale; +} + +void PicturePile::SetSlowdownRasterScaleFactor(int factor) { + slow_down_raster_scale_factor_for_debug_ = factor; +} + +void PicturePile::SetGatherPixelRefs(bool gather_pixel_refs) { + gather_pixel_refs_ = gather_pixel_refs; +} + +void PicturePile::SetBackgroundColor(SkColor background_color) { + background_color_ = background_color; +} + +void PicturePile::SetRequiresClear(bool requires_clear) { + requires_clear_ = requires_clear; +} + +bool PicturePile::IsSuitableForGpuRasterization() const { + return is_suitable_for_gpu_rasterization_; +} + +void PicturePile::SetTileGridSize(const gfx::Size& tile_grid_size) { + DCHECK_GT(tile_grid_size.width(), 0); + DCHECK_GT(tile_grid_size.height(), 0); + + tile_grid_size_ = tile_grid_size; +} + +void PicturePile::SetUnsuitableForGpuRasterizationForTesting() { + is_suitable_for_gpu_rasterization_ = false; +} + +gfx::Size PicturePile::GetTileGridSizeForTesting() const { + return tile_grid_size_; +} + +bool PicturePile::CanRasterSlowTileCheck(const gfx::Rect& layer_rect) const { + bool include_borders = false; + for (TilingData::Iterator tile_iter(&tiling_, layer_rect, include_borders); + tile_iter; ++tile_iter) { + PictureMap::const_iterator map_iter = picture_map_.find(tile_iter.index()); + if (map_iter == picture_map_.end()) + return false; + } + return true; +} + +void PicturePile::DetermineIfSolidColor() { + is_solid_color_ = false; + solid_color_ = SK_ColorTRANSPARENT; + + if (picture_map_.empty()) { + return; + } + + PictureMap::const_iterator it = picture_map_.begin(); + const Picture* picture = it->second.get(); + + // Missing recordings due to frequent invalidations or being too far away + // from the interest rect will cause the a null picture to exist. + if (!picture) + return; + + // Don't bother doing more work if the first image is too complicated. + if (picture->ApproximateOpCount() > kOpCountThatIsOkToAnalyze) + return; + + // Make sure all of the mapped images point to the same picture. + for (++it; it != picture_map_.end(); ++it) { + if (it->second.get() != picture) + return; + } + + gfx::Size layer_size = GetSize(); + skia::AnalysisCanvas canvas(layer_size.width(), layer_size.height()); + + picture->Raster(&canvas, nullptr, Region(), 1.0f); + is_solid_color_ = canvas.GetColorIfSolid(&solid_color_); +} + +gfx::Rect PicturePile::PaddedRect(const PictureMapKey& key) const { + gfx::Rect tile = tiling_.TileBounds(key.first, key.second); + return PadRect(tile); +} + +gfx::Rect PicturePile::PadRect(const gfx::Rect& rect) const { + gfx::Rect padded_rect = rect; + padded_rect.Inset(-buffer_pixels(), -buffer_pixels(), -buffer_pixels(), + -buffer_pixels()); + return padded_rect; +} + +void PicturePile::Clear() { + picture_map_.clear(); + recorded_viewport_ = gfx::Rect(); + has_any_recordings_ = false; + is_solid_color_ = false; +} + +void PicturePile::SetBufferPixels(int new_buffer_pixels) { + if (new_buffer_pixels == buffer_pixels()) + return; + + Clear(); + tiling_.SetBorderTexels(new_buffer_pixels); +} + +} // namespace cc diff --git a/cc/playback/picture_pile.h b/cc/playback/picture_pile.h new file mode 100644 index 0000000..889e9f5 --- /dev/null +++ b/cc/playback/picture_pile.h @@ -0,0 +1,107 @@ +// Copyright 2012 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. + +#ifndef CC_PLAYBACK_PICTURE_PILE_H_ +#define CC_PLAYBACK_PICTURE_PILE_H_ + +#include <bitset> +#include <utility> +#include <vector> + +#include "base/containers/hash_tables.h" +#include "base/memory/ref_counted.h" +#include "cc/base/tiling_data.h" +#include "cc/playback/picture.h" + +namespace cc { +class PicturePileImpl; + +class CC_EXPORT PicturePile : public RecordingSource { + public: + PicturePile(float min_contents_scale, const gfx::Size& tile_grid_size); + ~PicturePile() override; + + // RecordingSource overrides. + bool UpdateAndExpandInvalidation(ContentLayerClient* painter, + Region* invalidation, + const gfx::Size& layer_size, + const gfx::Rect& visible_layer_rect, + int frame_number, + RecordingMode recording_mode) override; + scoped_refptr<RasterSource> CreateRasterSource( + bool can_use_lcd_text) const override; + gfx::Size GetSize() const final; + void SetEmptyBounds() override; + void SetSlowdownRasterScaleFactor(int factor) override; + void SetGatherPixelRefs(bool gather_pixel_refs) override; + void SetBackgroundColor(SkColor background_color) override; + void SetRequiresClear(bool requires_clear) override; + bool IsSuitableForGpuRasterization() const override; + void SetUnsuitableForGpuRasterizationForTesting() override; + gfx::Size GetTileGridSizeForTesting() const override; + + typedef std::pair<int, int> PictureMapKey; + typedef base::hash_map<PictureMapKey, scoped_refptr<const Picture>> + PictureMap; + + // An internal CanRaster check that goes to the picture_map rather than + // using the recorded_viewport hint. + bool CanRasterSlowTileCheck(const gfx::Rect& layer_rect) const; + + void Clear(); + + void SetMinContentsScale(float min_contents_scale); + void SetTileGridSize(const gfx::Size& tile_grid_size); + + gfx::Rect PaddedRect(const PictureMapKey& key) const; + gfx::Rect PadRect(const gfx::Rect& rect) const; + + int buffer_pixels() const { return tiling_.border_texels(); } + + // A picture pile is a tiled set of pictures. The picture map is a map of tile + // indices to picture infos. + PictureMap picture_map_; + TilingData tiling_; + + // If non-empty, all pictures tiles inside this rect are recorded. There may + // be recordings outside this rect, but everything inside the rect is + // recorded. + gfx::Rect recorded_viewport_; + float min_contents_scale_; + gfx::Size tile_grid_size_; + int slow_down_raster_scale_factor_for_debug_; + bool gather_pixel_refs_; + // A hint about whether there are any recordings. This may be a false + // positive. + bool has_any_recordings_; + bool clear_canvas_with_debug_color_; + bool requires_clear_; + bool is_solid_color_; + SkColor solid_color_; + SkColor background_color_; + int pixel_record_distance_; + + private: + friend class PicturePileImpl; + + void CreatePictures(ContentLayerClient* painter, + RecordingMode recording_mode, + const std::vector<gfx::Rect>& record_rects); + void GetInvalidTileRects(const gfx::Rect& interest_rect, + std::vector<gfx::Rect>* invalid_tiles); + bool ApplyInvalidationAndResize(const gfx::Rect& interest_rect, + Region* invalidation, + const gfx::Size& layer_size, + int frame_number); + void DetermineIfSolidColor(); + void SetBufferPixels(int buffer_pixels); + + bool is_suitable_for_gpu_rasterization_; + + DISALLOW_COPY_AND_ASSIGN(PicturePile); +}; + +} // namespace cc + +#endif // CC_PLAYBACK_PICTURE_PILE_H_ diff --git a/cc/playback/picture_pile_impl.cc b/cc/playback/picture_pile_impl.cc new file mode 100644 index 0000000..96c6f63 --- /dev/null +++ b/cc/playback/picture_pile_impl.cc @@ -0,0 +1,463 @@ +// Copyright 2012 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 <algorithm> +#include <limits> +#include <set> + +#include "base/trace_event/trace_event.h" +#include "cc/base/region.h" +#include "cc/debug/debug_colors.h" +#include "cc/playback/picture_pile_impl.h" +#include "cc/playback/raster_source_helper.h" +#include "skia/ext/analysis_canvas.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" +#include "ui/gfx/geometry/rect_conversions.h" + +namespace cc { + +scoped_refptr<PicturePileImpl> PicturePileImpl::CreateFromPicturePile( + const PicturePile* other, + bool can_use_lcd_text) { + return make_scoped_refptr(new PicturePileImpl(other, can_use_lcd_text)); +} + +PicturePileImpl::PicturePileImpl() + : background_color_(SK_ColorTRANSPARENT), + requires_clear_(true), + can_use_lcd_text_(true), + is_solid_color_(false), + solid_color_(SK_ColorTRANSPARENT), + has_any_recordings_(false), + clear_canvas_with_debug_color_(false), + min_contents_scale_(0.f), + slow_down_raster_scale_factor_for_debug_(0), + should_attempt_to_use_distance_field_text_(false), + picture_memory_usage_(0) { +} + +PicturePileImpl::PicturePileImpl(const PicturePile* other, + bool can_use_lcd_text) + : picture_map_(other->picture_map_), + tiling_(other->tiling_), + background_color_(other->background_color_), + requires_clear_(other->requires_clear_), + can_use_lcd_text_(can_use_lcd_text), + is_solid_color_(other->is_solid_color_), + solid_color_(other->solid_color_), + recorded_viewport_(other->recorded_viewport_), + has_any_recordings_(other->has_any_recordings_), + clear_canvas_with_debug_color_(other->clear_canvas_with_debug_color_), + min_contents_scale_(other->min_contents_scale_), + slow_down_raster_scale_factor_for_debug_( + other->slow_down_raster_scale_factor_for_debug_), + should_attempt_to_use_distance_field_text_(false), + picture_memory_usage_(0) { + // Figure out the picture size upon construction. + base::hash_set<const Picture*> pictures_seen; + for (const auto& map_value : picture_map_) { + const Picture* picture = map_value.second.get(); + if (pictures_seen.insert(picture).second) + picture_memory_usage_ += picture->ApproximateMemoryUsage(); + } +} + +PicturePileImpl::PicturePileImpl(const PicturePileImpl* other, + bool can_use_lcd_text) + : picture_map_(other->picture_map_), + tiling_(other->tiling_), + background_color_(other->background_color_), + requires_clear_(other->requires_clear_), + can_use_lcd_text_(can_use_lcd_text), + is_solid_color_(other->is_solid_color_), + solid_color_(other->solid_color_), + recorded_viewport_(other->recorded_viewport_), + has_any_recordings_(other->has_any_recordings_), + clear_canvas_with_debug_color_(other->clear_canvas_with_debug_color_), + min_contents_scale_(other->min_contents_scale_), + slow_down_raster_scale_factor_for_debug_( + other->slow_down_raster_scale_factor_for_debug_), + should_attempt_to_use_distance_field_text_( + other->should_attempt_to_use_distance_field_text_), + picture_memory_usage_(other->picture_memory_usage_) { +} + +PicturePileImpl::~PicturePileImpl() { +} + +void PicturePileImpl::PlaybackToSharedCanvas(SkCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const { + RasterCommon(canvas, NULL, canvas_rect, contents_scale); +} + +void PicturePileImpl::RasterForAnalysis(skia::AnalysisCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const { + RasterCommon(canvas, canvas, canvas_rect, contents_scale); +} + +void PicturePileImpl::PlaybackToCanvas(SkCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const { + RasterSourceHelper::PrepareForPlaybackToCanvas( + canvas, canvas_rect, gfx::Rect(tiling_.tiling_size()), contents_scale, + background_color_, clear_canvas_with_debug_color_, requires_clear_); + RasterCommon(canvas, NULL, canvas_rect, contents_scale); +} + +void PicturePileImpl::CoalesceRasters(const gfx::Rect& canvas_rect, + const gfx::Rect& content_rect, + float contents_scale, + PictureRegionMap* results) const { + DCHECK(results); + // Rasterize the collection of relevant picture piles. + gfx::Rect layer_rect = gfx::ScaleToEnclosingRect( + content_rect, 1.f / contents_scale); + + // Make sure pictures don't overlap by keeping track of previous right/bottom. + int min_content_left = -1; + int min_content_top = -1; + int last_row_index = -1; + int last_col_index = -1; + gfx::Rect last_content_rect; + + // Coalesce rasters of the same picture into different rects: + // - Compute the clip of each of the pile chunks, + // - Subtract it from the canvas rect to get difference region + // - Later, use the difference region to subtract each of the comprising + // rects from the canvas. + // Note that in essence, we're trying to mimic clipRegion with intersect op + // that also respects the current canvas transform and clip. In order to use + // the canvas transform, we must stick to clipRect operations (clipRegion + // ignores the transform). Intersect then can be written as subtracting the + // negation of the region we're trying to intersect. Luckily, we know that all + // of the rects will have to fit into |content_rect|, so we can start with + // that and subtract chunk rects to get the region that we need to subtract + // from the canvas. Then, we can use clipRect with difference op to subtract + // each rect in the region. + bool include_borders = true; + for (TilingData::Iterator tile_iter(&tiling_, layer_rect, include_borders); + tile_iter; + ++tile_iter) { + PictureMap::const_iterator map_iter = picture_map_.find(tile_iter.index()); + if (map_iter == picture_map_.end()) + continue; + const Picture* picture = map_iter->second.get(); + DCHECK(picture); + + // This is intentionally *enclosed* rect, so that the clip is aligned on + // integral post-scale content pixels and does not extend past the edges + // of the picture chunk's layer rect. The min_contents_scale enforces that + // enough buffer pixels have been added such that the enclosed rect + // encompasses all invalidated pixels at any larger scale level. + gfx::Rect chunk_rect = PaddedRect(tile_iter.index()); + gfx::Rect content_clip = + gfx::ScaleToEnclosedRect(chunk_rect, contents_scale); + DCHECK(!content_clip.IsEmpty()) << "Layer rect: " + << picture->LayerRect().ToString() + << "Contents scale: " << contents_scale; + content_clip.Intersect(canvas_rect); + + // Make sure iterator goes top->bottom. + DCHECK_GE(tile_iter.index_y(), last_row_index); + if (tile_iter.index_y() > last_row_index) { + // First tile in a new row. + min_content_left = content_clip.x(); + min_content_top = last_content_rect.bottom(); + } else { + // Make sure iterator goes left->right. + DCHECK_GT(tile_iter.index_x(), last_col_index); + min_content_left = last_content_rect.right(); + min_content_top = last_content_rect.y(); + } + + last_col_index = tile_iter.index_x(); + last_row_index = tile_iter.index_y(); + + // Only inset if the content_clip is less than then previous min. + int inset_left = std::max(0, min_content_left - content_clip.x()); + int inset_top = std::max(0, min_content_top - content_clip.y()); + content_clip.Inset(inset_left, inset_top, 0, 0); + + PictureRegionMap::iterator it = results->find(picture); + Region* clip_region; + if (it == results->end()) { + // The clip for a set of coalesced pictures starts out clipping the entire + // canvas. Each picture added to the set must subtract its own bounds + // from the clip region, poking a hole so that the picture is unclipped. + clip_region = &(*results)[picture]; + *clip_region = canvas_rect; + } else { + clip_region = &it->second; + } + + DCHECK(clip_region->Contains(content_clip)) + << "Content clips should not overlap."; + clip_region->Subtract(content_clip); + last_content_rect = content_clip; + } +} + +void PicturePileImpl::RasterCommon(SkCanvas* canvas, + SkDrawPictureCallback* callback, + const gfx::Rect& canvas_rect, + float contents_scale) const { + DCHECK(contents_scale >= min_contents_scale_); + + canvas->translate(-canvas_rect.x(), -canvas_rect.y()); + gfx::Rect content_tiling_rect = gfx::ToEnclosingRect( + gfx::ScaleRect(gfx::Rect(tiling_.tiling_size()), contents_scale)); + content_tiling_rect.Intersect(canvas_rect); + + canvas->clipRect(gfx::RectToSkRect(content_tiling_rect), + SkRegion::kIntersect_Op); + + PictureRegionMap picture_region_map; + CoalesceRasters( + canvas_rect, content_tiling_rect, contents_scale, &picture_region_map); + +#ifndef NDEBUG + Region total_clip; +#endif // NDEBUG + + // Iterate the coalesced map and use each picture's region + // to clip the canvas. + for (PictureRegionMap::iterator it = picture_region_map.begin(); + it != picture_region_map.end(); + ++it) { + const Picture* picture = it->first; + Region negated_clip_region = it->second; + +#ifndef NDEBUG + Region positive_clip = content_tiling_rect; + positive_clip.Subtract(negated_clip_region); + // Make sure we never rasterize the same region twice. + DCHECK(!total_clip.Intersects(positive_clip)); + total_clip.Union(positive_clip); +#endif // NDEBUG + + int repeat_count = std::max(1, slow_down_raster_scale_factor_for_debug_); + + for (int j = 0; j < repeat_count; ++j) + picture->Raster(canvas, callback, negated_clip_region, contents_scale); + } + +#ifndef NDEBUG + // Fill the clip with debug color. This allows us to + // distinguish between non painted areas and problems with missing + // pictures. + SkPaint paint; + for (Region::Iterator it(total_clip); it.has_rect(); it.next()) + canvas->clipRect(gfx::RectToSkRect(it.rect()), SkRegion::kDifference_Op); + paint.setColor(DebugColors::MissingPictureFillColor()); + paint.setXfermodeMode(SkXfermode::kSrc_Mode); + canvas->drawPaint(paint); +#endif // NDEBUG +} + +skia::RefPtr<SkPicture> PicturePileImpl::GetFlattenedPicture() { + TRACE_EVENT0("cc", "PicturePileImpl::GetFlattenedPicture"); + + gfx::Rect tiling_rect(tiling_.tiling_size()); + SkPictureRecorder recorder; + SkCanvas* canvas = + recorder.beginRecording(tiling_rect.width(), tiling_rect.height()); + if (!tiling_rect.IsEmpty()) + PlaybackToCanvas(canvas, tiling_rect, 1.0); + skia::RefPtr<SkPicture> picture = + skia::AdoptRef(recorder.endRecordingAsPicture()); + + return picture; +} + +size_t PicturePileImpl::GetPictureMemoryUsage() const { + return picture_memory_usage_; +} + +void PicturePileImpl::PerformSolidColorAnalysis( + const gfx::Rect& content_rect, + float contents_scale, + RasterSource::SolidColorAnalysis* analysis) const { + DCHECK(analysis); + TRACE_EVENT0("cc", "PicturePileImpl::PerformSolidColorAnalysis"); + + gfx::Rect layer_rect = gfx::ScaleToEnclosingRect( + content_rect, 1.0f / contents_scale); + + layer_rect.Intersect(gfx::Rect(tiling_.tiling_size())); + + skia::AnalysisCanvas canvas(layer_rect.width(), layer_rect.height()); + + RasterForAnalysis(&canvas, layer_rect, 1.0f); + + analysis->is_solid_color = canvas.GetColorIfSolid(&analysis->solid_color); +} + +void PicturePileImpl::GatherPixelRefs( + const gfx::Rect& content_rect, + float contents_scale, + std::vector<SkPixelRef*>* pixel_refs) const { + DCHECK_EQ(0u, pixel_refs->size()); + for (PixelRefIterator iter(content_rect, contents_scale, this); iter; + ++iter) { + pixel_refs->push_back(*iter); + } +} + +bool PicturePileImpl::CoversRect(const gfx::Rect& content_rect, + float contents_scale) const { + if (tiling_.tiling_size().IsEmpty()) + return false; + gfx::Rect layer_rect = + gfx::ScaleToEnclosingRect(content_rect, 1.f / contents_scale); + layer_rect.Intersect(gfx::Rect(tiling_.tiling_size())); + + // Common case inside of viewport to avoid the slower map lookups. + if (recorded_viewport_.Contains(layer_rect)) { + // Sanity check that there are no false positives in recorded_viewport_. + DCHECK(CanRasterSlowTileCheck(layer_rect)); + return true; + } + + return CanRasterSlowTileCheck(layer_rect); +} + +gfx::Size PicturePileImpl::GetSize() const { + return tiling_.tiling_size(); +} + +bool PicturePileImpl::IsSolidColor() const { + return is_solid_color_; +} + +SkColor PicturePileImpl::GetSolidColor() const { + DCHECK(IsSolidColor()); + return solid_color_; +} + +bool PicturePileImpl::HasRecordings() const { + return has_any_recordings_; +} + +gfx::Rect PicturePileImpl::PaddedRect(const PictureMapKey& key) const { + gfx::Rect padded_rect = tiling_.TileBounds(key.first, key.second); + padded_rect.Inset(-buffer_pixels(), -buffer_pixels(), -buffer_pixels(), + -buffer_pixels()); + return padded_rect; +} + +bool PicturePileImpl::CanRasterSlowTileCheck( + const gfx::Rect& layer_rect) const { + bool include_borders = false; + for (TilingData::Iterator tile_iter(&tiling_, layer_rect, include_borders); + tile_iter; ++tile_iter) { + PictureMap::const_iterator map_iter = picture_map_.find(tile_iter.index()); + if (map_iter == picture_map_.end()) + return false; + } + return true; +} + +void PicturePileImpl::SetShouldAttemptToUseDistanceFieldText() { + should_attempt_to_use_distance_field_text_ = true; +} + +bool PicturePileImpl::ShouldAttemptToUseDistanceFieldText() const { + return should_attempt_to_use_distance_field_text_; +} + +void PicturePileImpl::AsValueInto( + base::trace_event::TracedValue* pictures) const { + gfx::Rect tiling_rect(tiling_.tiling_size()); + std::set<const void*> appended_pictures; + bool include_borders = true; + for (TilingData::Iterator tile_iter(&tiling_, tiling_rect, include_borders); + tile_iter; ++tile_iter) { + PictureMap::const_iterator map_iter = picture_map_.find(tile_iter.index()); + if (map_iter == picture_map_.end()) + continue; + + const Picture* picture = map_iter->second.get(); + if (appended_pictures.count(picture) == 0) { + appended_pictures.insert(picture); + TracedValue::AppendIDRef(picture, pictures); + } + } +} + +bool PicturePileImpl::CanUseLCDText() const { + return can_use_lcd_text_; +} + +scoped_refptr<RasterSource> PicturePileImpl::CreateCloneWithoutLCDText() const { + DCHECK(CanUseLCDText()); + bool can_use_lcd_text = false; + return scoped_refptr<RasterSource>( + new PicturePileImpl(this, can_use_lcd_text)); +} + +PicturePileImpl::PixelRefIterator::PixelRefIterator( + const gfx::Rect& content_rect, + float contents_scale, + const PicturePileImpl* picture_pile) + : picture_pile_(picture_pile), + layer_rect_( + gfx::ScaleToEnclosingRect(content_rect, 1.f / contents_scale)), + tile_iterator_(&picture_pile_->tiling_, + layer_rect_, + false /* include_borders */) { + // Early out if there isn't a single tile. + if (!tile_iterator_) + return; + + AdvanceToTilePictureWithPixelRefs(); +} + +PicturePileImpl::PixelRefIterator::~PixelRefIterator() { +} + +PicturePileImpl::PixelRefIterator& + PicturePileImpl::PixelRefIterator::operator++() { + ++pixel_ref_iterator_; + if (pixel_ref_iterator_) + return *this; + + ++tile_iterator_; + AdvanceToTilePictureWithPixelRefs(); + return *this; +} + +void PicturePileImpl::PixelRefIterator::AdvanceToTilePictureWithPixelRefs() { + for (; tile_iterator_; ++tile_iterator_) { + PictureMap::const_iterator it = + picture_pile_->picture_map_.find(tile_iterator_.index()); + if (it == picture_pile_->picture_map_.end()) + continue; + + const Picture* picture = it->second.get(); + if ((processed_pictures_.count(picture) != 0) || + !picture->WillPlayBackBitmaps()) + continue; + + processed_pictures_.insert(picture); + pixel_ref_iterator_ = picture->GetPixelRefMapIterator(layer_rect_); + if (pixel_ref_iterator_) + break; + } +} + +void PicturePileImpl::DidBeginTracing() { + std::set<const void*> processed_pictures; + for (const auto& map_pair : picture_map_) { + const Picture* picture = map_pair.second.get(); + if (processed_pictures.count(picture) == 0) { + picture->EmitTraceSnapshot(); + processed_pictures.insert(picture); + } + } +} + +} // namespace cc diff --git a/cc/playback/picture_pile_impl.h b/cc/playback/picture_pile_impl.h new file mode 100644 index 0000000..e8be323 --- /dev/null +++ b/cc/playback/picture_pile_impl.h @@ -0,0 +1,159 @@ +// Copyright 2012 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. + +#ifndef CC_PLAYBACK_PICTURE_PILE_IMPL_H_ +#define CC_PLAYBACK_PICTURE_PILE_IMPL_H_ + +#include <map> +#include <set> +#include <vector> + +#include "base/time/time.h" +#include "cc/base/cc_export.h" +#include "cc/debug/rendering_stats_instrumentation.h" +#include "cc/playback/picture_pile.h" +#include "cc/playback/pixel_ref_map.h" +#include "cc/playback/raster_source.h" +#include "skia/ext/analysis_canvas.h" +#include "skia/ext/refptr.h" + +class SkCanvas; +class SkPicture; +class SkPixelRef; + +namespace gfx { +class Rect; +} + +namespace cc { + +class CC_EXPORT PicturePileImpl : public RasterSource { + public: + static scoped_refptr<PicturePileImpl> CreateFromPicturePile( + const PicturePile* other, + bool can_use_lcd_text); + + // RasterSource overrides. See RasterSource header for full description. + // When slow-down-raster-scale-factor is set to a value greater than 1, the + // reported rasterize time (in stats_instrumentation) is the minimum measured + // value over all runs. + void PlaybackToCanvas(SkCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const override; + void PlaybackToSharedCanvas(SkCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const override; + void PerformSolidColorAnalysis( + const gfx::Rect& content_rect, + float contents_scale, + RasterSource::SolidColorAnalysis* analysis) const override; + void GatherPixelRefs(const gfx::Rect& content_rect, + float contents_scale, + std::vector<SkPixelRef*>* pixel_refs) const override; + bool CoversRect(const gfx::Rect& content_rect, + float contents_scale) const override; + void SetShouldAttemptToUseDistanceFieldText() override; + bool ShouldAttemptToUseDistanceFieldText() const override; + gfx::Size GetSize() const override; + bool IsSolidColor() const override; + SkColor GetSolidColor() const override; + bool HasRecordings() const override; + bool CanUseLCDText() const override; + scoped_refptr<RasterSource> CreateCloneWithoutLCDText() const override; + + // Tracing functionality. + void DidBeginTracing() override; + void AsValueInto(base::trace_event::TracedValue* array) const override; + skia::RefPtr<SkPicture> GetFlattenedPicture() override; + size_t GetPictureMemoryUsage() const override; + + // Iterator used to return SkPixelRefs from this picture pile. + // Public for testing. + class CC_EXPORT PixelRefIterator { + public: + PixelRefIterator(const gfx::Rect& content_rect, + float contents_scale, + const PicturePileImpl* picture_pile); + ~PixelRefIterator(); + + SkPixelRef* operator->() const { return *pixel_ref_iterator_; } + SkPixelRef* operator*() const { return *pixel_ref_iterator_; } + PixelRefIterator& operator++(); + operator bool() const { return pixel_ref_iterator_; } + + private: + void AdvanceToTilePictureWithPixelRefs(); + + const PicturePileImpl* picture_pile_; + gfx::Rect layer_rect_; + TilingData::Iterator tile_iterator_; + PixelRefMap::Iterator pixel_ref_iterator_; + std::set<const void*> processed_pictures_; + }; + + protected: + friend class PicturePile; + friend class PixelRefIterator; + + using PictureMapKey = PicturePile::PictureMapKey; + using PictureMap = PicturePile::PictureMap; + + PicturePileImpl(); + explicit PicturePileImpl(const PicturePile* other, bool can_use_lcd_text); + explicit PicturePileImpl(const PicturePileImpl* other, bool can_use_lcd_text); + ~PicturePileImpl() override; + + int buffer_pixels() const { return tiling_.border_texels(); } + + // These members are const as this raster source may be in use on another + // thread and so should not be touched after construction. + const PictureMap picture_map_; + const TilingData tiling_; + const SkColor background_color_; + const bool requires_clear_; + const bool can_use_lcd_text_; + const bool is_solid_color_; + const SkColor solid_color_; + const gfx::Rect recorded_viewport_; + const bool has_any_recordings_; + const bool clear_canvas_with_debug_color_; + const float min_contents_scale_; + const int slow_down_raster_scale_factor_for_debug_; + // TODO(enne/vmiura): this has a read/write race between raster and compositor + // threads with multi-threaded Ganesh. Make this const or remove it. + bool should_attempt_to_use_distance_field_text_; + + size_t picture_memory_usage_; + + private: + typedef std::map<const Picture*, Region> PictureRegionMap; + + // Called when analyzing a tile. We can use AnalysisCanvas as + // SkDrawPictureCallback, which allows us to early out from analysis. + void RasterForAnalysis(skia::AnalysisCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const; + + void CoalesceRasters(const gfx::Rect& canvas_rect, + const gfx::Rect& content_rect, + float contents_scale, + PictureRegionMap* result) const; + + void RasterCommon(SkCanvas* canvas, + SkDrawPictureCallback* callback, + const gfx::Rect& canvas_rect, + float contents_scale) const; + + // An internal CanRaster check that goes to the picture_map rather than + // using the recorded_viewport hint. + bool CanRasterSlowTileCheck(const gfx::Rect& layer_rect) const; + + gfx::Rect PaddedRect(const PictureMapKey& key) const; + + DISALLOW_COPY_AND_ASSIGN(PicturePileImpl); +}; + +} // namespace cc + +#endif // CC_PLAYBACK_PICTURE_PILE_IMPL_H_ diff --git a/cc/playback/picture_pile_impl_perftest.cc b/cc/playback/picture_pile_impl_perftest.cc new file mode 100644 index 0000000..a70fe68 --- /dev/null +++ b/cc/playback/picture_pile_impl_perftest.cc @@ -0,0 +1,83 @@ +// 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/playback/picture_pile_impl.h" + +#include "cc/debug/lap_timer.h" +#include "cc/test/fake_picture_pile_impl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/perf/perf_test.h" + +namespace cc { +namespace { + +const int kTimeLimitMillis = 2000; +const int kWarmupRuns = 5; +const int kTimeCheckInterval = 10; + +const int kTileSize = 100; +const int kLayerSize = 1000; + +class PicturePileImplPerfTest : public testing::Test { + public: + PicturePileImplPerfTest() + : timer_(kWarmupRuns, + base::TimeDelta::FromMilliseconds(kTimeLimitMillis), + kTimeCheckInterval) {} + + void RunAnalyzeTest(const std::string& test_name, float contents_scale) { + scoped_refptr<PicturePileImpl> pile = FakePicturePileImpl::CreateFilledPile( + gfx::Size(kTileSize, kTileSize), gfx::Size(kLayerSize, kLayerSize)); + // Content rect that will align with top-left tile at scale 1.0. + gfx::Rect content_rect(0, 0, kTileSize, kTileSize); + + RasterSource::SolidColorAnalysis analysis; + timer_.Reset(); + do { + pile->PerformSolidColorAnalysis(content_rect, contents_scale, &analysis); + timer_.NextLap(); + } while (!timer_.HasTimeLimitExpired()); + + perf_test::PrintResult( + "analyze", "", test_name, timer_.LapsPerSecond(), "runs/s", true); + } + + void RunRasterTest(const std::string& test_name, float contents_scale) { + scoped_refptr<PicturePileImpl> pile = FakePicturePileImpl::CreateFilledPile( + gfx::Size(kTileSize, kTileSize), gfx::Size(kLayerSize, kLayerSize)); + // Content rect that will align with top-left tile at scale 1.0. + gfx::Rect content_rect(0, 0, kTileSize, kTileSize); + + SkBitmap bitmap; + bitmap.allocN32Pixels(1, 1); + SkCanvas canvas(bitmap); + + timer_.Reset(); + do { + pile->PlaybackToCanvas(&canvas, content_rect, contents_scale); + timer_.NextLap(); + } while (!timer_.HasTimeLimitExpired()); + + perf_test::PrintResult( + "raster", "", test_name, timer_.LapsPerSecond(), "runs/s", true); + } + + private: + LapTimer timer_; +}; + +TEST_F(PicturePileImplPerfTest, Analyze) { + RunAnalyzeTest("1", 1.0f); + RunAnalyzeTest("4", 0.5f); + RunAnalyzeTest("100", 0.1f); +} + +TEST_F(PicturePileImplPerfTest, Raster) { + RunRasterTest("1", 1.0f); + RunRasterTest("4", 0.5f); + RunRasterTest("100", 0.1f); +} + +} // namespace +} // namespace cc diff --git a/cc/playback/picture_pile_impl_unittest.cc b/cc/playback/picture_pile_impl_unittest.cc new file mode 100644 index 0000000..14b06ee --- /dev/null +++ b/cc/playback/picture_pile_impl_unittest.cc @@ -0,0 +1,549 @@ +// Copyright 2013 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 "base/memory/scoped_ptr.h" +#include "cc/test/fake_picture_pile_impl.h" +#include "cc/test/skia_common.h" +#include "skia/ext/refptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkPixelRef.h" +#include "third_party/skia/include/core/SkShader.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size_conversions.h" + +namespace cc { +namespace { + +TEST(PicturePileImplTest, AnalyzeIsSolidUnscaled) { + gfx::Size tile_size(100, 100); + gfx::Size layer_bounds(400, 400); + + scoped_ptr<FakePicturePile> recording_source = + FakePicturePile::CreateFilledPile(tile_size, layer_bounds); + + SkPaint solid_paint; + SkColor solid_color = SkColorSetARGB(255, 12, 23, 34); + solid_paint.setColor(solid_color); + + SkColor non_solid_color = SkColorSetARGB(128, 45, 56, 67); + SkPaint non_solid_paint; + non_solid_paint.setColor(non_solid_color); + + recording_source->add_draw_rect_with_paint(gfx::Rect(0, 0, 400, 400), + solid_paint); + recording_source->Rerecord(); + + scoped_refptr<FakePicturePileImpl> pile = + FakePicturePileImpl::CreateFromPile(recording_source.get(), nullptr); + + // Ensure everything is solid. + for (int y = 0; y <= 300; y += 100) { + for (int x = 0; x <= 300; x += 100) { + RasterSource::SolidColorAnalysis analysis; + gfx::Rect rect(x, y, 100, 100); + pile->PerformSolidColorAnalysis(rect, 1.0, &analysis); + EXPECT_TRUE(analysis.is_solid_color) << rect.ToString(); + EXPECT_EQ(analysis.solid_color, solid_color) << rect.ToString(); + } + } + + // Add one non-solid pixel and recreate the raster source. + recording_source->add_draw_rect_with_paint(gfx::Rect(50, 50, 1, 1), + non_solid_paint); + recording_source->Rerecord(); + pile = FakePicturePileImpl::CreateFromPile(recording_source.get(), nullptr); + + RasterSource::SolidColorAnalysis analysis; + pile->PerformSolidColorAnalysis(gfx::Rect(0, 0, 100, 100), 1.0, &analysis); + EXPECT_FALSE(analysis.is_solid_color); + + pile->PerformSolidColorAnalysis(gfx::Rect(100, 0, 100, 100), 1.0, &analysis); + EXPECT_TRUE(analysis.is_solid_color); + EXPECT_EQ(analysis.solid_color, solid_color); + + // Boundaries should be clipped. + analysis.is_solid_color = false; + pile->PerformSolidColorAnalysis(gfx::Rect(350, 0, 100, 100), 1.0, &analysis); + EXPECT_TRUE(analysis.is_solid_color); + EXPECT_EQ(analysis.solid_color, solid_color); + + analysis.is_solid_color = false; + pile->PerformSolidColorAnalysis(gfx::Rect(0, 350, 100, 100), 1.0, &analysis); + EXPECT_TRUE(analysis.is_solid_color); + EXPECT_EQ(analysis.solid_color, solid_color); + + analysis.is_solid_color = false; + pile->PerformSolidColorAnalysis(gfx::Rect(350, 350, 100, 100), 1.0, + &analysis); + EXPECT_TRUE(analysis.is_solid_color); + EXPECT_EQ(analysis.solid_color, solid_color); +} + +TEST(PicturePileImplTest, AnalyzeIsSolidScaled) { + gfx::Size tile_size(100, 100); + gfx::Size layer_bounds(400, 400); + + scoped_ptr<FakePicturePile> recording_source = + FakePicturePile::CreateFilledPile(tile_size, layer_bounds); + + SkColor solid_color = SkColorSetARGB(255, 12, 23, 34); + SkPaint solid_paint; + solid_paint.setColor(solid_color); + + SkColor non_solid_color = SkColorSetARGB(128, 45, 56, 67); + SkPaint non_solid_paint; + non_solid_paint.setColor(non_solid_color); + + recording_source->add_draw_rect_with_paint(gfx::Rect(0, 0, 400, 400), + solid_paint); + recording_source->Rerecord(); + + scoped_refptr<FakePicturePileImpl> pile = + FakePicturePileImpl::CreateFromPile(recording_source.get(), nullptr); + + // Ensure everything is solid. + for (int y = 0; y <= 30; y += 10) { + for (int x = 0; x <= 30; x += 10) { + RasterSource::SolidColorAnalysis analysis; + gfx::Rect rect(x, y, 10, 10); + pile->PerformSolidColorAnalysis(rect, 0.1f, &analysis); + EXPECT_TRUE(analysis.is_solid_color) << rect.ToString(); + EXPECT_EQ(analysis.solid_color, solid_color) << rect.ToString(); + } + } + + // Add one non-solid pixel and recreate the raster source. + recording_source->add_draw_rect_with_paint(gfx::Rect(50, 50, 1, 1), + non_solid_paint); + recording_source->Rerecord(); + pile = FakePicturePileImpl::CreateFromPile(recording_source.get(), nullptr); + + RasterSource::SolidColorAnalysis analysis; + pile->PerformSolidColorAnalysis(gfx::Rect(0, 0, 10, 10), 0.1f, &analysis); + EXPECT_FALSE(analysis.is_solid_color); + + pile->PerformSolidColorAnalysis(gfx::Rect(10, 0, 10, 10), 0.1f, &analysis); + EXPECT_TRUE(analysis.is_solid_color); + EXPECT_EQ(analysis.solid_color, solid_color); + + // Boundaries should be clipped. + analysis.is_solid_color = false; + pile->PerformSolidColorAnalysis(gfx::Rect(35, 0, 10, 10), 0.1f, &analysis); + EXPECT_TRUE(analysis.is_solid_color); + EXPECT_EQ(analysis.solid_color, solid_color); + + analysis.is_solid_color = false; + pile->PerformSolidColorAnalysis(gfx::Rect(0, 35, 10, 10), 0.1f, &analysis); + EXPECT_TRUE(analysis.is_solid_color); + EXPECT_EQ(analysis.solid_color, solid_color); + + analysis.is_solid_color = false; + pile->PerformSolidColorAnalysis(gfx::Rect(35, 35, 10, 10), 0.1f, &analysis); + EXPECT_TRUE(analysis.is_solid_color); + EXPECT_EQ(analysis.solid_color, solid_color); +} + +TEST(PicturePileImplTest, AnalyzeIsSolidEmpty) { + gfx::Size tile_size(100, 100); + gfx::Size layer_bounds(400, 400); + + scoped_refptr<FakePicturePileImpl> pile = + FakePicturePileImpl::CreateFilledPile(tile_size, layer_bounds); + RasterSource::SolidColorAnalysis analysis; + EXPECT_FALSE(analysis.is_solid_color); + + pile->PerformSolidColorAnalysis(gfx::Rect(0, 0, 400, 400), 1.f, &analysis); + + EXPECT_TRUE(analysis.is_solid_color); + EXPECT_EQ(analysis.solid_color, SkColorSetARGB(0, 0, 0, 0)); +} + +TEST(PicturePileImplTest, PixelRefIteratorDiscardableRefsOneTile) { + gfx::Size tile_size(256, 256); + gfx::Size layer_bounds(512, 512); + + scoped_ptr<FakePicturePile> recording_source = + FakePicturePile::CreateFilledPile(tile_size, layer_bounds); + + SkBitmap discardable_bitmap[2][2]; + CreateBitmap(gfx::Size(32, 32), "discardable", &discardable_bitmap[0][0]); + CreateBitmap(gfx::Size(32, 32), "discardable", &discardable_bitmap[0][1]); + CreateBitmap(gfx::Size(32, 32), "discardable", &discardable_bitmap[1][1]); + + // Discardable pixel refs are found in the following cells: + // |---|---| + // | x | x | + // |---|---| + // | | x | + // |---|---| + recording_source->add_draw_bitmap(discardable_bitmap[0][0], gfx::Point(0, 0)); + recording_source->add_draw_bitmap(discardable_bitmap[0][1], + gfx::Point(260, 0)); + recording_source->add_draw_bitmap(discardable_bitmap[1][1], + gfx::Point(260, 260)); + recording_source->SetGatherPixelRefs(true); + recording_source->Rerecord(); + + scoped_refptr<FakePicturePileImpl> pile = + FakePicturePileImpl::CreateFromPile(recording_source.get(), nullptr); + + // Tile sized iterators. These should find only one pixel ref. + { + PicturePileImpl::PixelRefIterator iterator( + gfx::Rect(0, 0, 256, 256), 1.0, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0][0].pixelRef()); + EXPECT_FALSE(++iterator); + } + { + PicturePileImpl::PixelRefIterator iterator( + gfx::Rect(0, 0, 512, 512), 2.0, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0][0].pixelRef()); + EXPECT_FALSE(++iterator); + } + { + PicturePileImpl::PixelRefIterator iterator( + gfx::Rect(0, 0, 128, 128), 0.5, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0][0].pixelRef()); + EXPECT_FALSE(++iterator); + } + // Shifted tile sized iterators. These should find only one pixel ref. + { + PicturePileImpl::PixelRefIterator iterator( + gfx::Rect(260, 260, 256, 256), 1.0, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1][1].pixelRef()); + EXPECT_FALSE(++iterator); + } + { + PicturePileImpl::PixelRefIterator iterator( + gfx::Rect(520, 520, 512, 512), 2.0, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1][1].pixelRef()); + EXPECT_FALSE(++iterator); + } + { + PicturePileImpl::PixelRefIterator iterator( + gfx::Rect(130, 130, 128, 128), 0.5, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1][1].pixelRef()); + EXPECT_FALSE(++iterator); + } + // Ensure there's no discardable pixel refs in the empty cell + { + PicturePileImpl::PixelRefIterator iterator( + gfx::Rect(0, 256, 256, 256), 1.0, pile.get()); + EXPECT_FALSE(iterator); + } + // Layer sized iterators. These should find three pixel ref. + { + PicturePileImpl::PixelRefIterator iterator( + gfx::Rect(0, 0, 512, 512), 1.0, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0][1].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1][1].pixelRef()); + EXPECT_FALSE(++iterator); + } + { + PicturePileImpl::PixelRefIterator iterator( + gfx::Rect(0, 0, 1024, 1024), 2.0, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0][1].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1][1].pixelRef()); + EXPECT_FALSE(++iterator); + } + { + PicturePileImpl::PixelRefIterator iterator( + gfx::Rect(0, 0, 256, 256), 0.5, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0][1].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1][1].pixelRef()); + EXPECT_FALSE(++iterator); + } + + // Copy test. + PicturePileImpl::PixelRefIterator iterator( + gfx::Rect(0, 0, 512, 512), 1.0, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0][1].pixelRef()); + + // copy now points to the same spot as iterator, + // but both can be incremented independently. + PicturePileImpl::PixelRefIterator copy = iterator; + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1][1].pixelRef()); + EXPECT_FALSE(++iterator); + + EXPECT_TRUE(copy); + EXPECT_TRUE(*copy == discardable_bitmap[0][1].pixelRef()); + EXPECT_TRUE(++copy); + EXPECT_TRUE(*copy == discardable_bitmap[1][1].pixelRef()); + EXPECT_FALSE(++copy); +} + +TEST(PicturePileImplTest, RasterFullContents) { + gfx::Size tile_size(1000, 1000); + gfx::Size layer_bounds(3, 5); + float contents_scale = 1.5f; + float raster_divisions = 2.f; + + scoped_ptr<FakePicturePile> recording_source = + FakePicturePile::CreateFilledPile(tile_size, layer_bounds); + recording_source->SetBackgroundColor(SK_ColorBLACK); + recording_source->SetIsSolidColor(false); + recording_source->SetRequiresClear(false); + recording_source->SetClearCanvasWithDebugColor(false); + + // Because the caller sets content opaque, it also promises that it + // has at least filled in layer_bounds opaquely. + SkPaint white_paint; + white_paint.setColor(SK_ColorWHITE); + recording_source->add_draw_rect_with_paint(gfx::Rect(layer_bounds), + white_paint); + + recording_source->SetMinContentsScale(contents_scale); + recording_source->Rerecord(); + + scoped_refptr<FakePicturePileImpl> pile = + FakePicturePileImpl::CreateFromPile(recording_source.get(), nullptr); + + gfx::Size content_bounds( + gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds, contents_scale))); + + // Simulate drawing into different tiles at different offsets. + int step_x = std::ceil(content_bounds.width() / raster_divisions); + int step_y = std::ceil(content_bounds.height() / raster_divisions); + for (int offset_x = 0; offset_x < content_bounds.width(); + offset_x += step_x) { + for (int offset_y = 0; offset_y < content_bounds.height(); + offset_y += step_y) { + gfx::Rect content_rect(offset_x, offset_y, step_x, step_y); + content_rect.Intersect(gfx::Rect(content_bounds)); + + // Simulate a canvas rect larger than the content rect. Every pixel + // up to one pixel outside the content rect is guaranteed to be opaque. + // Outside of that is undefined. + gfx::Rect canvas_rect(content_rect); + canvas_rect.Inset(0, 0, -1, -1); + + SkBitmap bitmap; + bitmap.allocN32Pixels(canvas_rect.width(), canvas_rect.height()); + SkCanvas canvas(bitmap); + canvas.clear(SK_ColorTRANSPARENT); + + pile->PlaybackToCanvas(&canvas, canvas_rect, contents_scale); + + SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels()); + int num_pixels = bitmap.width() * bitmap.height(); + bool all_white = true; + for (int i = 0; i < num_pixels; ++i) { + EXPECT_EQ(SkColorGetA(pixels[i]), 255u); + all_white &= (SkColorGetR(pixels[i]) == 255); + all_white &= (SkColorGetG(pixels[i]) == 255); + all_white &= (SkColorGetB(pixels[i]) == 255); + } + + // If the canvas doesn't extend past the edge of the content, + // it should be entirely white. Otherwise, the edge of the content + // will be non-white. + EXPECT_EQ(all_white, gfx::Rect(content_bounds).Contains(canvas_rect)); + } + } +} + +TEST(PicturePileImpl, RasterContentsTransparent) { + gfx::Size tile_size(1000, 1000); + gfx::Size layer_bounds(5, 3); + float contents_scale = 0.5f; + + scoped_ptr<FakePicturePile> recording_source = + FakePicturePile::CreateFilledPile(tile_size, layer_bounds); + recording_source->SetBackgroundColor(SK_ColorTRANSPARENT); + recording_source->SetRequiresClear(true); + recording_source->SetMinContentsScale(contents_scale); + recording_source->SetClearCanvasWithDebugColor(false); + recording_source->Rerecord(); + + scoped_refptr<FakePicturePileImpl> pile = + FakePicturePileImpl::CreateFromPile(recording_source.get(), nullptr); + gfx::Size content_bounds( + gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds, contents_scale))); + + gfx::Rect canvas_rect(content_bounds); + canvas_rect.Inset(0, 0, -1, -1); + + SkBitmap bitmap; + bitmap.allocN32Pixels(canvas_rect.width(), canvas_rect.height()); + SkCanvas canvas(bitmap); + + pile->PlaybackToCanvas(&canvas, canvas_rect, contents_scale); + + SkColor* pixels = reinterpret_cast<SkColor*>(bitmap.getPixels()); + int num_pixels = bitmap.width() * bitmap.height(); + for (int i = 0; i < num_pixels; ++i) { + EXPECT_EQ(SkColorGetA(pixels[i]), 0u); + } +} + +class OverlapTest : public ::testing::TestWithParam<float> { + public: + static float MinContentsScale() { return 1.f / 4.f; } +}; + +TEST_P(OverlapTest, NoOverlap) { + gfx::Size tile_size(10, 10); + gfx::Size layer_bounds(30, 30); + gfx::Size bigger_than_layer_bounds(300, 300); + float contents_scale = GetParam(); + // Pick an opaque color to not have to deal with premultiplication off-by-one. + SkColor test_color = SkColorSetARGB(255, 45, 56, 67); + + scoped_ptr<FakePicturePile> recording_source = + FakePicturePile::CreateFilledPile(tile_size, layer_bounds); + recording_source->SetBackgroundColor(SK_ColorTRANSPARENT); + recording_source->SetRequiresClear(true); + recording_source->SetMinContentsScale(MinContentsScale()); + recording_source->SetClearCanvasWithDebugColor(true); + + SkPaint color_paint; + color_paint.setColor(test_color); + // Additive paint, so that if two paints overlap, the color will change. + color_paint.setXfermodeMode(SkXfermode::kPlus_Mode); + // Paint outside the layer to make sure that blending works. + recording_source->add_draw_rect_with_paint( + gfx::RectF(bigger_than_layer_bounds), color_paint); + recording_source->Rerecord(); + + scoped_refptr<FakePicturePileImpl> pile = + FakePicturePileImpl::CreateFromPile(recording_source.get(), nullptr); + gfx::Size content_bounds( + gfx::ToCeiledSize(gfx::ScaleSize(layer_bounds, contents_scale))); + + SkBitmap bitmap; + bitmap.allocN32Pixels(content_bounds.width(), content_bounds.height()); + SkCanvas canvas(bitmap); + + pile->PlaybackToCanvas(&canvas, gfx::Rect(content_bounds), contents_scale); + + for (int y = 0; y < bitmap.height(); y++) { + for (int x = 0; x < bitmap.width(); x++) { + SkColor color = bitmap.getColor(x, y); + EXPECT_EQ(SkColorGetR(test_color), SkColorGetR(color)) << "x: " << x + << ", y: " << y; + EXPECT_EQ(SkColorGetG(test_color), SkColorGetG(color)) << "x: " << x + << ", y: " << y; + EXPECT_EQ(SkColorGetB(test_color), SkColorGetB(color)) << "x: " << x + << ", y: " << y; + EXPECT_EQ(SkColorGetA(test_color), SkColorGetA(color)) << "x: " << x + << ", y: " << y; + if (test_color != color) + break; + } + } +} + +INSTANTIATE_TEST_CASE_P(PicturePileImpl, + OverlapTest, + ::testing::Values(1.f, 0.873f, 1.f / 4.f, 4.f)); + +TEST(PicturePileImplTest, PixelRefIteratorBorders) { + // 3 tile width / 1 tile height pile + gfx::Size tile_size(128, 128); + gfx::Size layer_bounds(320, 128); + + // Fake picture pile uses a tile grid the size of the tile. So, + // any iteration that intersects with a tile will return all pixel refs + // inside of it. + scoped_ptr<FakePicturePile> recording_source = + FakePicturePile::CreateFilledPile(tile_size, layer_bounds); + recording_source->SetMinContentsScale(0.5f); + + // Bitmaps 0-2 are exactly on tiles 0-2, so that they overlap the borders + // of adjacent tiles. + gfx::Rect bitmap_rects[] = { + recording_source->tiling().TileBounds(0, 0), + recording_source->tiling().TileBounds(1, 0), + recording_source->tiling().TileBounds(2, 0), + }; + SkBitmap discardable_bitmap[arraysize(bitmap_rects)]; + + for (size_t i = 0; i < arraysize(bitmap_rects); ++i) { + CreateBitmap(bitmap_rects[i].size(), "discardable", &discardable_bitmap[i]); + recording_source->add_draw_bitmap(discardable_bitmap[i], + bitmap_rects[i].origin()); + } + + recording_source->SetGatherPixelRefs(true); + recording_source->Rerecord(); + + scoped_refptr<FakePicturePileImpl> pile = + FakePicturePileImpl::CreateFromPile(recording_source.get(), nullptr); + + // Sanity check that bitmaps 0-2 intersect the borders of their adjacent + // tiles, but not the actual tiles. + EXPECT_TRUE( + bitmap_rects[0].Intersects(pile->tiling().TileBoundsWithBorder(1, 0))); + EXPECT_FALSE(bitmap_rects[0].Intersects(pile->tiling().TileBounds(1, 0))); + EXPECT_TRUE( + bitmap_rects[1].Intersects(pile->tiling().TileBoundsWithBorder(0, 0))); + EXPECT_FALSE(bitmap_rects[1].Intersects(pile->tiling().TileBounds(0, 0))); + EXPECT_TRUE( + bitmap_rects[1].Intersects(pile->tiling().TileBoundsWithBorder(2, 0))); + EXPECT_FALSE(bitmap_rects[1].Intersects(pile->tiling().TileBounds(2, 0))); + EXPECT_TRUE( + bitmap_rects[2].Intersects(pile->tiling().TileBoundsWithBorder(1, 0))); + EXPECT_FALSE(bitmap_rects[2].Intersects(pile->tiling().TileBounds(1, 0))); + + // Tile-sized iterators. + { + // Because tile 0's borders extend onto tile 1, it will include both + // bitmap 0 and 1. However, it should *not* include bitmap 2. + PicturePileImpl::PixelRefIterator iterator( + pile->tiling().TileBounds(0, 0), 1.f, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1].pixelRef()); + EXPECT_FALSE(++iterator); + } + { + // Tile 1 + borders hits all bitmaps. + PicturePileImpl::PixelRefIterator iterator( + pile->tiling().TileBounds(1, 0), 1.f, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[0].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[2].pixelRef()); + EXPECT_FALSE(++iterator); + } + { + // Tile 2 should not include bitmap 0, which is only on tile 0 and the + // borders of tile 1. + PicturePileImpl::PixelRefIterator iterator( + pile->tiling().TileBounds(2, 0), 1.f, pile.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[2].pixelRef()); + EXPECT_FALSE(++iterator); + } +} + +} // namespace +} // namespace cc diff --git a/cc/playback/picture_pile_unittest.cc b/cc/playback/picture_pile_unittest.cc new file mode 100644 index 0000000..f929aef --- /dev/null +++ b/cc/playback/picture_pile_unittest.cc @@ -0,0 +1,1345 @@ +// Copyright 2013 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 <map> +#include <utility> + +#include "cc/playback/picture_pile.h" +#include "cc/test/fake_content_layer_client.h" +#include "cc/test/fake_picture_pile.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/geometry/size_conversions.h" + +namespace cc { +namespace { + +class PicturePileTestBase { + public: + PicturePileTestBase() + : min_scale_(0.125), + pile_(min_scale_, gfx::Size(1000, 1000)), + frame_number_(0) {} + + void InitializeData() { + pile_.SetTileGridSize(gfx::Size(1000, 1000)); + pile_.SetMinContentsScale(min_scale_); + client_ = FakeContentLayerClient(); + SetTilingSize(pile_.tiling().max_texture_size()); + } + + void SetTilingSize(const gfx::Size& tiling_size) { + Region invalidation; + gfx::Rect viewport_rect(tiling_size); + UpdateAndExpandInvalidation(&invalidation, tiling_size, viewport_rect); + } + + gfx::Size tiling_size() const { return pile_.GetSize(); } + gfx::Rect tiling_rect() const { return gfx::Rect(pile_.GetSize()); } + + bool UpdateAndExpandInvalidation(Region* invalidation, + const gfx::Size& layer_size, + const gfx::Rect& visible_layer_rect) { + frame_number_++; + return pile_.UpdateAndExpandInvalidation(&client_, invalidation, layer_size, + visible_layer_rect, frame_number_, + RecordingSource::RECORD_NORMALLY); + } + + bool UpdateWholePile() { + Region invalidation = tiling_rect(); + bool result = UpdateAndExpandInvalidation(&invalidation, tiling_size(), + tiling_rect()); + EXPECT_EQ(tiling_rect().ToString(), invalidation.ToString()); + return result; + } + + FakeContentLayerClient client_; + float min_scale_; + FakePicturePile pile_; + int frame_number_; +}; + +class PicturePileTest : public PicturePileTestBase, public testing::Test { + public: + void SetUp() override { InitializeData(); } +}; + +TEST_F(PicturePileTest, InvalidationOnTileBorderOutsideInterestRect) { + // Don't expand the interest rect past what we invalidate. + pile_.SetPixelRecordDistance(0); + + gfx::Size tile_size(100, 100); + pile_.tiling().SetMaxTextureSize(tile_size); + + gfx::Size pile_size(400, 400); + SetTilingSize(pile_size); + + // We have multiple tiles. + EXPECT_GT(pile_.tiling().num_tiles_x(), 2); + EXPECT_GT(pile_.tiling().num_tiles_y(), 2); + + // Record everything. + Region invalidation(tiling_rect()); + UpdateAndExpandInvalidation(&invalidation, tiling_size(), tiling_rect()); + + // +----------+-----------------+-----------+ + // | | VVVV 1,0| | + // | | VVVV | | + // | | VVVV | | + // | ...|.................|... | + // | ...|.................|... | + // +----------+-----------------+-----------+ + // | ...| |... | + // | ...| |... | + // | ...| |... | + // | ...| |... | + // | ...| 1,1|... | + // +----------+-----------------+-----------+ + // | ...|.................|... | + // | ...|.................|... | + // +----------+-----------------+-----------+ + // + // .. = border pixels for tile 1,1 + // VV = interest rect (what we will record) + // + // The first invalidation is inside VV, so it does not touch border pixels of + // tile 1,1. + // + // The second invalidation goes below VV into the .. border pixels of 1,1. + + // This is the VV interest rect which will be entirely inside 1,0 and not + // touch the border of 1,1. + gfx::Rect interest_rect( + pile_.tiling().TilePositionX(1) + pile_.tiling().border_texels(), + 0, + 10, + pile_.tiling().TileSizeY(0) - pile_.tiling().border_texels()); + + // Invalidate tile 1,0 only. This is a rect that avoids the borders of any + // other tiles. + gfx::Rect invalidate_tile = interest_rect; + // This should cause the tile 1,0 to be invalidated and re-recorded. The + // invalidation did not need to be expanded. + invalidation = invalidate_tile; + UpdateAndExpandInvalidation(&invalidation, tiling_size(), interest_rect); + EXPECT_EQ(invalidate_tile, invalidation); + + // Invalidate tile 1,0 and 1,1 by invalidating something that only touches the + // border of 1,1 (and is inside the tile bounds of 1,0). This is a 10px wide + // strip from the top of the tiling onto the border pixels of tile 1,1 that + // avoids border pixels of any other tiles. + gfx::Rect invalidate_border = interest_rect; + invalidate_border.Inset(0, 0, 0, -1); + // This should cause the tile 1,0 and 1,1 to be invalidated. The 1,1 tile will + // not be re-recorded since it does not touch the interest rect, so the + // invalidation should be expanded to cover all of 1,1. + invalidation = invalidate_border; + UpdateAndExpandInvalidation(&invalidation, tiling_size(), interest_rect); + Region expected_invalidation = invalidate_border; + expected_invalidation.Union(pile_.tiling().TileBounds(1, 1)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); +} + +TEST_F(PicturePileTest, SmallInvalidateInflated) { + // Invalidate something inside a tile. + Region invalidate_rect(gfx::Rect(50, 50, 1, 1)); + UpdateAndExpandInvalidation(&invalidate_rect, tiling_size(), tiling_rect()); + EXPECT_EQ(gfx::Rect(50, 50, 1, 1).ToString(), invalidate_rect.ToString()); + + EXPECT_EQ(1, pile_.tiling().num_tiles_x()); + EXPECT_EQ(1, pile_.tiling().num_tiles_y()); + + PicturePile::PictureMapKey key = FakePicturePile::PictureMapKey(0, 0); + PicturePile::PictureMap::iterator it = pile_.picture_map().find(key); + EXPECT_TRUE(it != pile_.picture_map().end()); + const Picture* picture = it->second.get(); + EXPECT_TRUE(picture); + + gfx::Rect picture_rect = + gfx::ScaleToEnclosedRect(picture->LayerRect(), min_scale_); + + // The the picture should be large enough that scaling it never makes a rect + // smaller than 1 px wide or tall. + EXPECT_FALSE(picture_rect.IsEmpty()) << "Picture rect " + << picture_rect.ToString(); +} + +TEST_F(PicturePileTest, LargeInvalidateInflated) { + // Invalidate something inside a tile. + Region invalidate_rect(gfx::Rect(50, 50, 100, 100)); + UpdateAndExpandInvalidation(&invalidate_rect, tiling_size(), tiling_rect()); + EXPECT_EQ(gfx::Rect(50, 50, 100, 100).ToString(), invalidate_rect.ToString()); + + EXPECT_EQ(1, pile_.tiling().num_tiles_x()); + EXPECT_EQ(1, pile_.tiling().num_tiles_y()); + + PicturePile::PictureMapKey key = FakePicturePile::PictureMapKey(0, 0); + PicturePile::PictureMap::iterator it = pile_.picture_map().find(key); + EXPECT_TRUE(it != pile_.picture_map().end()); + const Picture* picture = it->second.get(); + EXPECT_TRUE(picture); + + int expected_inflation = pile_.buffer_pixels(); + + gfx::Rect base_picture_rect(tiling_size()); + base_picture_rect.Inset(-expected_inflation, -expected_inflation); + EXPECT_EQ(base_picture_rect.ToString(), picture->LayerRect().ToString()); +} + +TEST_F(PicturePileTest, ClearingInvalidatesRecordedRect) { + gfx::Rect rect(0, 0, 5, 5); + EXPECT_TRUE(pile_.CanRasterLayerRect(rect)); + EXPECT_TRUE(pile_.CanRasterSlowTileCheck(rect)); + + pile_.Clear(); + + // Make sure both the cache-aware check (using recorded region) and the normal + // check are both false after clearing. + EXPECT_FALSE(pile_.CanRasterLayerRect(rect)); + EXPECT_FALSE(pile_.CanRasterSlowTileCheck(rect)); +} + +TEST_F(PicturePileTest, NoInvalidationValidViewport) { + // This test validates that the recorded_viewport cache of full tiles + // is still valid for some use cases. If it's not, it's a performance + // issue because CanRaster checks will go down the slow path. + EXPECT_TRUE(!pile_.recorded_viewport().IsEmpty()); + + // No invalidation, same viewport. + Region invalidation; + UpdateAndExpandInvalidation(&invalidation, tiling_size(), tiling_rect()); + EXPECT_TRUE(!pile_.recorded_viewport().IsEmpty()); + EXPECT_EQ(Region().ToString(), invalidation.ToString()); + + // Partial invalidation, same viewport. + invalidation = gfx::Rect(0, 0, 1, 1); + UpdateAndExpandInvalidation(&invalidation, tiling_size(), tiling_rect()); + EXPECT_TRUE(!pile_.recorded_viewport().IsEmpty()); + EXPECT_EQ(gfx::Rect(0, 0, 1, 1).ToString(), invalidation.ToString()); + + // No invalidation, changing viewport. + invalidation = Region(); + UpdateAndExpandInvalidation(&invalidation, tiling_size(), + gfx::Rect(5, 5, 5, 5)); + EXPECT_TRUE(!pile_.recorded_viewport().IsEmpty()); + EXPECT_EQ(Region().ToString(), invalidation.ToString()); +} + +TEST_F(PicturePileTest, BigFullLayerInvalidation) { + gfx::Size huge_layer_size(100000000, 100000000); + gfx::Rect viewport(300000, 400000, 5000, 6000); + + // Resize the pile. + Region invalidation; + UpdateAndExpandInvalidation(&invalidation, huge_layer_size, viewport); + + // Invalidating a huge layer should be fast. + base::TimeTicks start = base::TimeTicks::Now(); + invalidation = gfx::Rect(huge_layer_size); + UpdateAndExpandInvalidation(&invalidation, huge_layer_size, viewport); + base::TimeTicks end = base::TimeTicks::Now(); + base::TimeDelta length = end - start; + // This is verrrry generous to avoid flake. + EXPECT_LT(length.InSeconds(), 5); +} + +TEST_F(PicturePileTest, BigFullLayerInvalidationWithResizeGrow) { + gfx::Size huge_layer_size(100000000, 100000000); + gfx::Rect viewport(300000, 400000, 5000, 6000); + + // Resize the pile. + Region invalidation; + UpdateAndExpandInvalidation(&invalidation, huge_layer_size, viewport); + + // Resize the pile even larger, while invalidating everything in the old size. + // Invalidating the whole thing should be fast. + base::TimeTicks start = base::TimeTicks::Now(); + gfx::Size bigger_layer_size(huge_layer_size.width() * 2, + huge_layer_size.height() * 2); + invalidation = gfx::Rect(huge_layer_size); + UpdateAndExpandInvalidation(&invalidation, bigger_layer_size, viewport); + base::TimeTicks end = base::TimeTicks::Now(); + base::TimeDelta length = end - start; + // This is verrrry generous to avoid flake. + EXPECT_LT(length.InSeconds(), 5); +} + +TEST_F(PicturePileTest, BigFullLayerInvalidationWithResizeShrink) { + gfx::Size huge_layer_size(100000000, 100000000); + gfx::Rect viewport(300000, 400000, 5000, 6000); + + // Resize the pile. + Region invalidation; + UpdateAndExpandInvalidation(&invalidation, huge_layer_size, viewport); + + // Resize the pile smaller, while invalidating everything in the new size. + // Invalidating the whole thing should be fast. + base::TimeTicks start = base::TimeTicks::Now(); + gfx::Size smaller_layer_size(huge_layer_size.width() - 1000, + huge_layer_size.height() - 1000); + invalidation = gfx::Rect(smaller_layer_size); + UpdateAndExpandInvalidation(&invalidation, smaller_layer_size, viewport); + base::TimeTicks end = base::TimeTicks::Now(); + base::TimeDelta length = end - start; + // This is verrrry generous to avoid flake. + EXPECT_LT(length.InSeconds(), 5); +} + +TEST_F(PicturePileTest, InvalidationOutsideRecordingRect) { + gfx::Size huge_layer_size(10000000, 20000000); + gfx::Rect viewport(300000, 400000, 5000, 6000); + + // Resize the pile and set up the interest rect. + Region invalidation; + UpdateAndExpandInvalidation(&invalidation, huge_layer_size, viewport); + + // Invalidation inside the recording rect does not need to be expanded. + invalidation = viewport; + UpdateAndExpandInvalidation(&invalidation, huge_layer_size, viewport); + EXPECT_EQ(viewport.ToString(), invalidation.ToString()); + + // Invalidation outside the recording rect should expand to the tiles it + // covers. + gfx::Rect recorded_over_tiles = + pile_.tiling().ExpandRectToTileBounds(pile_.recorded_viewport()); + gfx::Rect invalidation_outside( + recorded_over_tiles.right(), recorded_over_tiles.y(), 30, 30); + invalidation = invalidation_outside; + UpdateAndExpandInvalidation(&invalidation, huge_layer_size, viewport); + gfx::Rect expanded_recorded_viewport = + pile_.tiling().ExpandRectToTileBounds(pile_.recorded_viewport()); + Region expected_invalidation = + pile_.tiling().ExpandRectToTileBounds(invalidation_outside); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); +} + +enum Corner { + TOP_LEFT, + TOP_RIGHT, + BOTTOM_LEFT, + BOTTOM_RIGHT, +}; + +class PicturePileResizeCornerTest : public PicturePileTestBase, + public testing::TestWithParam<Corner> { + protected: + void SetUp() override { InitializeData(); } + + static gfx::Rect CornerSinglePixelRect(Corner corner, const gfx::Size& s) { + switch (corner) { + case TOP_LEFT: + return gfx::Rect(0, 0, 1, 1); + case TOP_RIGHT: + return gfx::Rect(s.width() - 1, 0, 1, 1); + case BOTTOM_LEFT: + return gfx::Rect(0, s.height() - 1, 1, 1); + case BOTTOM_RIGHT: + return gfx::Rect(s.width() - 1, s.height() - 1, 1, 1); + } + NOTREACHED(); + return gfx::Rect(); + } +}; + +TEST_P(PicturePileResizeCornerTest, ResizePileOutsideInterestRect) { + Corner corner = GetParam(); + + // This size chosen to be larger than the interest rect size, which is + // at least kPixelDistanceToRecord * 2 in each dimension. + int tile_size = 100000; + // The small number subtracted keeps the last tile in each axis larger than + // the interest rect also. + int offset = -100; + gfx::Size base_tiling_size(6 * tile_size + offset, 6 * tile_size + offset); + gfx::Size grow_down_tiling_size(6 * tile_size + offset, + 8 * tile_size + offset); + gfx::Size grow_right_tiling_size(8 * tile_size + offset, + 6 * tile_size + offset); + gfx::Size grow_both_tiling_size(8 * tile_size + offset, + 8 * tile_size + offset); + + Region invalidation; + Region expected_invalidation; + + pile_.tiling().SetMaxTextureSize(gfx::Size(tile_size, tile_size)); + SetTilingSize(base_tiling_size); + + // We should have a recording for every tile. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + UpdateAndExpandInvalidation( + &invalidation, + grow_down_tiling_size, + CornerSinglePixelRect(corner, grow_down_tiling_size)); + + // We should have lost all of the recordings in the bottom row as none of them + // are in the current interest rect (which is either the above or below it). + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(8, pile_.tiling().num_tiles_y()); + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 6; ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_EQ(j < 5, it != map.end() && it->second.get()); + } + } + + // We invalidated all new pixels in the recording. + expected_invalidation = SubtractRegions(gfx::Rect(grow_down_tiling_size), + gfx::Rect(base_tiling_size)); + // But the new pixels don't cover the whole bottom row. + gfx::Rect bottom_row = gfx::UnionRects(pile_.tiling().TileBounds(0, 5), + pile_.tiling().TileBounds(5, 5)); + EXPECT_FALSE(expected_invalidation.Contains(bottom_row)); + // We invalidated the entire old bottom row. + expected_invalidation.Union(bottom_row); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, + base_tiling_size, + CornerSinglePixelRect(corner, base_tiling_size)); + + // When shrinking, we should have lost all the recordings in the bottom row + // not touching the interest rect. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + bool expect_tile; + switch (corner) { + case TOP_LEFT: + case TOP_RIGHT: + expect_tile = j < 5; + break; + case BOTTOM_LEFT: + // The interest rect in the bottom left tile means we'll record it. + expect_tile = j < 5 || (j == 5 && i == 0); + break; + case BOTTOM_RIGHT: + // The interest rect in the bottom right tile means we'll record it. + expect_tile = j < 5 || (j == 5 && i == 5); + break; + } + EXPECT_EQ(expect_tile, it != map.end() && it->second.get()); + } + } + + // When shrinking, the previously exposed region is invalidated. + expected_invalidation = SubtractRegions(gfx::Rect(grow_down_tiling_size), + gfx::Rect(base_tiling_size)); + // The whole bottom row of tiles (except any with the interest rect) are + // dropped. + gfx::Rect bottom_row_minus_existing_corner = gfx::UnionRects( + pile_.tiling().TileBounds(0, 5), pile_.tiling().TileBounds(5, 5)); + switch (corner) { + case TOP_LEFT: + case TOP_RIGHT: + // No tiles are kept in the changed region because it doesn't + // intersect with the interest rect. + break; + case BOTTOM_LEFT: + bottom_row_minus_existing_corner.Subtract( + pile_.tiling().TileBounds(0, 5)); + break; + case BOTTOM_RIGHT: + bottom_row_minus_existing_corner.Subtract( + pile_.tiling().TileBounds(5, 5)); + break; + } + + expected_invalidation.Union(bottom_row_minus_existing_corner); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation( + &invalidation, + grow_right_tiling_size, + CornerSinglePixelRect(corner, grow_right_tiling_size)); + + // We should have lost all of the recordings in the right column as none of + // them are in the current interest rect (which is either entirely left or + // right of it). + EXPECT_EQ(8, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 6; ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_EQ(i < 5, it != map.end() && it->second.get()); + } + } + + // We invalidated all new pixels in the recording. + expected_invalidation = SubtractRegions(gfx::Rect(grow_right_tiling_size), + gfx::Rect(base_tiling_size)); + // But the new pixels don't cover the whole right_column. + gfx::Rect right_column = gfx::UnionRects(pile_.tiling().TileBounds(5, 0), + pile_.tiling().TileBounds(5, 5)); + EXPECT_FALSE(expected_invalidation.Contains(right_column)); + // We invalidated the entire old right column. + expected_invalidation.Union(right_column); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, + base_tiling_size, + CornerSinglePixelRect(corner, base_tiling_size)); + + // When shrinking, we should have lost all the recordings in the right column + // not touching the interest rect. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + bool expect_tile; + switch (corner) { + case TOP_LEFT: + case BOTTOM_LEFT: + // No tiles are kept in the changed region because it doesn't + // intersect with the interest rect. + expect_tile = i < 5; + break; + case TOP_RIGHT: + // The interest rect in the top right tile means we'll record it. + expect_tile = i < 5 || (j == 0 && i == 5); + break; + case BOTTOM_RIGHT: + // The interest rect in the bottom right tile means we'll record it. + expect_tile = i < 5 || (j == 5 && i == 5); + break; + } + EXPECT_EQ(expect_tile, it != map.end() && it->second.get()); + } + } + + // When shrinking, the previously exposed region is invalidated. + expected_invalidation = SubtractRegions(gfx::Rect(grow_right_tiling_size), + gfx::Rect(base_tiling_size)); + // The whole right column of tiles (except for ones with the interest rect) + // are dropped. + gfx::Rect right_column_minus_existing_corner = gfx::UnionRects( + pile_.tiling().TileBounds(5, 0), pile_.tiling().TileBounds(5, 5)); + switch (corner) { + case TOP_LEFT: + case BOTTOM_LEFT: + break; + case TOP_RIGHT: + right_column_minus_existing_corner.Subtract( + pile_.tiling().TileBounds(5, 0)); + break; + case BOTTOM_RIGHT: + right_column_minus_existing_corner.Subtract( + pile_.tiling().TileBounds(5, 5)); + break; + } + expected_invalidation.Union(right_column_minus_existing_corner); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation( + &invalidation, + grow_both_tiling_size, + CornerSinglePixelRect(corner, grow_both_tiling_size)); + + // We should have lost the recordings in the right column and bottom row. + EXPECT_EQ(8, pile_.tiling().num_tiles_x()); + EXPECT_EQ(8, pile_.tiling().num_tiles_y()); + for (int i = 0; i < 6; ++i) { + for (int j = 0; j < 6; ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_EQ(i < 5 && j < 5, it != map.end() && it->second.get()); + } + } + + // We invalidated all new pixels in the recording. + expected_invalidation = SubtractRegions(gfx::Rect(grow_both_tiling_size), + gfx::Rect(base_tiling_size)); + // But the new pixels don't cover the whole right column or bottom row. + Region right_column_and_bottom_row = + UnionRegions(gfx::UnionRects(pile_.tiling().TileBounds(5, 0), + pile_.tiling().TileBounds(5, 5)), + gfx::UnionRects(pile_.tiling().TileBounds(0, 5), + pile_.tiling().TileBounds(5, 5))); + EXPECT_FALSE(expected_invalidation.Contains(right_column_and_bottom_row)); + // We invalidated the entire old right column and the old bottom row. + expected_invalidation.Union(right_column_and_bottom_row); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, base_tiling_size, + CornerSinglePixelRect(corner, base_tiling_size)); + + // We should have lost the recordings in the right column and bottom row, + // except where it intersects the interest rect. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + bool expect_tile; + switch (corner) { + case TOP_LEFT: + expect_tile = i < 5 && j < 5; + break; + case TOP_RIGHT: + // The interest rect in the top right tile means we'll record it. + expect_tile = (i < 5 && j < 5) || (j == 0 && i == 5); + break; + case BOTTOM_LEFT: + // The interest rect in the bottom left tile means we'll record it. + expect_tile = (i < 5 && j < 5) || (j == 5 && i == 0); + break; + case BOTTOM_RIGHT: + // The interest rect in the bottom right tile means we'll record it. + expect_tile = (i < 5 && j < 5) || (j == 5 && i == 5); + break; + } + EXPECT_EQ(expect_tile, it != map.end() && it->second.get()) << i << "," + << j; + } + } + + // We invalidated all previous pixels in the recording. + expected_invalidation = SubtractRegions(gfx::Rect(grow_both_tiling_size), + gfx::Rect(base_tiling_size)); + // The whole right column and bottom row of tiles (except for ones with the + // interest rect) are dropped. + Region right_column_and_bottom_row_minus_existing_corner = + right_column_and_bottom_row; + switch (corner) { + case TOP_LEFT: + break; + case BOTTOM_LEFT: + right_column_and_bottom_row_minus_existing_corner.Subtract( + pile_.tiling().TileBounds(0, 5)); + break; + case TOP_RIGHT: + right_column_and_bottom_row_minus_existing_corner.Subtract( + pile_.tiling().TileBounds(5, 0)); + break; + case BOTTOM_RIGHT: + right_column_and_bottom_row_minus_existing_corner.Subtract( + pile_.tiling().TileBounds(5, 5)); + break; + } + expected_invalidation.Union( + right_column_and_bottom_row_minus_existing_corner); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); +} + +TEST_P(PicturePileResizeCornerTest, SmallResizePileOutsideInterestRect) { + Corner corner = GetParam(); + + // This size chosen to be larger than the interest rect size, which is + // at least kPixelDistanceToRecord * 2 in each dimension. + int tile_size = 100000; + // The small number subtracted keeps the last tile in each axis larger than + // the interest rect also. + int offset = -100; + gfx::Size base_tiling_size(6 * tile_size + offset, 6 * tile_size + offset); + gfx::Size grow_down_tiling_size(6 * tile_size + offset, + 6 * tile_size + offset + 5); + gfx::Size grow_right_tiling_size(6 * tile_size + offset + 5, + 6 * tile_size + offset); + gfx::Size grow_both_tiling_size(6 * tile_size + offset + 5, + 6 * tile_size + offset + 5); + + Region invalidation; + Region expected_invalidation; + + pile_.tiling().SetMaxTextureSize(gfx::Size(tile_size, tile_size)); + SetTilingSize(base_tiling_size); + + // We should have a recording for every tile. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // In this test (unlike the large resize test), as all growing and shrinking + // happens within tiles, the resulting invalidation is symmetrical, so use + // this enum to repeat the test both ways. + enum ChangeDirection { GROW, SHRINK, LAST_DIRECTION = SHRINK }; + + // Grow downward. + for (int dir = 0; dir <= LAST_DIRECTION; ++dir) { + gfx::Size new_tiling_size = + dir == GROW ? grow_down_tiling_size : base_tiling_size; + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, new_tiling_size, + CornerSinglePixelRect(corner, new_tiling_size)); + + // We should have lost the recordings in the bottom row that do not + // intersect the interest rect. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + bool expect_tile; + switch (corner) { + case TOP_LEFT: + case TOP_RIGHT: + expect_tile = j < 5; + break; + case BOTTOM_LEFT: + // The interest rect in the bottom left tile means we'll record it. + expect_tile = j < 5 || (j == 5 && i == 0); + break; + case BOTTOM_RIGHT: + // The interest rect in the bottom right tile means we'll record it. + expect_tile = j < 5 || (j == 5 && i == 5); + break; + } + EXPECT_EQ(expect_tile, it != map.end() && it->second.get()); + } + } + + // We invalidated the bottom row outside the new interest rect. The tile + // that insects the interest rect in invalidated only on its newly + // exposed or previously exposed pixels. + if (dir == GROW) { + // Only calculate the expected invalidation while growing, as the tile + // bounds post-growing is the newly exposed / previously exposed sizes. + // Post-shrinking, the tile bounds are smaller, so can't be used. + switch (corner) { + case TOP_LEFT: + case TOP_RIGHT: + expected_invalidation = gfx::UnionRects( + pile_.tiling().TileBounds(0, 5), pile_.tiling().TileBounds(5, 5)); + break; + case BOTTOM_LEFT: + expected_invalidation = gfx::UnionRects( + pile_.tiling().TileBounds(1, 5), pile_.tiling().TileBounds(5, 5)); + expected_invalidation.Union(SubtractRects( + pile_.tiling().TileBounds(0, 5), gfx::Rect(base_tiling_size))); + break; + case BOTTOM_RIGHT: + expected_invalidation = gfx::UnionRects( + pile_.tiling().TileBounds(0, 5), pile_.tiling().TileBounds(4, 5)); + expected_invalidation.Union(SubtractRects( + pile_.tiling().TileBounds(5, 5), gfx::Rect(base_tiling_size))); + break; + } + } + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + } + + // Grow right. + for (int dir = 0; dir <= LAST_DIRECTION; ++dir) { + gfx::Size new_tiling_size = + dir == GROW ? grow_right_tiling_size : base_tiling_size; + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, new_tiling_size, + CornerSinglePixelRect(corner, new_tiling_size)); + + // We should have lost the recordings in the right column. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + bool expect_tile; + switch (corner) { + case TOP_LEFT: + case BOTTOM_LEFT: + expect_tile = i < 5; + break; + case TOP_RIGHT: + // The interest rect in the top right tile means we'll record it. + expect_tile = i < 5 || (j == 0 && i == 5); + break; + case BOTTOM_RIGHT: + // The interest rect in the bottom right tile means we'll record it. + expect_tile = i < 5 || (j == 5 && i == 5); + break; + } + EXPECT_EQ(expect_tile, it != map.end() && it->second.get()); + } + } + + // We invalidated the right column outside the new interest rect. The tile + // that insects the interest rect in invalidated only on its new or + // previously exposed pixels. + if (dir == GROW) { + // Calculate the expected invalidation the first time through the loop. + switch (corner) { + case TOP_LEFT: + case BOTTOM_LEFT: + expected_invalidation = gfx::UnionRects( + pile_.tiling().TileBounds(5, 0), pile_.tiling().TileBounds(5, 5)); + break; + case TOP_RIGHT: + expected_invalidation = gfx::UnionRects( + pile_.tiling().TileBounds(5, 1), pile_.tiling().TileBounds(5, 5)); + expected_invalidation.Union(SubtractRects( + pile_.tiling().TileBounds(5, 0), gfx::Rect(base_tiling_size))); + break; + case BOTTOM_RIGHT: + expected_invalidation = gfx::UnionRects( + pile_.tiling().TileBounds(5, 0), pile_.tiling().TileBounds(5, 4)); + expected_invalidation.Union(SubtractRects( + pile_.tiling().TileBounds(5, 5), gfx::Rect(base_tiling_size))); + break; + } + } + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + } + + // Grow both. + for (int dir = 0; dir <= LAST_DIRECTION; ++dir) { + gfx::Size new_tiling_size = + dir == GROW ? grow_both_tiling_size : base_tiling_size; + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, new_tiling_size, + CornerSinglePixelRect(corner, new_tiling_size)); + + // We should have lost the recordings in the right column and bottom row. + // The tile that insects the interest rect in invalidated only on its new + // or previously exposed pixels. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + bool expect_tile; + switch (corner) { + case TOP_LEFT: + expect_tile = i < 5 && j < 5; + break; + case TOP_RIGHT: + // The interest rect in the top right tile means we'll record it. + expect_tile = (i < 5 && j < 5) || (j == 0 && i == 5); + break; + case BOTTOM_LEFT: + // The interest rect in the bottom left tile means we'll record it. + expect_tile = (i < 5 && j < 5) || (j == 5 && i == 0); + break; + case BOTTOM_RIGHT: + // The interest rect in the bottom right tile means we'll record it. + expect_tile = (i < 5 && j < 5) || (j == 5 && i == 5); + break; + } + EXPECT_EQ(expect_tile, it != map.end() && it->second.get()) << i << "," + << j; + } + } + + // We invalidated the right column and the bottom row outside the new + // interest rect. The tile that insects the interest rect in invalidated + // only on its new or previous exposed pixels. + if (dir == GROW) { + // Calculate the expected invalidation the first time through the loop. + switch (corner) { + case TOP_LEFT: + expected_invalidation = gfx::UnionRects( + pile_.tiling().TileBounds(5, 0), pile_.tiling().TileBounds(5, 5)); + expected_invalidation.Union( + gfx::UnionRects(pile_.tiling().TileBounds(0, 5), + pile_.tiling().TileBounds(5, 5))); + break; + case TOP_RIGHT: + expected_invalidation = gfx::UnionRects( + pile_.tiling().TileBounds(5, 1), pile_.tiling().TileBounds(5, 5)); + expected_invalidation.Union( + gfx::UnionRects(pile_.tiling().TileBounds(0, 5), + pile_.tiling().TileBounds(5, 5))); + expected_invalidation.Union(SubtractRects( + pile_.tiling().TileBounds(5, 0), gfx::Rect(base_tiling_size))); + break; + case BOTTOM_LEFT: + expected_invalidation = gfx::UnionRects( + pile_.tiling().TileBounds(5, 0), pile_.tiling().TileBounds(5, 5)); + expected_invalidation.Union( + gfx::UnionRects(pile_.tiling().TileBounds(1, 5), + pile_.tiling().TileBounds(5, 5))); + expected_invalidation.Union(SubtractRects( + pile_.tiling().TileBounds(0, 5), gfx::Rect(base_tiling_size))); + break; + case BOTTOM_RIGHT: + expected_invalidation = gfx::UnionRects( + pile_.tiling().TileBounds(5, 0), pile_.tiling().TileBounds(5, 4)); + expected_invalidation.Union( + gfx::UnionRects(pile_.tiling().TileBounds(0, 5), + pile_.tiling().TileBounds(4, 5))); + expected_invalidation.Union(SubtractRegions( + pile_.tiling().TileBounds(5, 5), gfx::Rect(base_tiling_size))); + break; + } + } + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + } +} + +INSTANTIATE_TEST_CASE_P( + PicturePileResizeCornerTests, + PicturePileResizeCornerTest, + ::testing::Values(TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT)); + +TEST_F(PicturePileTest, ResizePileInsideInterestRect) { + // This size chosen to be small enough that all the rects below fit inside the + // the interest rect, so they are smaller than kPixelDistanceToRecord in each + // dimension. + int tile_size = 100; + gfx::Size base_tiling_size(5 * tile_size, 5 * tile_size); + gfx::Size grow_down_tiling_size(5 * tile_size, 7 * tile_size); + gfx::Size grow_right_tiling_size(7 * tile_size, 5 * tile_size); + gfx::Size grow_both_tiling_size(7 * tile_size, 7 * tile_size); + + Region invalidation; + Region expected_invalidation; + + pile_.tiling().SetMaxTextureSize(gfx::Size(tile_size, tile_size)); + SetTilingSize(base_tiling_size); + + // We should have a recording for every tile. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + UpdateAndExpandInvalidation( + &invalidation, grow_down_tiling_size, gfx::Rect(1, 1)); + + // We should have a recording for every tile. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(8, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the newly exposed pixels on the bottom row of tiles. + expected_invalidation = SubtractRegions(gfx::Rect(grow_down_tiling_size), + gfx::Rect(base_tiling_size)); + Region bottom_row_new_pixels = + SubtractRegions(gfx::UnionRects(pile_.tiling().TileBounds(0, 5), + pile_.tiling().TileBounds(5, 5)), + gfx::Rect(base_tiling_size)); + EXPECT_TRUE(expected_invalidation.Contains(bottom_row_new_pixels)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, base_tiling_size, gfx::Rect(1, 1)); + + // We should have a recording for every tile. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the previously exposed pixels on the bottom row of tiles. + expected_invalidation = SubtractRegions(gfx::Rect(grow_down_tiling_size), + gfx::Rect(base_tiling_size)); + EXPECT_TRUE(expected_invalidation.Contains(bottom_row_new_pixels)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation( + &invalidation, grow_right_tiling_size, gfx::Rect(1, 1)); + + // We should have a recording for every tile. + EXPECT_EQ(8, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the newly exposed pixels on the right column of tiles. + expected_invalidation = SubtractRegions(gfx::Rect(grow_right_tiling_size), + gfx::Rect(base_tiling_size)); + Region right_column_new_pixels = + SubtractRegions(gfx::UnionRects(pile_.tiling().TileBounds(5, 0), + pile_.tiling().TileBounds(5, 5)), + gfx::Rect(base_tiling_size)); + EXPECT_TRUE(expected_invalidation.Contains(right_column_new_pixels)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, base_tiling_size, gfx::Rect(1, 1)); + + // We should have lost the recordings that are now outside the tiling only. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the previously exposed pixels on the right column of tiles. + expected_invalidation = SubtractRegions(gfx::Rect(grow_right_tiling_size), + gfx::Rect(base_tiling_size)); + EXPECT_TRUE(expected_invalidation.Contains(right_column_new_pixels)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation( + &invalidation, grow_both_tiling_size, gfx::Rect(1, 1)); + + // We should have a recording for every tile. + EXPECT_EQ(8, pile_.tiling().num_tiles_x()); + EXPECT_EQ(8, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the newly exposed pixels on the bottom row and right column + // of tiles. + expected_invalidation = SubtractRegions(gfx::Rect(grow_both_tiling_size), + gfx::Rect(base_tiling_size)); + Region bottom_row_and_right_column_new_pixels = SubtractRegions( + UnionRegions(gfx::UnionRects(pile_.tiling().TileBounds(0, 5), + pile_.tiling().TileBounds(5, 5)), + gfx::UnionRects(pile_.tiling().TileBounds(5, 0), + pile_.tiling().TileBounds(5, 5))), + gfx::Rect(base_tiling_size)); + EXPECT_TRUE( + expected_invalidation.Contains(bottom_row_and_right_column_new_pixels)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, base_tiling_size, gfx::Rect()); + + // We should have lost the recordings that are now outside the tiling only. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the previously exposed pixels on the bottom row and right + // column of tiles. + expected_invalidation = SubtractRegions(gfx::Rect(grow_both_tiling_size), + gfx::Rect(base_tiling_size)); + EXPECT_TRUE( + expected_invalidation.Contains(bottom_row_and_right_column_new_pixels)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); +} + +TEST_F(PicturePileTest, SmallResizePileInsideInterestRect) { + // This size chosen to be small enough that all the rects below fit inside the + // the interest rect, so they are smaller than kPixelDistanceToRecord in each + // dimension. + int tile_size = 100; + gfx::Size base_tiling_size(5 * tile_size, 5 * tile_size); + gfx::Size grow_down_tiling_size(5 * tile_size, 5 * tile_size + 5); + gfx::Size grow_right_tiling_size(5 * tile_size + 5, 5 * tile_size); + gfx::Size grow_both_tiling_size(5 * tile_size + 5, 5 * tile_size + 5); + + Region invalidation; + Region expected_invalidation; + + pile_.tiling().SetMaxTextureSize(gfx::Size(tile_size, tile_size)); + SetTilingSize(base_tiling_size); + + // We should have a recording for every tile. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + UpdateAndExpandInvalidation( + &invalidation, grow_down_tiling_size, gfx::Rect(1, 1)); + + // We should have a recording for every tile. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the newly exposed pixels. + expected_invalidation = SubtractRegions(gfx::Rect(grow_down_tiling_size), + gfx::Rect(base_tiling_size)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, base_tiling_size, gfx::Rect(1, 1)); + + // We should have a recording for every tile. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the previously exposed pixels. + expected_invalidation = SubtractRegions(gfx::Rect(grow_down_tiling_size), + gfx::Rect(base_tiling_size)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation( + &invalidation, grow_right_tiling_size, gfx::Rect(1, 1)); + + // We should have a recording for every tile. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the newly exposed pixels. + expected_invalidation = SubtractRegions(gfx::Rect(grow_right_tiling_size), + gfx::Rect(base_tiling_size)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, base_tiling_size, gfx::Rect(1, 1)); + + // We should have lost the recordings that are now outside the tiling only. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the previously exposed pixels. + expected_invalidation = SubtractRegions(gfx::Rect(grow_right_tiling_size), + gfx::Rect(base_tiling_size)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation( + &invalidation, grow_both_tiling_size, gfx::Rect(1, 1)); + + // We should have a recording for every tile. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the newly exposed pixels. + expected_invalidation = SubtractRegions(gfx::Rect(grow_both_tiling_size), + gfx::Rect(base_tiling_size)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); + + UpdateWholePile(); + UpdateAndExpandInvalidation(&invalidation, base_tiling_size, gfx::Rect()); + + // We should have lost the recordings that are now outside the tiling only. + EXPECT_EQ(6, pile_.tiling().num_tiles_x()); + EXPECT_EQ(6, pile_.tiling().num_tiles_y()); + for (int i = 0; i < pile_.tiling().num_tiles_x(); ++i) { + for (int j = 0; j < pile_.tiling().num_tiles_y(); ++j) { + FakePicturePile::PictureMapKey key(i, j); + FakePicturePile::PictureMap& map = pile_.picture_map(); + FakePicturePile::PictureMap::iterator it = map.find(key); + EXPECT_TRUE(it != map.end() && it->second.get()); + } + } + + // We invalidated the previously exposed pixels. + expected_invalidation = SubtractRegions(gfx::Rect(grow_both_tiling_size), + gfx::Rect(base_tiling_size)); + EXPECT_EQ(expected_invalidation.ToString(), invalidation.ToString()); + invalidation.Clear(); +} + +TEST_F(PicturePileTest, SolidRectangleIsSolid) { + // If the client has no contents, the solid state will be true. + Region invalidation1(tiling_rect()); + UpdateAndExpandInvalidation(&invalidation1, tiling_size(), tiling_rect()); + EXPECT_TRUE(pile_.is_solid_color()); + EXPECT_EQ(static_cast<SkColor>(SK_ColorTRANSPARENT), pile_.solid_color()); + + // If there is a single rect that covers the view, the solid + // state will be true. + SkPaint paint; + paint.setColor(SK_ColorCYAN); + client_.add_draw_rect(tiling_rect(), paint); + Region invalidation2(tiling_rect()); + UpdateAndExpandInvalidation(&invalidation2, tiling_size(), tiling_rect()); + EXPECT_TRUE(pile_.is_solid_color()); + EXPECT_EQ(SK_ColorCYAN, pile_.solid_color()); + + // If a second smaller rect is draw that doesn't cover the viewport + // completely, the solid state will be false. + gfx::Rect smallRect = tiling_rect(); + smallRect.Inset(10, 10, 10, 10); + client_.add_draw_rect(smallRect, paint); + Region invalidation3(tiling_rect()); + UpdateAndExpandInvalidation(&invalidation3, tiling_size(), tiling_rect()); + EXPECT_FALSE(pile_.is_solid_color()); + + // If a third rect is drawn over everything, we should be solid again. + paint.setColor(SK_ColorRED); + client_.add_draw_rect(tiling_rect(), paint); + Region invalidation4(tiling_rect()); + UpdateAndExpandInvalidation(&invalidation4, tiling_size(), tiling_rect()); + EXPECT_TRUE(pile_.is_solid_color()); + EXPECT_EQ(SK_ColorRED, pile_.solid_color()); + + // If we draw too many, we don't bother doing the analysis and we should no + // longer be in a solid state. There are 8 rects, two clips and a translate. + client_.add_draw_rect(tiling_rect(), paint); + client_.add_draw_rect(tiling_rect(), paint); + client_.add_draw_rect(tiling_rect(), paint); + client_.add_draw_rect(tiling_rect(), paint); + client_.add_draw_rect(tiling_rect(), paint); + Region invalidation5(tiling_rect()); + UpdateAndExpandInvalidation(&invalidation5, tiling_size(), tiling_rect()); + EXPECT_FALSE(pile_.is_solid_color()); +} + +TEST_F(PicturePileTest, NonSolidRectangleOnOffsettedLayerIsNonSolid) { + gfx::Rect visible_rect(tiling_rect()); + visible_rect.Offset(gfx::Vector2d(1000, 1000)); + // The picture pile requires that the tiling completely encompass the viewport + // to make this test work correctly since the recorded viewport is an + // intersection of the tile size and viewport rect. This is possibly a flaw + // in |PicturePile|. + gfx::Size tiling_size(visible_rect.right(), visible_rect.bottom()); + // |Setup()| will create pictures here that mess with the test, clear it! + pile_.Clear(); + + SkPaint paint; + paint.setColor(SK_ColorCYAN); + + // Add a rect that doesn't cover the viewport completely, the solid state + // will be false. + gfx::Rect smallRect = visible_rect; + smallRect.Inset(10, 10, 10, 10); + client_.add_draw_rect(smallRect, paint); + Region invalidation(visible_rect); + UpdateAndExpandInvalidation(&invalidation, tiling_size, visible_rect); + EXPECT_FALSE(pile_.is_solid_color()); +} + +TEST_F(PicturePileTest, SetEmptyBounds) { + EXPECT_TRUE(pile_.is_solid_color()); + EXPECT_FALSE(pile_.GetSize().IsEmpty()); + EXPECT_FALSE(pile_.picture_map().empty()); + EXPECT_TRUE(pile_.HasRecordings()); + pile_.SetEmptyBounds(); + EXPECT_FALSE(pile_.is_solid_color()); + EXPECT_TRUE(pile_.GetSize().IsEmpty()); + EXPECT_TRUE(pile_.picture_map().empty()); + EXPECT_FALSE(pile_.HasRecordings()); +} + +} // namespace +} // namespace cc diff --git a/cc/playback/picture_unittest.cc b/cc/playback/picture_unittest.cc new file mode 100644 index 0000000..53310e3 --- /dev/null +++ b/cc/playback/picture_unittest.cc @@ -0,0 +1,181 @@ +// Copyright 2013 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/playback/picture.h" + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "cc/test/fake_content_layer_client.h" +#include "cc/test/skia_common.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkGraphics.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/skia_util.h" + +namespace cc { +namespace { + +TEST(PictureTest, AsBase64String) { + SkGraphics::Init(); + + gfx::Rect layer_rect(100, 100); + + gfx::Size tile_grid_size(100, 100); + + FakeContentLayerClient content_layer_client; + + scoped_ptr<base::Value> tmp; + + SkPaint red_paint; + red_paint.setColor(SkColorSetARGB(255, 255, 0, 0)); + SkPaint green_paint; + green_paint.setColor(SkColorSetARGB(255, 0, 255, 0)); + + // Invalid picture (not a dict). + tmp.reset(new base::StringValue("abc!@#$%")); + scoped_refptr<Picture> invalid_picture = + Picture::CreateFromValue(tmp.get()); + EXPECT_FALSE(invalid_picture.get()); + + // Single full-size rect picture. + content_layer_client.add_draw_rect(layer_rect, red_paint); + + scoped_refptr<Picture> one_rect_picture = + Picture::Create(layer_rect, &content_layer_client, tile_grid_size, false, + RecordingSource::RECORD_NORMALLY); + scoped_ptr<base::Value> serialized_one_rect(one_rect_picture->AsValue()); + + // Reconstruct the picture. + scoped_refptr<Picture> one_rect_picture_check = + Picture::CreateFromValue(serialized_one_rect.get()); + EXPECT_TRUE(one_rect_picture_check); + + // Check for equivalence. + unsigned char one_rect_buffer[4 * 100 * 100] = {0}; + DrawPicture(one_rect_buffer, layer_rect, one_rect_picture); + unsigned char one_rect_buffer_check[4 * 100 * 100] = {0}; + DrawPicture(one_rect_buffer_check, layer_rect, one_rect_picture_check); + + EXPECT_EQ(one_rect_picture->LayerRect(), one_rect_picture_check->LayerRect()); + EXPECT_EQ(0, memcmp(one_rect_buffer, one_rect_buffer_check, 4 * 100 * 100)); + + // Two rect picture. + content_layer_client.add_draw_rect(gfx::Rect(25, 25, 50, 50), green_paint); + + scoped_refptr<Picture> two_rect_picture = + Picture::Create(layer_rect, &content_layer_client, tile_grid_size, false, + RecordingSource::RECORD_NORMALLY); + + scoped_ptr<base::Value> serialized_two_rect(two_rect_picture->AsValue()); + + // Reconstruct the picture. + scoped_refptr<Picture> two_rect_picture_check = + Picture::CreateFromValue(serialized_two_rect.get()); + EXPECT_TRUE(two_rect_picture_check); + + // Check for equivalence. + unsigned char two_rect_buffer[4 * 100 * 100] = {0}; + DrawPicture(two_rect_buffer, layer_rect, two_rect_picture); + unsigned char two_rect_buffer_check[4 * 100 * 100] = {0}; + DrawPicture(two_rect_buffer_check, layer_rect, two_rect_picture_check); + + EXPECT_EQ(two_rect_picture->LayerRect(), two_rect_picture_check->LayerRect()); + EXPECT_EQ(0, memcmp(two_rect_buffer, two_rect_buffer_check, 4 * 100 * 100)); +} + +TEST(PictureTest, CreateFromSkpValue) { + SkGraphics::Init(); + + gfx::Rect layer_rect(100, 200); + + gfx::Size tile_grid_size(100, 200); + + FakeContentLayerClient content_layer_client; + + scoped_ptr<base::Value> tmp; + + SkPaint red_paint; + red_paint.setColor(SkColorSetARGB(255, 255, 0, 0)); + SkPaint green_paint; + green_paint.setColor(SkColorSetARGB(255, 0, 255, 0)); + + // Invalid picture (not a dict). + tmp.reset(new base::StringValue("abc!@#$%")); + scoped_refptr<Picture> invalid_picture = + Picture::CreateFromSkpValue(tmp.get()); + EXPECT_TRUE(!invalid_picture.get()); + + // Single full-size rect picture. + content_layer_client.add_draw_rect(layer_rect, red_paint); + scoped_refptr<Picture> one_rect_picture = + Picture::Create(layer_rect, &content_layer_client, tile_grid_size, false, + RecordingSource::RECORD_NORMALLY); + scoped_ptr<base::Value> serialized_one_rect( + one_rect_picture->AsValue()); + + const base::DictionaryValue* value = NULL; + EXPECT_TRUE(serialized_one_rect->GetAsDictionary(&value)); + + // Decode the picture from base64. + const base::Value* skp_value; + EXPECT_TRUE(value->Get("skp64", &skp_value)); + + // Reconstruct the picture. + scoped_refptr<Picture> one_rect_picture_check = + Picture::CreateFromSkpValue(skp_value); + EXPECT_TRUE(one_rect_picture_check); + + EXPECT_EQ(100, one_rect_picture_check->LayerRect().width()); + EXPECT_EQ(200, one_rect_picture_check->LayerRect().height()); +} + +TEST(PictureTest, RecordingModes) { + SkGraphics::Init(); + + gfx::Rect layer_rect(100, 200); + + gfx::Size tile_grid_size(100, 200); + + FakeContentLayerClient content_layer_client; + EXPECT_EQ(NULL, content_layer_client.last_canvas()); + + scoped_refptr<Picture> picture = + Picture::Create(layer_rect, &content_layer_client, tile_grid_size, false, + RecordingSource::RECORD_NORMALLY); + EXPECT_TRUE(content_layer_client.last_canvas() != NULL); + EXPECT_EQ(ContentLayerClient::PAINTING_BEHAVIOR_NORMAL, + content_layer_client.last_painting_control()); + EXPECT_TRUE(picture.get()); + + picture = Picture::Create(layer_rect, &content_layer_client, tile_grid_size, + false, RecordingSource::RECORD_WITH_SK_NULL_CANVAS); + EXPECT_TRUE(content_layer_client.last_canvas() != NULL); + EXPECT_EQ(ContentLayerClient::PAINTING_BEHAVIOR_NORMAL, + content_layer_client.last_painting_control()); + EXPECT_TRUE(picture.get()); + + picture = + Picture::Create(layer_rect, &content_layer_client, tile_grid_size, false, + RecordingSource::RECORD_WITH_PAINTING_DISABLED); + EXPECT_TRUE(content_layer_client.last_canvas() != NULL); + EXPECT_EQ(ContentLayerClient::DISPLAY_LIST_PAINTING_DISABLED, + content_layer_client.last_painting_control()); + EXPECT_TRUE(picture.get()); + + picture = + Picture::Create(layer_rect, &content_layer_client, tile_grid_size, false, + RecordingSource::RECORD_WITH_CACHING_DISABLED); + EXPECT_TRUE(content_layer_client.last_canvas() != NULL); + EXPECT_EQ(ContentLayerClient::DISPLAY_LIST_CACHING_DISABLED, + content_layer_client.last_painting_control()); + EXPECT_TRUE(picture.get()); + + // RECORD_WITH_CONSTRUCTION_DISABLED is not supported for Picture. + + EXPECT_EQ(5, RecordingSource::RECORDING_MODE_COUNT); +} + +} // namespace +} // namespace cc diff --git a/cc/playback/pixel_ref_map.cc b/cc/playback/pixel_ref_map.cc new file mode 100644 index 0000000..bb5a84c --- /dev/null +++ b/cc/playback/pixel_ref_map.cc @@ -0,0 +1,172 @@ +// Copyright 2015 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/playback/pixel_ref_map.h" + +#include <algorithm> +#include <limits> + +#include "cc/base/util.h" +#include "cc/playback/display_item_list.h" +#include "cc/playback/picture.h" +#include "skia/ext/pixel_ref_utils.h" + +namespace cc { + +PixelRefMap::PixelRefMap(const gfx::Size& cell_size) : cell_size_(cell_size) { + DCHECK(!cell_size.IsEmpty()); +} + +PixelRefMap::~PixelRefMap() { +} + +void PixelRefMap::GatherPixelRefsFromPicture(SkPicture* picture) { + DCHECK(picture); + + int min_x = std::numeric_limits<int>::max(); + int min_y = std::numeric_limits<int>::max(); + int max_x = 0; + int max_y = 0; + + skia::DiscardablePixelRefList pixel_refs; + skia::PixelRefUtils::GatherDiscardablePixelRefs(picture, &pixel_refs); + for (skia::DiscardablePixelRefList::const_iterator it = pixel_refs.begin(); + it != pixel_refs.end(); ++it) { + gfx::Point min( + RoundDown(static_cast<int>(it->pixel_ref_rect.x()), cell_size_.width()), + RoundDown(static_cast<int>(it->pixel_ref_rect.y()), + cell_size_.height())); + gfx::Point max( + RoundDown(static_cast<int>(std::ceil(it->pixel_ref_rect.right())), + cell_size_.width()), + RoundDown(static_cast<int>(std::ceil(it->pixel_ref_rect.bottom())), + cell_size_.height())); + + for (int y = min.y(); y <= max.y(); y += cell_size_.height()) { + for (int x = min.x(); x <= max.x(); x += cell_size_.width()) { + PixelRefMapKey key(x, y); + data_hash_map_[key].push_back(it->pixel_ref); + } + } + + min_x = std::min(min_x, min.x()); + min_y = std::min(min_y, min.y()); + max_x = std::max(max_x, max.x()); + max_y = std::max(max_y, max.y()); + } + + min_pixel_cell_ = gfx::Point(min_x, min_y); + max_pixel_cell_ = gfx::Point(max_x, max_y); +} + +base::LazyInstance<PixelRefs> PixelRefMap::Iterator::empty_pixel_refs_; + +PixelRefMap::Iterator::Iterator() + : target_pixel_ref_map_(NULL), + current_pixel_refs_(empty_pixel_refs_.Pointer()), + current_index_(0), + min_point_(-1, -1), + max_point_(-1, -1), + current_x_(0), + current_y_(0) { +} + +PixelRefMap::Iterator::Iterator(const gfx::Rect& rect, const Picture* picture) + : target_pixel_ref_map_(&(picture->pixel_refs_)), + current_pixel_refs_(empty_pixel_refs_.Pointer()), + current_index_(0) { + map_layer_rect_ = picture->layer_rect_; + PointToFirstPixelRef(rect); +} + +PixelRefMap::Iterator::Iterator(const gfx::Rect& rect, + const DisplayItemList* display_list) + : target_pixel_ref_map_(display_list->pixel_refs_.get()), + current_pixel_refs_(empty_pixel_refs_.Pointer()), + current_index_(0) { + map_layer_rect_ = display_list->layer_rect_; + PointToFirstPixelRef(rect); +} + +PixelRefMap::Iterator::~Iterator() { +} + +PixelRefMap::Iterator& PixelRefMap::Iterator::operator++() { + ++current_index_; + // If we're not at the end of the list, then we have the next item. + if (current_index_ < current_pixel_refs_->size()) + return *this; + + DCHECK(current_y_ <= max_point_.y()); + while (true) { + gfx::Size cell_size = target_pixel_ref_map_->cell_size_; + + // Advance the current grid cell. + current_x_ += cell_size.width(); + if (current_x_ > max_point_.x()) { + current_y_ += cell_size.height(); + current_x_ = min_point_.x(); + if (current_y_ > max_point_.y()) { + current_pixel_refs_ = empty_pixel_refs_.Pointer(); + current_index_ = 0; + break; + } + } + + // If there are no pixel refs at this grid cell, keep incrementing. + PixelRefMapKey key(current_x_, current_y_); + PixelRefHashmap::const_iterator iter = + target_pixel_ref_map_->data_hash_map_.find(key); + if (iter == target_pixel_ref_map_->data_hash_map_.end()) + continue; + + // We found a non-empty list: store it and get the first pixel ref. + current_pixel_refs_ = &iter->second; + current_index_ = 0; + break; + } + return *this; +} + +void PixelRefMap::Iterator::PointToFirstPixelRef(const gfx::Rect& rect) { + gfx::Rect query_rect(rect); + // Early out if the query rect doesn't intersect this picture. + if (!query_rect.Intersects(map_layer_rect_) || !target_pixel_ref_map_) { + min_point_ = gfx::Point(0, 0); + max_point_ = gfx::Point(0, 0); + current_x_ = 1; + current_y_ = 1; + return; + } + + // First, subtract the layer origin as cells are stored in layer space. + query_rect.Offset(-map_layer_rect_.OffsetFromOrigin()); + + DCHECK(!target_pixel_ref_map_->cell_size_.IsEmpty()); + gfx::Size cell_size(target_pixel_ref_map_->cell_size_); + // We have to find a cell_size aligned point that corresponds to + // query_rect. Point is a multiple of cell_size. + min_point_ = gfx::Point(RoundDown(query_rect.x(), cell_size.width()), + RoundDown(query_rect.y(), cell_size.height())); + max_point_ = + gfx::Point(RoundDown(query_rect.right() - 1, cell_size.width()), + RoundDown(query_rect.bottom() - 1, cell_size.height())); + + // Limit the points to known pixel ref boundaries. + min_point_ = gfx::Point( + std::max(min_point_.x(), target_pixel_ref_map_->min_pixel_cell_.x()), + std::max(min_point_.y(), target_pixel_ref_map_->min_pixel_cell_.y())); + max_point_ = gfx::Point( + std::min(max_point_.x(), target_pixel_ref_map_->max_pixel_cell_.x()), + std::min(max_point_.y(), target_pixel_ref_map_->max_pixel_cell_.y())); + + // Make the current x be cell_size.width() less than min point, so that + // the first increment will point at min_point_. + current_x_ = min_point_.x() - cell_size.width(); + current_y_ = min_point_.y(); + if (current_y_ <= max_point_.y()) + ++(*this); +} + +} // namespace cc diff --git a/cc/playback/pixel_ref_map.h b/cc/playback/pixel_ref_map.h new file mode 100644 index 0000000..e154521 --- /dev/null +++ b/cc/playback/pixel_ref_map.h @@ -0,0 +1,94 @@ +// Copyright 2015 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. + +#ifndef CC_PLAYBACK_PIXEL_REF_MAP_H_ +#define CC_PLAYBACK_PIXEL_REF_MAP_H_ + +#include <utility> +#include <vector> + +#include "base/containers/hash_tables.h" +#include "base/lazy_instance.h" +#include "base/memory/ref_counted.h" +#include "cc/base/cc_export.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +class SkPixelRef; + +namespace cc { + +class Picture; +class DisplayItemList; + +typedef std::pair<int, int> PixelRefMapKey; +typedef std::vector<SkPixelRef*> PixelRefs; +typedef base::hash_map<PixelRefMapKey, PixelRefs> PixelRefHashmap; + +// This class is used and owned by cc Picture class. It is used to gather pixel +// refs which would happen after record. It takes in |cell_size| to decide how +// big each grid cell should be. +class CC_EXPORT PixelRefMap { + public: + explicit PixelRefMap(const gfx::Size& cell_size); + ~PixelRefMap(); + void GatherPixelRefsFromPicture(SkPicture* picture); + + bool empty() const { return data_hash_map_.empty(); } + + // This iterator imprecisely returns the set of pixel refs that are needed to + // raster this layer rect from this picture. Internally, pixel refs are + // clumped into tile grid buckets, so there may be false positives. + class CC_EXPORT Iterator { + public: + // Default iterator constructor that is used as place holder for invalid + // Iterator. + Iterator(); + Iterator(const gfx::Rect& layer_rect, const Picture* picture); + Iterator(const gfx::Rect& layer_rect, const DisplayItemList* picture); + ~Iterator(); + + SkPixelRef* operator->() const { + DCHECK_LT(current_index_, current_pixel_refs_->size()); + return (*current_pixel_refs_)[current_index_]; + } + + SkPixelRef* operator*() const { + DCHECK_LT(current_index_, current_pixel_refs_->size()); + return (*current_pixel_refs_)[current_index_]; + } + + Iterator& operator++(); + operator bool() const { + return current_index_ < current_pixel_refs_->size(); + } + + private: + void PointToFirstPixelRef(const gfx::Rect& query_rect); + + static base::LazyInstance<PixelRefs> empty_pixel_refs_; + const PixelRefMap* target_pixel_ref_map_; + const PixelRefs* current_pixel_refs_; + unsigned current_index_; + + gfx::Rect map_layer_rect_; + + gfx::Point min_point_; + gfx::Point max_point_; + int current_x_; + int current_y_; + }; + + private: + gfx::Point min_pixel_cell_; + gfx::Point max_pixel_cell_; + gfx::Size cell_size_; + + PixelRefHashmap data_hash_map_; +}; + +} // namespace cc + +#endif // CC_PLAYBACK_PIXEL_REF_MAP_H_ diff --git a/cc/playback/pixel_ref_map_unittest.cc b/cc/playback/pixel_ref_map_unittest.cc new file mode 100644 index 0000000..af88b44 --- /dev/null +++ b/cc/playback/pixel_ref_map_unittest.cc @@ -0,0 +1,292 @@ +// Copyright 2015 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/playback/pixel_ref_map.h" + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "cc/playback/picture.h" +#include "cc/test/fake_content_layer_client.h" +#include "cc/test/skia_common.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkGraphics.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/skia_util.h" + +namespace cc { +namespace { + +TEST(PixelRefMapTest, PixelRefMapIterator) { + gfx::Rect layer_rect(2048, 2048); + + gfx::Size tile_grid_size(512, 512); + + FakeContentLayerClient content_layer_client; + + // Discardable pixel refs are found in the following grids: + // |---|---|---|---| + // | | x | | x | + // |---|---|---|---| + // | x | | x | | + // |---|---|---|---| + // | | x | | x | + // |---|---|---|---| + // | x | | x | | + // |---|---|---|---| + SkBitmap discardable_bitmap[4][4]; + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + if ((x + y) & 1) { + CreateBitmap(gfx::Size(500, 500), "discardable", + &discardable_bitmap[y][x]); + SkPaint paint; + content_layer_client.add_draw_bitmap( + discardable_bitmap[y][x], gfx::Point(x * 512 + 6, y * 512 + 6), + paint); + } + } + } + + scoped_refptr<Picture> picture = + Picture::Create(layer_rect, &content_layer_client, tile_grid_size, true, + RecordingSource::RECORD_NORMALLY); + + // Default iterator does not have any pixel refs. + { + PixelRefMap::Iterator iterator; + EXPECT_FALSE(iterator); + } + + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + PixelRefMap::Iterator iterator(gfx::Rect(x * 512, y * 512, 500, 500), + picture.get()); + if ((x + y) & 1) { + EXPECT_TRUE(iterator) << x << " " << y; + EXPECT_TRUE(*iterator == discardable_bitmap[y][x].pixelRef()) + << x << " " << y; + EXPECT_FALSE(++iterator) << x << " " << y; + } else { + EXPECT_FALSE(iterator) << x << " " << y; + } + } + } + // Capture 4 pixel refs. + { + PixelRefMap::Iterator iterator(gfx::Rect(512, 512, 2048, 2048), + picture.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1][2].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[2][1].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[2][3].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[3][2].pixelRef()); + EXPECT_FALSE(++iterator); + } + + // Copy test. + PixelRefMap::Iterator iterator(gfx::Rect(512, 512, 2048, 2048), + picture.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1][2].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[2][1].pixelRef()); + + // copy now points to the same spot as iterator, + // but both can be incremented independently. + PixelRefMap::Iterator copy = iterator; + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[2][3].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[3][2].pixelRef()); + EXPECT_FALSE(++iterator); + + EXPECT_TRUE(copy); + EXPECT_TRUE(*copy == discardable_bitmap[2][1].pixelRef()); + EXPECT_TRUE(++copy); + EXPECT_TRUE(*copy == discardable_bitmap[2][3].pixelRef()); + EXPECT_TRUE(++copy); + EXPECT_TRUE(*copy == discardable_bitmap[3][2].pixelRef()); + EXPECT_FALSE(++copy); +} + +TEST(PixelRefMapTest, PixelRefMapIteratorNonZeroLayer) { + gfx::Rect layer_rect(1024, 0, 2048, 2048); + + gfx::Size tile_grid_size(512, 512); + + FakeContentLayerClient content_layer_client; + + // Discardable pixel refs are found in the following grids: + // |---|---|---|---| + // | | x | | x | + // |---|---|---|---| + // | x | | x | | + // |---|---|---|---| + // | | x | | x | + // |---|---|---|---| + // | x | | x | | + // |---|---|---|---| + SkBitmap discardable_bitmap[4][4]; + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + if ((x + y) & 1) { + CreateBitmap(gfx::Size(500, 500), "discardable", + &discardable_bitmap[y][x]); + SkPaint paint; + content_layer_client.add_draw_bitmap( + discardable_bitmap[y][x], + gfx::Point(1024 + x * 512 + 6, y * 512 + 6), paint); + } + } + } + + scoped_refptr<Picture> picture = + Picture::Create(layer_rect, &content_layer_client, tile_grid_size, true, + RecordingSource::RECORD_NORMALLY); + + // Default iterator does not have any pixel refs. + { + PixelRefMap::Iterator iterator; + EXPECT_FALSE(iterator); + } + + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + PixelRefMap::Iterator iterator( + gfx::Rect(1024 + x * 512, y * 512, 500, 500), picture.get()); + if ((x + y) & 1) { + EXPECT_TRUE(iterator) << x << " " << y; + EXPECT_TRUE(*iterator == discardable_bitmap[y][x].pixelRef()); + EXPECT_FALSE(++iterator) << x << " " << y; + } else { + EXPECT_FALSE(iterator) << x << " " << y; + } + } + } + // Capture 4 pixel refs. + { + PixelRefMap::Iterator iterator(gfx::Rect(1024 + 512, 512, 2048, 2048), + picture.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1][2].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[2][1].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[2][3].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[3][2].pixelRef()); + EXPECT_FALSE(++iterator); + } + + // Copy test. + { + PixelRefMap::Iterator iterator(gfx::Rect(1024 + 512, 512, 2048, 2048), + picture.get()); + EXPECT_TRUE(iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[1][2].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[2][1].pixelRef()); + + // copy now points to the same spot as iterator, + // but both can be incremented independently. + PixelRefMap::Iterator copy = iterator; + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[2][3].pixelRef()); + EXPECT_TRUE(++iterator); + EXPECT_TRUE(*iterator == discardable_bitmap[3][2].pixelRef()); + EXPECT_FALSE(++iterator); + + EXPECT_TRUE(copy); + EXPECT_TRUE(*copy == discardable_bitmap[2][1].pixelRef()); + EXPECT_TRUE(++copy); + EXPECT_TRUE(*copy == discardable_bitmap[2][3].pixelRef()); + EXPECT_TRUE(++copy); + EXPECT_TRUE(*copy == discardable_bitmap[3][2].pixelRef()); + EXPECT_FALSE(++copy); + } + + // Non intersecting rects + { + PixelRefMap::Iterator iterator(gfx::Rect(0, 0, 1000, 1000), picture.get()); + EXPECT_FALSE(iterator); + } + { + PixelRefMap::Iterator iterator(gfx::Rect(3500, 0, 1000, 1000), + picture.get()); + EXPECT_FALSE(iterator); + } + { + PixelRefMap::Iterator iterator(gfx::Rect(0, 1100, 1000, 1000), + picture.get()); + EXPECT_FALSE(iterator); + } + { + PixelRefMap::Iterator iterator(gfx::Rect(3500, 1100, 1000, 1000), + picture.get()); + EXPECT_FALSE(iterator); + } +} + +TEST(PixelRefMapTest, PixelRefMapIteratorOnePixelQuery) { + gfx::Rect layer_rect(2048, 2048); + + gfx::Size tile_grid_size(512, 512); + + FakeContentLayerClient content_layer_client; + + // Discardable pixel refs are found in the following grids: + // |---|---|---|---| + // | | x | | x | + // |---|---|---|---| + // | x | | x | | + // |---|---|---|---| + // | | x | | x | + // |---|---|---|---| + // | x | | x | | + // |---|---|---|---| + SkBitmap discardable_bitmap[4][4]; + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + if ((x + y) & 1) { + CreateBitmap(gfx::Size(500, 500), "discardable", + &discardable_bitmap[y][x]); + SkPaint paint; + content_layer_client.add_draw_bitmap( + discardable_bitmap[y][x], gfx::Point(x * 512 + 6, y * 512 + 6), + paint); + } + } + } + + scoped_refptr<Picture> picture = + Picture::Create(layer_rect, &content_layer_client, tile_grid_size, true, + RecordingSource::RECORD_NORMALLY); + + // Default iterator does not have any pixel refs. + { + PixelRefMap::Iterator iterator; + EXPECT_FALSE(iterator); + } + + for (int y = 0; y < 4; ++y) { + for (int x = 0; x < 4; ++x) { + PixelRefMap::Iterator iterator(gfx::Rect(x * 512, y * 512 + 256, 1, 1), + picture.get()); + if ((x + y) & 1) { + EXPECT_TRUE(iterator) << x << " " << y; + EXPECT_TRUE(*iterator == discardable_bitmap[y][x].pixelRef()); + EXPECT_FALSE(++iterator) << x << " " << y; + } else { + EXPECT_FALSE(iterator) << x << " " << y; + } + } + } +} + +} // namespace +} // namespace cc diff --git a/cc/playback/raster_source.h b/cc/playback/raster_source.h new file mode 100644 index 0000000..c0a484a --- /dev/null +++ b/cc/playback/raster_source.h @@ -0,0 +1,116 @@ +// 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. + +#ifndef CC_PLAYBACK_RASTER_SOURCE_H_ +#define CC_PLAYBACK_RASTER_SOURCE_H_ + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "cc/base/cc_export.h" +#include "cc/debug/traced_value.h" +#include "skia/ext/refptr.h" +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkPixelRef.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +class SkCanvas; +class SkPicture; + +namespace cc { + +class Picture; + +class CC_EXPORT RasterSource : public base::RefCountedThreadSafe<RasterSource> { + public: + struct CC_EXPORT SolidColorAnalysis { + SolidColorAnalysis() + : is_solid_color(false), solid_color(SK_ColorTRANSPARENT) {} + ~SolidColorAnalysis() {} + + bool is_solid_color; + SkColor solid_color; + }; + + // Raster a subrect of this RasterSource into the given canvas. It is + // assumed that contents_scale has already been applied to this canvas. + // Writes the total number of pixels rasterized and the time spent + // rasterizing to the stats if the respective pointer is not nullptr. + // It is assumed that the canvas passed here will only be rasterized by + // this raster source via this call. + virtual void PlaybackToCanvas(SkCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const = 0; + + // Similar to above, except that the canvas passed here can (or was already) + // rasterized into by another raster source. That is, it is not safe to clear + // the canvas or discard its underlying memory. + virtual void PlaybackToSharedCanvas(SkCanvas* canvas, + const gfx::Rect& canvas_rect, + float contents_scale) const = 0; + + // Analyze to determine if the given rect at given scale is of solid color in + // this raster source. + virtual void PerformSolidColorAnalysis( + const gfx::Rect& content_rect, + float contents_scale, + SolidColorAnalysis* analysis) const = 0; + + // Returns true iff the whole raster source is of solid color. + virtual bool IsSolidColor() const = 0; + + // Returns the color of the raster source if it is solid color. The results + // are unspecified if IsSolidColor returns false. + virtual SkColor GetSolidColor() const = 0; + + // Returns the size of this raster source. + virtual gfx::Size GetSize() const = 0; + + // Populate the given list with all SkPixelRefs that may overlap the given + // rect at given scale. + virtual void GatherPixelRefs(const gfx::Rect& content_rect, + float contents_scale, + std::vector<SkPixelRef*>* pixel_refs) const = 0; + + // Return true iff this raster source can raster the given rect at given + // scale. + virtual bool CoversRect(const gfx::Rect& content_rect, + float contents_scale) const = 0; + + // Returns true if this raster source has anything to rasterize. + virtual bool HasRecordings() const = 0; + + // Informs the raster source that it should attempt to use distance field text + // during rasterization. + virtual void SetShouldAttemptToUseDistanceFieldText() = 0; + + // Return true iff this raster source would benefit from using distance + // field text. + virtual bool ShouldAttemptToUseDistanceFieldText() const = 0; + + // Tracing functionality. + virtual void DidBeginTracing() = 0; + virtual void AsValueInto(base::trace_event::TracedValue* array) const = 0; + virtual skia::RefPtr<SkPicture> GetFlattenedPicture() = 0; + virtual size_t GetPictureMemoryUsage() const = 0; + + // Return true if LCD anti-aliasing may be used when rastering text. + virtual bool CanUseLCDText() const = 0; + + virtual scoped_refptr<RasterSource> CreateCloneWithoutLCDText() const = 0; + + protected: + friend class base::RefCountedThreadSafe<RasterSource>; + + RasterSource() {} + virtual ~RasterSource() {} + + private: + DISALLOW_COPY_AND_ASSIGN(RasterSource); +}; + +} // namespace cc + +#endif // CC_PLAYBACK_RASTER_SOURCE_H_ diff --git a/cc/playback/raster_source_helper.cc b/cc/playback/raster_source_helper.cc new file mode 100644 index 0000000..6510b3c --- /dev/null +++ b/cc/playback/raster_source_helper.cc @@ -0,0 +1,81 @@ +// 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/playback/raster_source_helper.h" + +#include "base/trace_event/trace_event.h" +#include "cc/debug/debug_colors.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/skia_util.h" + +namespace cc { + +void RasterSourceHelper::PrepareForPlaybackToCanvas( + SkCanvas* canvas, + const gfx::Rect& canvas_rect, + const gfx::Rect& source_rect, + float contents_scale, + SkColor background_color, + bool clear_canvas_with_debug_color, + bool requires_clear) { + canvas->discard(); + if (clear_canvas_with_debug_color) { + // Any non-painted areas in the content bounds will be left in this color. + canvas->clear(DebugColors::NonPaintedFillColor()); + } + + // If this raster source has opaque contents, it is guaranteeing that it will + // draw an opaque rect the size of the layer. If it is not, then we must + // clear this canvas ourselves. + if (requires_clear) { + TRACE_EVENT_INSTANT0("cc", "SkCanvas::clear", TRACE_EVENT_SCOPE_THREAD); + // Clearing is about ~4x faster than drawing a rect even if the content + // isn't covering a majority of the canvas. + canvas->clear(SK_ColorTRANSPARENT); + } else { + // Even if completely covered, for rasterizations that touch the edge of the + // layer, we also need to raster the background color underneath the last + // texel (since the recording won't cover it) and outside the last texel + // (due to linear filtering when using this texture). + gfx::Rect content_rect = + gfx::ToEnclosingRect(gfx::ScaleRect(source_rect, contents_scale)); + + // The final texel of content may only be partially covered by a + // rasterization; this rect represents the content rect that is fully + // covered by content. + gfx::Rect deflated_content_rect = content_rect; + deflated_content_rect.Inset(0, 0, 1, 1); + if (!deflated_content_rect.Contains(canvas_rect)) { + if (clear_canvas_with_debug_color) { + // Any non-painted areas outside of the content bounds are left in + // this color. If this is seen then it means that cc neglected to + // rerasterize a tile that used to intersect with the content rect + // after the content bounds grew. + canvas->save(); + canvas->translate(-canvas_rect.x(), -canvas_rect.y()); + canvas->clipRect(gfx::RectToSkRect(content_rect), + SkRegion::kDifference_Op); + canvas->drawColor(DebugColors::MissingResizeInvalidations(), + SkXfermode::kSrc_Mode); + canvas->restore(); + } + + // Drawing at most 2 x 2 x (canvas width + canvas height) texels is 2-3X + // faster than clearing, so special case this. + canvas->save(); + canvas->translate(-canvas_rect.x(), -canvas_rect.y()); + gfx::Rect inflated_content_rect = content_rect; + inflated_content_rect.Inset(0, 0, -1, -1); + canvas->clipRect(gfx::RectToSkRect(inflated_content_rect), + SkRegion::kReplace_Op); + canvas->clipRect(gfx::RectToSkRect(deflated_content_rect), + SkRegion::kDifference_Op); + canvas->drawColor(background_color, SkXfermode::kSrc_Mode); + canvas->restore(); + } + } +} + +} // namespace cc diff --git a/cc/playback/raster_source_helper.h b/cc/playback/raster_source_helper.h new file mode 100644 index 0000000..3051303 --- /dev/null +++ b/cc/playback/raster_source_helper.h @@ -0,0 +1,29 @@ +// 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. + +#ifndef CC_PLAYBACK_RASTER_SOURCE_HELPER_H_ +#define CC_PLAYBACK_RASTER_SOURCE_HELPER_H_ + +#include "cc/base/cc_export.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/geometry/rect.h" + +class SkCanvas; + +namespace cc { + +class CC_EXPORT RasterSourceHelper { + public: + static void PrepareForPlaybackToCanvas(SkCanvas* canvas, + const gfx::Rect& canvas_rect, + const gfx::Rect& source_rect, + float contents_scale, + SkColor background_color, + bool clear_canvas_with_debug_color, + bool requires_clear); +}; + +} // namespace cc + +#endif // CC_PLAYBACK_RASTER_SOURCE_HELPER_H_ diff --git a/cc/playback/recording_source.h b/cc/playback/recording_source.h new file mode 100644 index 0000000..cf8c7c4 --- /dev/null +++ b/cc/playback/recording_source.h @@ -0,0 +1,63 @@ +// 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. + +#ifndef CC_PLAYBACK_RECORDING_SOURCE_H_ +#define CC_PLAYBACK_RECORDING_SOURCE_H_ + +#include "base/memory/ref_counted.h" +#include "cc/base/cc_export.h" +#include "third_party/skia/include/core/SkColor.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +namespace cc { +class ContentLayerClient; +class Region; +class RasterSource; + +class CC_EXPORT RecordingSource { + public: + // TODO(schenney) Remove RECORD_WITH_SK_NULL_CANVAS when we no longer + // support a non-Slimming Paint path. + enum RecordingMode { + RECORD_NORMALLY, + RECORD_WITH_SK_NULL_CANVAS, + RECORD_WITH_PAINTING_DISABLED, + RECORD_WITH_CACHING_DISABLED, + RECORD_WITH_CONSTRUCTION_DISABLED, + RECORDING_MODE_COUNT, // Must be the last entry. + }; + + virtual ~RecordingSource() {} + // Re-record parts of the picture that are invalid. + // Invalidations are in layer space, and will be expanded to cover everything + // that was either recorded/changed or that has no recording, leaving out only + // pieces that we had a recording for and it was not changed. + // Return true iff the pile was modified. + virtual bool UpdateAndExpandInvalidation(ContentLayerClient* painter, + Region* invalidation, + const gfx::Size& layer_size, + const gfx::Rect& visible_layer_rect, + int frame_number, + RecordingMode recording_mode) = 0; + + virtual scoped_refptr<RasterSource> CreateRasterSource( + bool can_use_lcd_text) const = 0; + + virtual gfx::Size GetSize() const = 0; + virtual void SetEmptyBounds() = 0; + virtual void SetSlowdownRasterScaleFactor(int factor) = 0; + virtual void SetGatherPixelRefs(bool gather_pixel_refs) = 0; + virtual void SetBackgroundColor(SkColor background_color) = 0; + virtual void SetRequiresClear(bool requires_clear) = 0; + virtual bool IsSuitableForGpuRasterization() const = 0; + + // TODO(hendrikw): Figure out how to remove this. + virtual void SetUnsuitableForGpuRasterizationForTesting() = 0; + virtual gfx::Size GetTileGridSizeForTesting() const = 0; +}; + +} // namespace cc + +#endif // CC_PLAYBACK_RECORDING_SOURCE_H_ diff --git a/cc/playback/recording_source_unittest.cc b/cc/playback/recording_source_unittest.cc new file mode 100644 index 0000000..01a89a1 --- /dev/null +++ b/cc/playback/recording_source_unittest.cc @@ -0,0 +1,470 @@ +// Copyright 2015 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 <vector> + +#include "cc/playback/display_list_raster_source.h" +#include "cc/test/fake_display_list_recording_source.h" +#include "cc/test/fake_picture_pile.h" +#include "cc/test/fake_picture_pile_impl.h" +#include "cc/test/impl_side_painting_settings.h" +#include "cc/test/skia_common.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace cc { +namespace { + +template <class T> +scoped_ptr<T> CreateRecordingSource(const gfx::Rect& viewport, + const gfx::Size& grid_cell_size); + +template <> +scoped_ptr<FakePicturePile> CreateRecordingSource<FakePicturePile>( + const gfx::Rect& viewport, + const gfx::Size& grid_cell_size) { + return FakePicturePile::CreateFilledPile(grid_cell_size, viewport.size()); +} + +template <> +scoped_ptr<FakeDisplayListRecordingSource> CreateRecordingSource< + FakeDisplayListRecordingSource>(const gfx::Rect& viewport, + const gfx::Size& grid_cell_size) { + scoped_ptr<FakeDisplayListRecordingSource> recording_source = + FakeDisplayListRecordingSource::CreateRecordingSource(viewport); + recording_source->SetGridCellSize(grid_cell_size); + + return recording_source.Pass(); +} + +template <class T> +scoped_refptr<RasterSource> CreateRasterSource(T* recording_source); + +template <> +scoped_refptr<RasterSource> CreateRasterSource( + FakePicturePile* recording_source) { + return FakePicturePileImpl::CreateFromPile(recording_source, nullptr); +} + +template <> +scoped_refptr<RasterSource> CreateRasterSource( + FakeDisplayListRecordingSource* recording_source) { + bool can_use_lcd_text = true; + return DisplayListRasterSource::CreateFromDisplayListRecordingSource( + recording_source, can_use_lcd_text); +} + +template <typename T> +class RecordingSourceTest : public testing::Test {}; + +using testing::Types; + +typedef Types<FakePicturePile, FakeDisplayListRecordingSource> + RecordingSourceImplementations; + +TYPED_TEST_CASE(RecordingSourceTest, RecordingSourceImplementations); + +TYPED_TEST(RecordingSourceTest, NoGatherPixelRefEmptyPixelRefs) { + gfx::Size grid_cell_size(128, 128); + gfx::Rect recorded_viewport(0, 0, 256, 256); + + scoped_ptr<TypeParam> recording_source = + CreateRecordingSource<TypeParam>(recorded_viewport, grid_cell_size); + recording_source->SetGatherPixelRefs(false); + recording_source->Rerecord(); + + scoped_refptr<RasterSource> raster_source = + CreateRasterSource<TypeParam>(recording_source.get()); + + // If recording source do not gather pixel ref, raster source is not going to + // get pixel refs. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(recorded_viewport, 1.0, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } +} + +TYPED_TEST(RecordingSourceTest, EmptyPixelRefs) { + gfx::Size grid_cell_size(128, 128); + gfx::Rect recorded_viewport(0, 0, 256, 256); + + scoped_ptr<TypeParam> recording_source = + CreateRecordingSource<TypeParam>(recorded_viewport, grid_cell_size); + recording_source->SetGatherPixelRefs(true); + recording_source->Rerecord(); + + scoped_refptr<RasterSource> raster_source = + CreateRasterSource<TypeParam>(recording_source.get()); + + // Tile sized iterators. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 128, 128), 1.0, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 256, 256), 2.0, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 64, 64), 0.5, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + // Shifted tile sized iterators. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(140, 140, 128, 128), 1.0, + &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(280, 280, 256, 256), 2.0, + &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(70, 70, 64, 64), 0.5, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + // Layer sized iterators. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 256, 256), 1.0, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 512, 512), 2.0, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 128, 128), 0.5, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } +} + +TYPED_TEST(RecordingSourceTest, NoDiscardablePixelRefs) { + gfx::Size grid_cell_size(128, 128); + gfx::Rect recorded_viewport(0, 0, 256, 256); + + scoped_ptr<TypeParam> recording_source = + CreateRecordingSource<TypeParam>(recorded_viewport, grid_cell_size); + + SkPaint simple_paint; + simple_paint.setColor(SkColorSetARGB(255, 12, 23, 34)); + + SkBitmap non_discardable_bitmap; + CreateBitmap(gfx::Size(128, 128), "notdiscardable", &non_discardable_bitmap); + + recording_source->add_draw_rect_with_paint(gfx::Rect(0, 0, 256, 256), + simple_paint); + recording_source->add_draw_rect_with_paint(gfx::Rect(128, 128, 512, 512), + simple_paint); + recording_source->add_draw_rect_with_paint(gfx::Rect(512, 0, 256, 256), + simple_paint); + recording_source->add_draw_rect_with_paint(gfx::Rect(0, 512, 256, 256), + simple_paint); + recording_source->add_draw_bitmap(non_discardable_bitmap, gfx::Point(128, 0)); + recording_source->add_draw_bitmap(non_discardable_bitmap, gfx::Point(0, 128)); + recording_source->add_draw_bitmap(non_discardable_bitmap, + gfx::Point(150, 150)); + recording_source->SetGatherPixelRefs(true); + recording_source->Rerecord(); + + scoped_refptr<RasterSource> raster_source = + CreateRasterSource<TypeParam>(recording_source.get()); + + // Tile sized iterators. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 128, 128), 1.0, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 256, 256), 2.0, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 64, 64), 0.5, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + // Shifted tile sized iterators. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(140, 140, 128, 128), 1.0, + &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(280, 280, 256, 256), 2.0, + &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(70, 70, 64, 64), 0.5, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + // Layer sized iterators. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 256, 256), 1.0, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 512, 512), 2.0, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 128, 128), 0.5, &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } +} + +TYPED_TEST(RecordingSourceTest, DiscardablePixelRefs) { + gfx::Size grid_cell_size(128, 128); + gfx::Rect recorded_viewport(0, 0, 256, 256); + + scoped_ptr<TypeParam> recording_source = + CreateRecordingSource<TypeParam>(recorded_viewport, grid_cell_size); + + SkBitmap discardable_bitmap[2][2]; + CreateBitmap(gfx::Size(32, 32), "discardable", &discardable_bitmap[0][0]); + CreateBitmap(gfx::Size(32, 32), "discardable", &discardable_bitmap[1][0]); + CreateBitmap(gfx::Size(32, 32), "discardable", &discardable_bitmap[1][1]); + + // Discardable pixel refs are found in the following cells: + // |---|---| + // | x | | + // |---|---| + // | x | x | + // |---|---| + recording_source->add_draw_bitmap(discardable_bitmap[0][0], gfx::Point(0, 0)); + recording_source->add_draw_bitmap(discardable_bitmap[1][0], + gfx::Point(0, 130)); + recording_source->add_draw_bitmap(discardable_bitmap[1][1], + gfx::Point(140, 140)); + recording_source->SetGatherPixelRefs(true); + recording_source->Rerecord(); + + scoped_refptr<RasterSource> raster_source = + CreateRasterSource<TypeParam>(recording_source.get()); + + // Tile sized iterators. These should find only one pixel ref. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 128, 128), 1.0, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 256, 256), 2.0, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 64, 64), 0.5, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + + // Shifted tile sized iterators. These should find only one pixel ref. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(140, 140, 128, 128), 1.0, + &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(280, 280, 256, 256), 2.0, + &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(70, 70, 64, 64), 0.5, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + + // Ensure there's no discardable pixel refs in the empty cell + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(140, 0, 128, 128), 1.0, + &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + + // Layer sized iterators. These should find all 3 pixel refs. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 256, 256), 1.0, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[1][0].pixelRef()); + EXPECT_TRUE(pixel_refs[2] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(3u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 512, 512), 2.0, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[1][0].pixelRef()); + EXPECT_TRUE(pixel_refs[2] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(3u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 128, 128), 0.5, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[1][0].pixelRef()); + EXPECT_TRUE(pixel_refs[2] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(3u, pixel_refs.size()); + } +} + +TYPED_TEST(RecordingSourceTest, DiscardablePixelRefsBaseNonDiscardable) { + gfx::Size grid_cell_size(256, 256); + gfx::Rect recorded_viewport(0, 0, 512, 512); + + scoped_ptr<TypeParam> recording_source = + CreateRecordingSource<TypeParam>(recorded_viewport, grid_cell_size); + + SkBitmap non_discardable_bitmap; + CreateBitmap(gfx::Size(512, 512), "notdiscardable", &non_discardable_bitmap); + + SkBitmap discardable_bitmap[2][2]; + CreateBitmap(gfx::Size(128, 128), "discardable", &discardable_bitmap[0][0]); + CreateBitmap(gfx::Size(128, 128), "discardable", &discardable_bitmap[0][1]); + CreateBitmap(gfx::Size(128, 128), "discardable", &discardable_bitmap[1][1]); + + // One large non-discardable bitmap covers the whole grid. + // Discardable pixel refs are found in the following cells: + // |---|---| + // | x | x | + // |---|---| + // | | x | + // |---|---| + recording_source->add_draw_bitmap(non_discardable_bitmap, gfx::Point(0, 0)); + recording_source->add_draw_bitmap(discardable_bitmap[0][0], gfx::Point(0, 0)); + recording_source->add_draw_bitmap(discardable_bitmap[0][1], + gfx::Point(260, 0)); + recording_source->add_draw_bitmap(discardable_bitmap[1][1], + gfx::Point(260, 260)); + recording_source->SetGatherPixelRefs(true); + recording_source->Rerecord(); + + scoped_refptr<RasterSource> raster_source = + CreateRasterSource<TypeParam>(recording_source.get()); + + // Tile sized iterators. These should find only one pixel ref. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 256, 256), 1.0, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 512, 512), 2.0, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 128, 128), 0.5, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + // Shifted tile sized iterators. These should find only one pixel ref. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(260, 260, 256, 256), 1.0, + &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(520, 520, 512, 512), 2.0, + &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(130, 130, 128, 128), 0.5, + &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(1u, pixel_refs.size()); + } + // Ensure there's no discardable pixel refs in the empty cell + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 256, 256, 256), 1.0, + &pixel_refs); + EXPECT_TRUE(pixel_refs.empty()); + } + // Layer sized iterators. These should find three pixel ref. + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 512, 512), 1.0, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[0][1].pixelRef()); + EXPECT_TRUE(pixel_refs[2] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(3u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 1024, 1024), 2.0, + &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[0][1].pixelRef()); + EXPECT_TRUE(pixel_refs[2] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(3u, pixel_refs.size()); + } + { + std::vector<SkPixelRef*> pixel_refs; + raster_source->GatherPixelRefs(gfx::Rect(0, 0, 256, 256), 0.5, &pixel_refs); + EXPECT_FALSE(pixel_refs.empty()); + EXPECT_TRUE(pixel_refs[0] == discardable_bitmap[0][0].pixelRef()); + EXPECT_TRUE(pixel_refs[1] == discardable_bitmap[0][1].pixelRef()); + EXPECT_TRUE(pixel_refs[2] == discardable_bitmap[1][1].pixelRef()); + EXPECT_EQ(3u, pixel_refs.size()); + } +} + +} // namespace +} // namespace cc diff --git a/cc/playback/transform_display_item.cc b/cc/playback/transform_display_item.cc new file mode 100644 index 0000000..1f4e5db --- /dev/null +++ b/cc/playback/transform_display_item.cc @@ -0,0 +1,59 @@ +// 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/playback/transform_display_item.h" + +#include "base/strings/stringprintf.h" +#include "base/trace_event/trace_event_argument.h" +#include "third_party/skia/include/core/SkCanvas.h" + +namespace cc { + +TransformDisplayItem::TransformDisplayItem() + : transform_(gfx::Transform::kSkipInitialization) { +} + +TransformDisplayItem::~TransformDisplayItem() { +} + +void TransformDisplayItem::SetNew(const gfx::Transform& transform) { + transform_ = transform; + + size_t memory_usage = sizeof(gfx::Transform); + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 1 /* op_count */, + memory_usage); +} + +void TransformDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + canvas->save(); + if (!transform_.IsIdentity()) + canvas->concat(transform_.matrix()); +} + +void TransformDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->AppendString(base::StringPrintf("TransformDisplayItem transform: [%s]", + transform_.ToString().c_str())); +} + +EndTransformDisplayItem::EndTransformDisplayItem() { + DisplayItem::SetNew(true /* suitable_for_gpu_raster */, 0 /* op_count */, + 0 /* memory_usage */); +} + +EndTransformDisplayItem::~EndTransformDisplayItem() { +} + +void EndTransformDisplayItem::Raster(SkCanvas* canvas, + SkDrawPictureCallback* callback) const { + canvas->restore(); +} + +void EndTransformDisplayItem::AsValueInto( + base::trace_event::TracedValue* array) const { + array->AppendString("EndTransformDisplayItem"); +} + +} // namespace cc diff --git a/cc/playback/transform_display_item.h b/cc/playback/transform_display_item.h new file mode 100644 index 0000000..731249d --- /dev/null +++ b/cc/playback/transform_display_item.h @@ -0,0 +1,47 @@ +// 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. + +#ifndef CC_PLAYBACK_TRANSFORM_DISPLAY_ITEM_H_ +#define CC_PLAYBACK_TRANSFORM_DISPLAY_ITEM_H_ + +#include "base/memory/scoped_ptr.h" +#include "cc/base/cc_export.h" +#include "cc/playback/display_item.h" +#include "ui/gfx/transform.h" + +class SkCanvas; +class SkDrawPictureCallback; + +namespace cc { + +class CC_EXPORT TransformDisplayItem : public DisplayItem { + public: + TransformDisplayItem(); + ~TransformDisplayItem() override; + + void SetNew(const gfx::Transform& transform); + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; + + private: + gfx::Transform transform_; +}; + +class CC_EXPORT EndTransformDisplayItem : public DisplayItem { + public: + EndTransformDisplayItem(); + ~EndTransformDisplayItem() override; + + static scoped_ptr<EndTransformDisplayItem> Create() { + return make_scoped_ptr(new EndTransformDisplayItem()); + } + + void Raster(SkCanvas* canvas, SkDrawPictureCallback* callback) const override; + void AsValueInto(base::trace_event::TracedValue* array) const override; +}; + +} // namespace cc + +#endif // CC_PLAYBACK_TRANSFORM_DISPLAY_ITEM_H_ |