diff --git a/cc/BUILD.gn b/cc/BUILD.gn index 6b7472f4f52fc7..fa0bd60bad3841 100644 --- a/cc/BUILD.gn +++ b/cc/BUILD.gn @@ -652,6 +652,8 @@ cc_static_library("test_support") { "test/test_occlusion_tracker.h", "test/test_shared_bitmap_manager.cc", "test/test_shared_bitmap_manager.h", + "test/test_skcanvas.cc", + "test/test_skcanvas.h", "test/test_task_graph_runner.cc", "test/test_task_graph_runner.h", "test/test_texture.cc", @@ -769,6 +771,7 @@ cc_test("cc_unittests") { "output/texture_mailbox_deleter_unittest.cc", "paint/discardable_image_map_unittest.cc", "paint/display_item_list_unittest.cc", + "paint/paint_op_buffer_unittest.cc", "quads/draw_polygon_unittest.cc", "quads/draw_quad_unittest.cc", "quads/nine_patch_generator_unittest.cc", @@ -939,6 +942,7 @@ cc_test("cc_perftests") { "//cc/ipc", "//cc/ipc:interfaces", "//cc/paint", + "//cc/paint", "//cc/surfaces", "//cc/surfaces:surface_id", "//gpu", diff --git a/cc/paint/BUILD.gn b/cc/paint/BUILD.gn index 3af5dee140ea11..11f9fdac082b2a 100644 --- a/cc/paint/BUILD.gn +++ b/cc/paint/BUILD.gn @@ -32,13 +32,19 @@ cc_component("paint") { "paint_canvas.cc", "paint_canvas.h", "paint_export.h", + "paint_flags.cc", "paint_flags.h", "paint_image.cc", "paint_image.h", + "paint_op_buffer.cc", + "paint_op_buffer.h", + "paint_record.cc", "paint_record.h", "paint_recorder.cc", "paint_recorder.h", "paint_shader.h", + "record_paint_canvas.cc", + "record_paint_canvas.h", "skia_paint_canvas.cc", "skia_paint_canvas.h", "transform_display_item.cc", diff --git a/cc/paint/display_item_list.cc b/cc/paint/display_item_list.cc index 83599482f53461..c1b069548409f6 100644 --- a/cc/paint/display_item_list.cc +++ b/cc/paint/display_item_list.cc @@ -100,9 +100,13 @@ NOINLINE DISABLE_CFI_PERF void RasterItem(const DisplayItem& base_item, if (canvas->quickReject(item.picture->cullRect())) break; - // SkPicture always does a wrapping save/restore on the canvas, so it is - // not necessary here. + // TODO(enne): Maybe the PaintRecord itself could know whether this + // was needed? It's not clear whether these save/restore semantics + // that SkPicture handles during playback are things that should be + // kept around. + canvas->save(); item.picture->playback(canvas, callback); + canvas->restore(); break; } case DisplayItem::FLOAT_CLIP: { @@ -176,6 +180,33 @@ void DisplayItemList::Raster(SkCanvas* canvas, canvas->restore(); } +// Atttempts to merge a CompositingDisplayItem and DrawingDisplayItem +// into a single "draw with alpha". This function returns true if +// it was successful. If false, then the caller is responsible for +// drawing these items. This is a DisplayItemList version of the +// SkRecord optimization SkRecordNoopSaveLayerDrawRestores. +static bool MergeAndDrawIfPossible(const CompositingDisplayItem& save_item, + const DrawingDisplayItem& draw_item, + SkCanvas* canvas) { + if (save_item.color_filter) + return false; + if (save_item.xfermode != SkBlendMode::kSrcOver) + return false; + // TODO(enne): I believe that lcd_text_requires_opaque_layer is not + // relevant here and that lcd text is preserved post merge, but I haven't + // tested that. + const PaintRecord* record = draw_item.picture.get(); + if (record->approximateOpCount() != 1) + return false; + + const PaintOp* op = record->GetFirstOp(); + if (!op->IsDrawOp()) + return false; + + op->RasterWithAlpha(canvas, save_item.alpha); + return true; +} + void DisplayItemList::Raster(SkCanvas* canvas, SkPicture::AbortCallback* callback) const { gfx::Rect canvas_playback_rect; @@ -184,14 +215,33 @@ void DisplayItemList::Raster(SkCanvas* canvas, std::vector indices; rtree_.Search(canvas_playback_rect, &indices); - for (size_t index : indices) { - RasterItem(items_[index], canvas, callback); - + for (size_t i = 0; i < indices.size(); ++i) { // We use a callback during solid color analysis on the compositor thread to // break out early. Since we're handling a sequence of pictures via rtree // query results ourselves, we have to respect the callback and early out. if (callback && callback->abort()) break; + + const DisplayItem& item = items_[indices[i]]; + // Optimize empty begin/end compositing and merge begin/draw/end compositing + // where possible. + // TODO(enne): remove empty clips here too? + // TODO(enne): does this happen recursively? Or is this good enough? + if (i < indices.size() - 2 && item.type == DisplayItem::COMPOSITING) { + const DisplayItem& second = items_[indices[i + 1]]; + const DisplayItem& third = items_[indices[i + 2]]; + if (second.type == DisplayItem::DRAWING && + third.type == DisplayItem::END_COMPOSITING) { + if (MergeAndDrawIfPossible( + static_cast(item), + static_cast(second), canvas)) { + i += 2; + continue; + } + } + } + + RasterItem(item, canvas, callback); } } diff --git a/cc/paint/display_item_list_unittest.cc b/cc/paint/display_item_list_unittest.cc index f1b9e759f9b1c3..b166229c662460 100644 --- a/cc/paint/display_item_list_unittest.cc +++ b/cc/paint/display_item_list_unittest.cc @@ -17,16 +17,17 @@ #include "cc/paint/compositing_display_item.h" #include "cc/paint/drawing_display_item.h" #include "cc/paint/filter_display_item.h" - #include "cc/paint/float_clip_display_item.h" #include "cc/paint/paint_canvas.h" #include "cc/paint/paint_flags.h" #include "cc/paint/paint_record.h" #include "cc/paint/paint_recorder.h" +#include "cc/paint/skia_paint_canvas.h" #include "cc/paint/transform_display_item.h" #include "cc/test/geometry_test_utils.h" #include "cc/test/pixel_test_utils.h" #include "cc/test/skia_common.h" +#include "cc/test/test_skcanvas.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkBitmap.h" @@ -80,6 +81,19 @@ sk_sp CreateRectPicture(const gfx::Rect& bounds) { return recorder.finishRecordingAsPicture(); } +sk_sp CreateRectPictureWithAlpha(const gfx::Rect& bounds, + uint8_t alpha) { + PaintRecorder recorder; + PaintCanvas* canvas = + recorder.beginRecording(bounds.width(), bounds.height()); + PaintFlags flags; + flags.setAlpha(alpha); + canvas->drawRect( + SkRect::MakeXYWH(bounds.x(), bounds.y(), bounds.width(), bounds.height()), + flags); + return recorder.finishRecordingAsPicture(); +} + void AppendFirstSerializationTestPicture(scoped_refptr list, const gfx::Size& layer_size) { gfx::PointF offset(2.f, 3.f); @@ -704,4 +718,110 @@ TEST(DisplayItemListTest, AppendVisualRectBlockContainingFilterNoDrawings) { EXPECT_RECT_EQ(filter_bounds, list->VisualRectForTesting(3)); } +// Verify that raster time optimizations for compositing item / draw single op / +// end compositing item can be collapsed together into a single draw op +// with the opacity from the compositing item folded in. +TEST(DisplayItemListTest, SaveDrawRestore) { + auto list = make_scoped_refptr(new DisplayItemList); + + list->CreateAndAppendPairedBeginItem( + 80, SkBlendMode::kSrcOver, nullptr, nullptr, false); + list->CreateAndAppendDrawingItem( + kVisualRect, CreateRectPictureWithAlpha(kVisualRect, 40)); + list->CreateAndAppendPairedEndItem(); + list->Finalize(); + + SaveCountingCanvas canvas; + list->Raster(&canvas, nullptr); + + EXPECT_EQ(0, canvas.save_count_); + EXPECT_EQ(0, canvas.restore_count_); + EXPECT_EQ(gfx::RectToSkRect(kVisualRect), canvas.draw_rect_); + + float expected_alpha = 80 * 40 / 255.f; + EXPECT_LE(std::abs(expected_alpha - canvas.paint_.getAlpha()), 1.f); +} + +// Verify that compositing item / end compositing item is a noop. +// Here we're testing that Skia does an optimization that skips +// save/restore with nothing in between. If skia stops doing this +// then we should reimplement this optimization in display list raster. +TEST(DisplayItemListTest, SaveRestoreNoops) { + auto list = make_scoped_refptr(new DisplayItemList); + + list->CreateAndAppendPairedBeginItem( + 80, SkBlendMode::kSrcOver, nullptr, nullptr, false); + list->CreateAndAppendPairedEndItem(); + list->CreateAndAppendPairedBeginItem( + 255, SkBlendMode::kSrcOver, nullptr, nullptr, false); + list->CreateAndAppendPairedEndItem(); + list->CreateAndAppendPairedBeginItem( + 255, SkBlendMode::kSrc, nullptr, nullptr, false); + list->CreateAndAppendPairedEndItem(); + list->Finalize(); + + SaveCountingCanvas canvas; + list->Raster(&canvas, nullptr); + + EXPECT_EQ(0, canvas.save_count_); + EXPECT_EQ(0, canvas.restore_count_); +} + +// The same as SaveDrawRestore, but with save flags that prevent the +// optimization. +TEST(DisplayItemListTest, SaveDrawRestoreFail_BadSaveFlags) { + auto list = make_scoped_refptr(new DisplayItemList); + + // Use a blend mode that's not compatible with the SaveDrawRestore + // optimization. + list->CreateAndAppendPairedBeginItem( + 80, SkBlendMode::kSrc, nullptr, nullptr, false); + list->CreateAndAppendDrawingItem( + kVisualRect, CreateRectPictureWithAlpha(kVisualRect, 40)); + list->CreateAndAppendPairedEndItem(); + list->Finalize(); + + SaveCountingCanvas canvas; + list->Raster(&canvas, nullptr); + + EXPECT_EQ(1, canvas.save_count_); + EXPECT_EQ(1, canvas.restore_count_); + EXPECT_EQ(gfx::RectToSkRect(kVisualRect), canvas.draw_rect_); + EXPECT_LE(40, canvas.paint_.getAlpha()); +} + +// The same as SaveDrawRestore, but with too many ops in the PaintRecord. +TEST(DisplayItemListTest, SaveDrawRestoreFail_TooManyOps) { + sk_sp record; + { + PaintRecorder recorder; + PaintCanvas* canvas = + recorder.beginRecording(kVisualRect.width(), kVisualRect.height()); + PaintFlags flags; + flags.setAlpha(40); + canvas->drawRect(gfx::RectToSkRect(kVisualRect), flags); + // Add an extra op here. + canvas->drawRect(gfx::RectToSkRect(kVisualRect), flags); + record = recorder.finishRecordingAsPicture(); + } + EXPECT_GT(record->approximateOpCount(), 1); + + auto list = make_scoped_refptr(new DisplayItemList); + + list->CreateAndAppendPairedBeginItem( + 80, SkBlendMode::kSrcOver, nullptr, nullptr, false); + list->CreateAndAppendDrawingItem(kVisualRect, + std::move(record)); + list->CreateAndAppendPairedEndItem(); + list->Finalize(); + + SaveCountingCanvas canvas; + list->Raster(&canvas, nullptr); + + EXPECT_EQ(1, canvas.save_count_); + EXPECT_EQ(1, canvas.restore_count_); + EXPECT_EQ(gfx::RectToSkRect(kVisualRect), canvas.draw_rect_); + EXPECT_LE(40, canvas.paint_.getAlpha()); +} + } // namespace cc diff --git a/cc/paint/paint_canvas.h b/cc/paint/paint_canvas.h index 86348f075d1101..e1fa9d0b7ad778 100644 --- a/cc/paint/paint_canvas.h +++ b/cc/paint/paint_canvas.h @@ -11,19 +11,25 @@ #include "build/build_config.h" #include "cc/paint/paint_export.h" #include "cc/paint/paint_image.h" -#include "cc/paint/paint_record.h" #include "third_party/skia/include/core/SkCanvas.h" namespace cc { class DisplayItemList; class PaintFlags; +class PaintOpBuffer; + +using PaintRecord = PaintOpBuffer; class CC_PAINT_EXPORT PaintCanvas { public: + PaintCanvas() {} virtual ~PaintCanvas() {} virtual SkMetaData& getMetaData() = 0; + + // TODO(enne): this only appears to mostly be used to determine if this is + // recording or not, so could be simplified or removed. virtual SkImageInfo imageInfo() const = 0; // TODO(enne): It would be nice to get rid of flush() entirely, as it @@ -36,7 +42,7 @@ class CC_PAINT_EXPORT PaintCanvas { virtual int save() = 0; virtual int saveLayer(const SkRect* bounds, const PaintFlags* flags) = 0; - virtual int saveLayerAlpha(const SkRect* bounds, U8CPU alpha) = 0; + virtual int saveLayerAlpha(const SkRect* bounds, uint8_t alpha) = 0; virtual void restore() = 0; virtual int getSaveCount() const = 0; @@ -87,6 +93,8 @@ class CC_PAINT_EXPORT PaintCanvas { virtual bool getDeviceClipBounds(SkIRect* bounds) const = 0; virtual void drawColor(SkColor color, SkBlendMode mode) = 0; void drawColor(SkColor color) { drawColor(color, SkBlendMode::kSrcOver); } + + // TODO(enne): This is a synonym for drawColor with kSrc. Remove it. virtual void clear(SkColor color) = 0; virtual void drawLine(SkScalar x0, @@ -180,6 +188,9 @@ class CC_PAINT_EXPORT PaintCanvas { protected: friend class PaintSurface; friend class PaintRecorder; + + private: + DISALLOW_COPY_AND_ASSIGN(PaintCanvas); }; class CC_PAINT_EXPORT PaintCanvasAutoRestore { diff --git a/cc/paint/paint_flags.cc b/cc/paint/paint_flags.cc new file mode 100644 index 00000000000000..e16a8bb8073b50 --- /dev/null +++ b/cc/paint/paint_flags.cc @@ -0,0 +1,42 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cc/paint/paint_flags.h" + +namespace cc { + +bool PaintFlags::IsSimpleOpacity() const { + uint32_t color = getColor(); + if (SK_ColorTRANSPARENT != SkColorSetA(color, SK_AlphaTRANSPARENT)) + return false; + if (!isSrcOver()) + return false; + if (getLooper()) + return false; + if (getPathEffect()) + return false; + if (getShader()) + return false; + if (getMaskFilter()) + return false; + if (getColorFilter()) + return false; + if (getImageFilter()) + return false; + return true; +} + +bool PaintFlags::SupportsFoldingAlpha() const { + if (!isSrcOver()) + return false; + if (getColorFilter()) + return false; + if (getImageFilter()) + return false; + if (getLooper()) + return false; + return true; +} + +} // namespace cc diff --git a/cc/paint/paint_flags.h b/cc/paint/paint_flags.h index b7e96c68e2ec48..37b460d6bda362 100644 --- a/cc/paint/paint_flags.h +++ b/cc/paint/paint_flags.h @@ -7,7 +7,6 @@ #include "base/compiler_specific.h" #include "cc/paint/paint_export.h" -#include "cc/paint/paint_shader.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColorFilter.h" #include "third_party/skia/include/core/SkDrawLooper.h" @@ -19,6 +18,8 @@ namespace cc { +using PaintShader = SkShader; + class CC_PAINT_EXPORT PaintFlags { public: enum Style { @@ -198,6 +199,14 @@ class CC_PAINT_EXPORT PaintFlags { return paint_.computeFastBounds(orig, storage); } + bool operator==(const PaintFlags& flags) { return flags.paint_ == paint_; } + bool operator!=(const PaintFlags& flags) { return flags.paint_ != paint_; } + + // Returns true if this just represents an opacity blend when + // used as saveLayer flags. + bool IsSimpleOpacity() const; + bool SupportsFoldingAlpha() const; + private: friend const SkPaint& ToSkPaint(const PaintFlags& flags); friend const SkPaint* ToSkPaint(const PaintFlags* flags); diff --git a/cc/paint/paint_op_buffer.cc b/cc/paint/paint_op_buffer.cc new file mode 100644 index 00000000000000..b10c2a8cd38b06 --- /dev/null +++ b/cc/paint/paint_op_buffer.cc @@ -0,0 +1,579 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cc/paint/paint_op_buffer.h" + +#include "cc/paint/display_item_list.h" +#include "cc/paint/paint_record.h" +#include "third_party/skia/include/core/SkAnnotation.h" + +namespace cc { + +#define TYPES(M) \ + M(AnnotateOp) \ + M(ClipPathOp) \ + M(ClipRectOp) \ + M(ClipRRectOp) \ + M(ConcatOp) \ + M(DrawArcOp) \ + M(DrawCircleOp) \ + M(DrawColorOp) \ + M(DrawDisplayItemListOp) \ + M(DrawDRRectOp) \ + M(DrawImageOp) \ + M(DrawImageRectOp) \ + M(DrawIRectOp) \ + M(DrawLineOp) \ + M(DrawOvalOp) \ + M(DrawPathOp) \ + M(DrawPosTextOp) \ + M(DrawRecordOp) \ + M(DrawRectOp) \ + M(DrawRRectOp) \ + M(DrawTextOp) \ + M(DrawTextBlobOp) \ + M(NoopOp) \ + M(RestoreOp) \ + M(RotateOp) \ + M(SaveOp) \ + M(SaveLayerOp) \ + M(SaveLayerAlphaOp) \ + M(ScaleOp) \ + M(SetMatrixOp) \ + M(TranslateOp) + +// Helper template to share common code for RasterWithAlpha when paint ops +// have or don't have PaintFlags. +template +struct Rasterizer { + static void Raster(const T* op, + SkCanvas* canvas, + const SkMatrix& original_ctm) { + // Paint ops with kHasPaintFlags need to declare RasterWithPaintFlags + // otherwise, the paint op needs its own Raster function. Without its + // own, this becomes an infinite loop as PaintOp::Raster calls itself. + static_assert( + !std::is_same::value, + "No Raster function"); + + op->Raster(canvas); + } + static void RasterWithAlpha(const T* op, SkCanvas* canvas, uint8_t alpha) { + DCHECK(T::kIsDrawOp); + // TODO(enne): is it ok to just drop the bounds here? + canvas->saveLayerAlpha(nullptr, alpha); + op->Raster(canvas); + canvas->restore(); + } +}; + +template +struct Rasterizer { + static void Raster(const T* op, + SkCanvas* canvas, + const SkMatrix& original_ctm) { + op->RasterWithFlags(canvas, op->flags); + } + static void RasterWithAlpha(const T* op, SkCanvas* canvas, uint8_t alpha) { + DCHECK(T::kIsDrawOp); + SkMatrix unused_matrix; + if (alpha == 255) { + Raster(op, canvas, unused_matrix); + } else if (op->flags.SupportsFoldingAlpha()) { + PaintFlags flags = op->flags; + flags.setAlpha(SkMulDiv255Round(flags.getAlpha(), alpha)); + op->RasterWithFlags(canvas, flags); + } else { + canvas->saveLayerAlpha(nullptr, alpha); + op->RasterWithFlags(canvas, op->flags); + canvas->restore(); + } + } +}; + +template <> +struct Rasterizer { + static void Raster(const SetMatrixOp* op, + SkCanvas* canvas, + const SkMatrix& original_ctm) { + op->Raster(canvas, original_ctm); + } + static void RasterWithAlpha(const SetMatrixOp* op, + SkCanvas* canvas, + uint8_t alpha) { + NOTREACHED(); + } +}; + +template <> +struct Rasterizer { + static void Raster(const DrawRecordOp* op, + SkCanvas* canvas, + const SkMatrix& original_ctm) { + op->Raster(canvas); + } + static void RasterWithAlpha(const DrawRecordOp* op, + SkCanvas* canvas, + uint8_t alpha) { + // This "looking into records" optimization is done here instead of + // in the PaintOpBuffer::Raster function as DisplayItemList calls + // into RasterWithAlpha directly. + if (op->record->approximateOpCount() == 1) { + PaintOp* single_op = op->record->GetFirstOp(); + // RasterWithAlpha only supported for draw ops. + if (single_op->IsDrawOp()) { + single_op->RasterWithAlpha(canvas, alpha); + return; + } + } + + canvas->saveLayerAlpha(nullptr, alpha); + op->Raster(canvas); + canvas->restore(); + } +}; + +// TODO(enne): partially specialize RasterWithAlpha for draw color? + +static constexpr size_t kNumOpTypes = + static_cast(PaintOpType::LastPaintOpType) + 1; + +// Verify that every op is in the TYPES macro. +#define M(T) +1 +static_assert(kNumOpTypes == TYPES(M), "Missing op in list"); +#undef M + +using RasterFunction = void (*)(const PaintOp* op, + SkCanvas* canvas, + const SkMatrix& original_ctm); +#define M(T) \ + [](const PaintOp* op, SkCanvas* canvas, const SkMatrix& original_ctm) { \ + Rasterizer::Raster(static_cast(op), \ + canvas, original_ctm); \ + }, +static const RasterFunction g_raster_functions[kNumOpTypes] = {TYPES(M)}; +#undef M + +using RasterAlphaFunction = void (*)(const PaintOp* op, + SkCanvas* canvas, + uint8_t alpha); +#define M(T) \ + T::kIsDrawOp ? \ + [](const PaintOp* op, SkCanvas* canvas, uint8_t alpha) { \ + Rasterizer::RasterWithAlpha( \ + static_cast(op), canvas, alpha); \ + } : static_cast(nullptr), +static const RasterAlphaFunction g_raster_alpha_functions[kNumOpTypes] = { + TYPES(M)}; +#undef M + +// Most state ops (matrix, clip, save, restore) have a trivial destructor. +// TODO(enne): evaluate if we need the nullptr optimization or if +// we even need to differentiate trivial destructors here. +using VoidFunction = void (*)(PaintOp* op); +#define M(T) \ + !std::is_trivially_destructible::value \ + ? [](PaintOp* op) { static_cast(op)->~T(); } \ + : static_cast(nullptr), +static const VoidFunction g_destructor_functions[kNumOpTypes] = {TYPES(M)}; +#undef M + +#define M(T) T::kIsDrawOp, +static bool g_is_draw_op[kNumOpTypes] = {TYPES(M)}; +#undef M + +#define M(T) \ + static_assert(sizeof(T) <= sizeof(LargestPaintOp), \ + #T " must be no bigger than LargestPaintOp"); +TYPES(M); +#undef M + +#define M(T) \ + static_assert(ALIGNOF(T) <= PaintOpBuffer::PaintOpAlign, \ + #T " must have alignment no bigger than PaintOpAlign"); +TYPES(M); +#undef M + +#undef TYPES + +SkRect PaintOp::kUnsetRect = {SK_ScalarInfinity, 0, 0, 0}; + +void AnnotateOp::Raster(SkCanvas* canvas) const { + switch (annotation_type) { + case PaintCanvas::AnnotationType::URL: + SkAnnotateRectWithURL(canvas, rect, data.get()); + break; + case PaintCanvas::AnnotationType::LINK_TO_DESTINATION: + SkAnnotateLinkToDestination(canvas, rect, data.get()); + break; + case PaintCanvas::AnnotationType::NAMED_DESTINATION: { + SkPoint point = SkPoint::Make(rect.x(), rect.y()); + SkAnnotateNamedDestination(canvas, point, data.get()); + break; + } + } +} + +void ClipPathOp::Raster(SkCanvas* canvas) const { + canvas->clipPath(path, op, antialias); +} + +void ClipRectOp::Raster(SkCanvas* canvas) const { + canvas->clipRect(rect, op, antialias); +} + +void ClipRRectOp::Raster(SkCanvas* canvas) const { + canvas->clipRRect(rrect, op, antialias); +} + +void ConcatOp::Raster(SkCanvas* canvas) const { + canvas->concat(matrix); +} + +void DrawArcOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawArc(oval, start_angle, sweep_angle, use_center, ToSkPaint(flags)); +} + +void DrawCircleOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawCircle(cx, cy, radius, ToSkPaint(flags)); +} + +void DrawColorOp::Raster(SkCanvas* canvas) const { + canvas->drawColor(color, mode); +} + +void DrawDisplayItemListOp::Raster(SkCanvas* canvas) const { + list->Raster(canvas, nullptr); +} + +void DrawDRRectOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawDRRect(outer, inner, ToSkPaint(flags)); +} + +void DrawImageOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawImage(image.sk_image().get(), left, top, ToSkPaint(&flags)); +} + +void DrawImageRectOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + // TODO(enne): Probably PaintCanvas should just use the skia enum directly. + SkCanvas::SrcRectConstraint skconstraint = + static_cast(constraint); + canvas->drawImageRect(image.sk_image().get(), src, dst, ToSkPaint(&flags), + skconstraint); +} + +void DrawIRectOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawIRect(rect, ToSkPaint(flags)); +} + +void DrawLineOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawLine(x0, y0, x1, y1, ToSkPaint(flags)); +} + +void DrawOvalOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawOval(oval, ToSkPaint(flags)); +} + +void DrawPathOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawPath(path, ToSkPaint(flags)); +} + +void DrawPosTextOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawPosText(GetData(), bytes, GetArray(), ToSkPaint(flags)); +} + +void DrawRecordOp::Raster(SkCanvas* canvas) const { + record->playback(canvas); +} + +void DrawRectOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawRect(rect, ToSkPaint(flags)); +} + +void DrawRRectOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawRRect(rrect, ToSkPaint(flags)); +} + +void DrawTextOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawText(GetData(), bytes, x, y, ToSkPaint(flags)); +} + +void DrawTextBlobOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + canvas->drawTextBlob(blob.get(), x, y, ToSkPaint(flags)); +} + +void RestoreOp::Raster(SkCanvas* canvas) const { + canvas->restore(); +} + +void RotateOp::Raster(SkCanvas* canvas) const { + canvas->rotate(degrees); +} + +void SaveOp::Raster(SkCanvas* canvas) const { + canvas->save(); +} + +void SaveLayerOp::RasterWithFlags(SkCanvas* canvas, + const PaintFlags& flags) const { + // See PaintOp::kUnsetRect + bool unset = bounds.left() == SK_ScalarInfinity; + + canvas->saveLayer(unset ? nullptr : &bounds, ToSkPaint(&flags)); +} + +void SaveLayerAlphaOp::Raster(SkCanvas* canvas) const { + // See PaintOp::kUnsetRect + bool unset = bounds.left() == SK_ScalarInfinity; + canvas->saveLayerAlpha(unset ? nullptr : &bounds, alpha); +} + +void ScaleOp::Raster(SkCanvas* canvas) const { + canvas->scale(sx, sy); +} + +void SetMatrixOp::Raster(SkCanvas* canvas, const SkMatrix& original_ctm) const { + canvas->setMatrix(SkMatrix::Concat(original_ctm, matrix)); +} + +void TranslateOp::Raster(SkCanvas* canvas) const { + canvas->translate(dx, dy); +} + +bool PaintOp::IsDrawOp() const { + return g_is_draw_op[type]; +} + +void PaintOp::Raster(SkCanvas* canvas, const SkMatrix& original_ctm) const { + g_raster_functions[type](this, canvas, original_ctm); +} + +void PaintOp::RasterWithAlpha(SkCanvas* canvas, uint8_t alpha) const { + g_raster_alpha_functions[type](this, canvas, alpha); +} + +int ClipPathOp::CountSlowPaths() const { + return antialias && !path.isConvex() ? 1 : 0; +} + +int DrawLineOp::CountSlowPaths() const { + if (const SkPathEffect* effect = flags.getPathEffect()) { + SkPathEffect::DashInfo info; + SkPathEffect::DashType dashType = effect->asADash(&info); + if (flags.getStrokeCap() != PaintFlags::kRound_Cap && + dashType == SkPathEffect::kDash_DashType && info.fCount == 2) { + // The PaintFlags will count this as 1, so uncount that here as + // this kind of line is special cased and not slow. + return -1; + } + } + return 0; +} + +int DrawPathOp::CountSlowPaths() const { + // This logic is copied from SkPathCounter instead of attempting to expose + // that from Skia. + if (!flags.isAntiAlias() || path.isConvex()) + return 0; + + PaintFlags::Style paintStyle = flags.getStyle(); + const SkRect& pathBounds = path.getBounds(); + if (paintStyle == PaintFlags::kStroke_Style && flags.getStrokeWidth() == 0) { + // AA hairline concave path is not slow. + return 0; + } else if (paintStyle == PaintFlags::kFill_Style && + pathBounds.width() < 64.f && pathBounds.height() < 64.f && + !path.isVolatile()) { + // AADF eligible concave path is not slow. + return 0; + } else { + return 1; + } +} + +AnnotateOp::AnnotateOp(PaintCanvas::AnnotationType annotation_type, + const SkRect& rect, + sk_sp data) + : annotation_type(annotation_type), rect(rect), data(std::move(data)) {} + +AnnotateOp::~AnnotateOp() = default; + +DrawDisplayItemListOp::DrawDisplayItemListOp( + scoped_refptr list) + : list(list) {} + +size_t DrawDisplayItemListOp::AdditionalBytesUsed() const { + return list->ApproximateMemoryUsage(); +} + +DrawDisplayItemListOp::DrawDisplayItemListOp(const DrawDisplayItemListOp& op) = + default; + +DrawDisplayItemListOp& DrawDisplayItemListOp::operator=( + const DrawDisplayItemListOp& op) = default; + +DrawDisplayItemListOp::~DrawDisplayItemListOp() = default; + +DrawImageOp::DrawImageOp(const PaintImage& image, + SkScalar left, + SkScalar top, + const PaintFlags* flags) + : image(image), + left(left), + top(top), + flags(flags ? *flags : PaintFlags()) {} + +DrawImageOp::~DrawImageOp() = default; + +DrawImageRectOp::DrawImageRectOp(const PaintImage& image, + const SkRect& src, + const SkRect& dst, + const PaintFlags* flags, + PaintCanvas::SrcRectConstraint constraint) + : image(image), + flags(flags ? *flags : PaintFlags()), + src(src), + dst(dst), + constraint(constraint) {} + +DrawImageRectOp::~DrawImageRectOp() = default; + +DrawPosTextOp::DrawPosTextOp(size_t bytes, + size_t count, + const PaintFlags& flags) + : PaintOpWithArray(bytes, count), flags(flags) {} + +DrawPosTextOp::~DrawPosTextOp() = default; + +DrawRecordOp::DrawRecordOp(sk_sp record) + : record(std::move(record)) {} + +DrawRecordOp::~DrawRecordOp() = default; + +size_t DrawRecordOp::AdditionalBytesUsed() const { + return record->approximateBytesUsed(); +} + +DrawTextBlobOp::DrawTextBlobOp(sk_sp blob, + SkScalar x, + SkScalar y, + const PaintFlags& flags) + : blob(std::move(blob)), x(x), y(y), flags(flags) {} + +DrawTextBlobOp::~DrawTextBlobOp() = default; + +PaintOpBuffer::PaintOpBuffer() : cull_rect_(SkRect::MakeEmpty()) {} + +PaintOpBuffer::PaintOpBuffer(const SkRect& cull_rect) : cull_rect_(cull_rect) {} + +PaintOpBuffer::~PaintOpBuffer() { + Reset(); +} + +void PaintOpBuffer::Reset() { + for (auto* op : Iterator(this)) { + auto func = g_destructor_functions[op->type]; + if (func) + func(op); + } + + // Leave data_ allocated, reserved_ unchanged. + used_ = 0; + op_count_ = 0; + num_slow_paths_ = 0; +} + +void PaintOpBuffer::playback(SkCanvas* canvas) const { + // TODO(enne): a PaintRecord that contains a SetMatrix assumes that the + // SetMatrix is local to that PaintRecord itself. Said differently, if you + // translate(x, y), then draw a paint record with a SetMatrix(identity), + // the translation should be preserved instead of clobbering the top level + // transform. This could probably be done more efficiently. + SkMatrix original = canvas->getTotalMatrix(); + + for (Iterator iter(this); iter; ++iter) { + // Optimize out save/restores or save/draw/restore that can be a single + // draw. See also: similar code in SkRecordOpts and cc's DisplayItemList. + // TODO(enne): consider making this recursive? + const PaintOp* op = *iter; + if (op->GetType() == PaintOpType::SaveLayerAlpha) { + const PaintOp* second = iter.peek1(); + if (second) { + if (second->GetType() == PaintOpType::Restore) { + ++iter; + continue; + } + if (second->IsDrawOp()) { + const PaintOp* third = iter.peek2(); + if (third && third->GetType() == PaintOpType::Restore) { + const SaveLayerAlphaOp* save_op = + static_cast(op); + second->RasterWithAlpha(canvas, save_op->alpha); + ++iter; + ++iter; + continue; + } + } + } + } + // TODO(enne): skip SaveLayer followed by restore with nothing in + // between, however SaveLayer with image filters on it (or maybe + // other PaintFlags options) are not a noop. Figure out what these + // are so we can skip them correctly. + + op->Raster(canvas, original); + } +} + +void PaintOpBuffer::playback(SkCanvas* canvas, + SkPicture::AbortCallback* callback) const { + // The abort callback is only used for analysis, in general, so + // this playback code can be more straightforward and not do the + // optimizations in the other function. + if (!callback) { + playback(canvas); + return; + } + + SkMatrix original = canvas->getTotalMatrix(); + + // TODO(enne): ideally callers would just iterate themselves and we + // can remove the entire notion of an abort callback. + for (auto* op : Iterator(this)) { + op->Raster(canvas, original); + if (callback && callback->abort()) + return; + } +} + +void PaintOpBuffer::ReallocBuffer(size_t new_size) { + DCHECK_GE(new_size, used_); + std::unique_ptr new_data( + static_cast(base::AlignedAlloc(new_size, PaintOpAlign))); + memcpy(new_data.get(), data_.get(), used_); + data_ = std::move(new_data); + reserved_ = new_size; +} + +void PaintOpBuffer::ShrinkToFit() { + if (!used_ || used_ == reserved_) + return; + ReallocBuffer(used_); +} + +} // namespace cc diff --git a/cc/paint/paint_op_buffer.h b/cc/paint/paint_op_buffer.h new file mode 100644 index 00000000000000..36eb1d3c48d694 --- /dev/null +++ b/cc/paint/paint_op_buffer.h @@ -0,0 +1,836 @@ +// Copyright 2017 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_PAINT_PAINT_OP_BUFFER_H_ +#define CC_PAINT_PAINT_OP_BUFFER_H_ + +#include + +#include "base/logging.h" +#include "base/memory/aligned_memory.h" +#include "cc/base/math_util.h" +#include "cc/paint/paint_canvas.h" +#include "cc/paint/paint_export.h" +#include "cc/paint/paint_flags.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/include/core/SkTextBlob.h" + +// PaintOpBuffer is a reimplementation of SkLiteDL. +// See: third_party/skia/src/core/SkLiteDL.h. + +namespace cc { + +class DisplayItemList; + +class CC_PAINT_EXPORT ThreadsafeMatrix : public SkMatrix { + public: + explicit ThreadsafeMatrix(const SkMatrix& matrix) : SkMatrix(matrix) { + (void)getType(); + } +}; + +class CC_PAINT_EXPORT ThreadsafePath : public SkPath { + public: + explicit ThreadsafePath(const SkPath& path) : SkPath(path) { + updateBoundsCache(); + } +}; + +enum class PaintOpType : uint8_t { + Annotate, + ClipPath, + ClipRect, + ClipRRect, + Concat, + DrawArc, + DrawCircle, + DrawColor, + DrawDisplayItemList, + DrawDRRect, + DrawImage, + DrawImageRect, + DrawIRect, + DrawLine, + DrawOval, + DrawPath, + DrawPosText, + DrawRecord, + DrawRect, + DrawRRect, + DrawText, + DrawTextBlob, + Noop, + Restore, + Rotate, + Save, + SaveLayer, + SaveLayerAlpha, + Scale, + SetMatrix, + Translate, + LastPaintOpType = Translate, +}; + +struct CC_PAINT_EXPORT PaintOp { + uint32_t type : 8; + uint32_t skip : 24; + + PaintOpType GetType() const { return static_cast(type); } + + void Raster(SkCanvas* canvas, const SkMatrix& original_ctm) const; + bool IsDrawOp() const; + + // Only valid for draw ops. + void RasterWithAlpha(SkCanvas* canvas, uint8_t alpha) const; + + int CountSlowPaths() const { return 0; } + + // Returns the number of bytes used by this op in referenced sub records + // and display lists. This doesn't count other objects like paths or blobs. + size_t AdditionalBytesUsed() const { return 0; } + + static constexpr bool kIsDrawOp = false; + // If an op has |kHasPaintFlags| set to true, it must: + // (1) Provide a PaintFlags member called |flags| + // (2) Provide a RasterWithFlags function instead of a Raster function. + static constexpr bool kHasPaintFlags = false; + static SkRect kUnsetRect; +}; + +struct CC_PAINT_EXPORT PaintOpWithData : PaintOp { + // Having data is just a helper for ops that have a varying amount of data and + // want a way to store that inline. This is for ops that pass in a + // void* and a length. The void* data is assumed to not have any alignment + // requirements. + explicit PaintOpWithData(size_t bytes) : bytes(bytes) {} + + // Get data out by calling paint_op_data. This can't be part of the class + // because it needs to know the size of the derived type. + size_t bytes; + + protected: + // For some derived object T, return the internally stored data. + // This needs the fully derived type to know how much to offset + // from the start of the top to the data. + template + const void* GetDataForThis(const T* op) const { + static_assert(std::is_convertible::value, + "T is not a PaintOpWithData"); + // Arbitrary data for a PaintOp is stored after the PaintOp itself + // in the PaintOpBuffer. Therefore, to access this data, it's + // pointer math to increment past the size of T. Accessing the + // next op in the buffer is ((char*)op) + op->skip, with the data + // fitting between. + return op + 1; + } + + template + void* GetDataForThis(T* op) { + return const_cast( + const_cast(this)->GetDataForThis( + const_cast(op))); + } +}; + +struct CC_PAINT_EXPORT PaintOpWithArrayBase : PaintOp {}; + +template +struct CC_PAINT_EXPORT PaintOpWithArray : PaintOpWithArrayBase { + // Paint op that has a M[count] and a char[bytes]. + // Array data is stored first so that it can be aligned with T's alignment + // with the arbitrary unaligned char data after it. + // Memory layout here is: | op | M[count] | char[bytes] | padding | next op | + // Next op is located at (char*)(op) + op->skip. + PaintOpWithArray(size_t bytes, size_t count) : bytes(bytes), count(count) {} + + size_t bytes; + size_t count; + + protected: + template + const void* GetDataForThis(const T* op) const { + static_assert(std::is_convertible::value, + "T is not a PaintOpWithData"); + const char* start_array = + reinterpret_cast(GetArrayForThis(op)); + return start_array + sizeof(M) * count; + } + + template + void* GetDataForThis(T* op) { + return const_cast( + const_cast(this)->GetDataForThis( + const_cast(op))); + } + + template + const M* GetArrayForThis(const T* op) const { + static_assert(std::is_convertible::value, + "T is not a PaintOpWithData"); + // As an optimization to not have to store an additional offset, + // assert that T has the same or more alignment requirements than M. Thus, + // if T is aligned, and M's alignment needs are a multiple of T's size, then + // M will also be aligned when placed immediately after T. + static_assert( + sizeof(T) % ALIGNOF(M) == 0, + "T must be padded such that an array of M is aligned after it"); + static_assert( + ALIGNOF(T) >= ALIGNOF(M), + "T must have not have less alignment requirements than the array data"); + return reinterpret_cast(op + 1); + } + + template + M* GetArrayForThis(T* op) { + return const_cast( + const_cast(this)->GetArrayForThis( + const_cast(op))); + } +}; + +struct CC_PAINT_EXPORT AnnotateOp final : PaintOp { + enum class AnnotationType { + URL, + LinkToDestination, + NamedDestination, + }; + + static constexpr PaintOpType kType = PaintOpType::Annotate; + AnnotateOp(PaintCanvas::AnnotationType annotation_type, + const SkRect& rect, + sk_sp data); + ~AnnotateOp(); + void Raster(SkCanvas* canvas) const; + + PaintCanvas::AnnotationType annotation_type; + SkRect rect; + sk_sp data; +}; + +struct CC_PAINT_EXPORT ClipPathOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::ClipPath; + ClipPathOp(SkPath path, SkClipOp op, bool antialias) + : path(path), op(op), antialias(antialias) {} + void Raster(SkCanvas* canvas) const; + int CountSlowPaths() const; + + ThreadsafePath path; + SkClipOp op; + bool antialias; +}; + +struct CC_PAINT_EXPORT ClipRectOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::ClipRect; + ClipRectOp(const SkRect& rect, SkClipOp op, bool antialias) + : rect(rect), op(op), antialias(antialias) {} + void Raster(SkCanvas* canvas) const; + + SkRect rect; + SkClipOp op; + bool antialias; +}; + +struct CC_PAINT_EXPORT ClipRRectOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::ClipRRect; + ClipRRectOp(const SkRRect& rrect, SkClipOp op, bool antialias) + : rrect(rrect), op(op), antialias(antialias) {} + void Raster(SkCanvas* canvas) const; + + SkRRect rrect; + SkClipOp op; + bool antialias; +}; + +struct CC_PAINT_EXPORT ConcatOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::Concat; + explicit ConcatOp(const SkMatrix& matrix) : matrix(matrix) {} + void Raster(SkCanvas* canvas) const; + + ThreadsafeMatrix matrix; +}; + +struct CC_PAINT_EXPORT DrawArcOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawArc; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawArcOp(const SkRect& oval, + SkScalar start_angle, + SkScalar sweep_angle, + bool use_center, + const PaintFlags& flags) + : oval(oval), + start_angle(start_angle), + sweep_angle(sweep_angle), + use_center(use_center), + flags(flags) {} + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + SkRect oval; + SkScalar start_angle; + SkScalar sweep_angle; + bool use_center; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawCircleOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawCircle; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawCircleOp(SkScalar cx, + SkScalar cy, + SkScalar radius, + const PaintFlags& flags) + : cx(cx), cy(cy), radius(radius), flags(flags) {} + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + SkScalar cx; + SkScalar cy; + SkScalar radius; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawColorOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawColor; + static constexpr bool kIsDrawOp = true; + DrawColorOp(SkColor color, SkBlendMode mode) : color(color), mode(mode) {} + void Raster(SkCanvas* canvas) const; + + SkColor color; + SkBlendMode mode; +}; + +struct CC_PAINT_EXPORT DrawDisplayItemListOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawDisplayItemList; + static constexpr bool kIsDrawOp = true; + explicit DrawDisplayItemListOp(scoped_refptr list); + // Windows wants to generate these when types are exported, so + // provide them here explicitly so that DisplayItemList doesn't have + // to be defined in this header. + DrawDisplayItemListOp(const DrawDisplayItemListOp& op); + DrawDisplayItemListOp& operator=(const DrawDisplayItemListOp& op); + ~DrawDisplayItemListOp(); + void Raster(SkCanvas* canvas) const; + size_t AdditionalBytesUsed() const; + // TODO(enne): DisplayItemList should know number of slow paths. + + scoped_refptr list; +}; + +struct CC_PAINT_EXPORT DrawDRRectOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawDRRect; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawDRRectOp(const SkRRect& outer, + const SkRRect& inner, + const PaintFlags& flags) + : outer(outer), inner(inner), flags(flags) {} + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + SkRRect outer; + SkRRect inner; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawImageOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawImage; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawImageOp(const PaintImage& image, + SkScalar left, + SkScalar top, + const PaintFlags* flags); + ~DrawImageOp(); + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + PaintImage image; + SkScalar left; + SkScalar top; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawImageRectOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawImageRect; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawImageRectOp(const PaintImage& image, + const SkRect& src, + const SkRect& dst, + const PaintFlags* flags, + PaintCanvas::SrcRectConstraint constraint); + ~DrawImageRectOp(); + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + PaintImage image; + PaintFlags flags; + SkRect src; + SkRect dst; + PaintCanvas::SrcRectConstraint constraint; +}; + +struct CC_PAINT_EXPORT DrawIRectOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawIRect; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawIRectOp(const SkIRect& rect, const PaintFlags& flags) + : rect(rect), flags(flags) {} + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + SkIRect rect; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawLineOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawLine; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawLineOp(SkScalar x0, + SkScalar y0, + SkScalar x1, + SkScalar y1, + const PaintFlags& flags) + : x0(x0), y0(y0), x1(x1), y1(y1), flags(flags) {} + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + int CountSlowPaths() const; + + SkScalar x0; + SkScalar y0; + SkScalar x1; + SkScalar y1; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawOvalOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawOval; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawOvalOp(const SkRect& oval, const PaintFlags& flags) + : oval(oval), flags(flags) {} + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + SkRect oval; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawPathOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawPath; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawPathOp(const SkPath& path, const PaintFlags& flags) + : path(path), flags(flags) {} + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + int CountSlowPaths() const; + + ThreadsafePath path; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawPosTextOp final : PaintOpWithArray { + static constexpr PaintOpType kType = PaintOpType::DrawPosText; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawPosTextOp(size_t bytes, size_t count, const PaintFlags& flags); + ~DrawPosTextOp(); + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + const void* GetData() const { return GetDataForThis(this); } + void* GetData() { return GetDataForThis(this); } + const SkPoint* GetArray() const { return GetArrayForThis(this); } + SkPoint* GetArray() { return GetArrayForThis(this); } + + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawRecordOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawRecord; + static constexpr bool kIsDrawOp = true; + explicit DrawRecordOp(sk_sp record); + ~DrawRecordOp(); + void Raster(SkCanvas* canvas) const; + size_t AdditionalBytesUsed() const; + + sk_sp record; +}; + +struct CC_PAINT_EXPORT DrawRectOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawRect; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawRectOp(const SkRect& rect, const PaintFlags& flags) + : rect(rect), flags(flags) {} + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + SkRect rect; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawRRectOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawRRect; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawRRectOp(const SkRRect& rrect, const PaintFlags& flags) + : rrect(rrect), flags(flags) {} + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + SkRRect rrect; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawTextOp final : PaintOpWithData { + static constexpr PaintOpType kType = PaintOpType::DrawText; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawTextOp(size_t bytes, SkScalar x, SkScalar y, const PaintFlags& flags) + : PaintOpWithData(bytes), x(x), y(y), flags(flags) {} + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + void* GetData() { return GetDataForThis(this); } + const void* GetData() const { return GetDataForThis(this); } + + SkScalar x; + SkScalar y; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT DrawTextBlobOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::DrawTextBlob; + static constexpr bool kIsDrawOp = true; + static constexpr bool kHasPaintFlags = true; + DrawTextBlobOp(sk_sp blob, + SkScalar x, + SkScalar y, + const PaintFlags& flags); + ~DrawTextBlobOp(); + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + sk_sp blob; + SkScalar x; + SkScalar y; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT NoopOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::Noop; + void Raster(SkCanvas* canvas) const {} +}; + +struct CC_PAINT_EXPORT RestoreOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::Restore; + void Raster(SkCanvas* canvas) const; +}; + +struct CC_PAINT_EXPORT RotateOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::Rotate; + explicit RotateOp(SkScalar degrees) : degrees(degrees) {} + void Raster(SkCanvas* canvas) const; + + SkScalar degrees; +}; + +struct CC_PAINT_EXPORT SaveOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::Save; + void Raster(SkCanvas* canvas) const; +}; + +struct CC_PAINT_EXPORT SaveLayerOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::SaveLayer; + static constexpr bool kHasPaintFlags = true; + SaveLayerOp(const SkRect* bounds, const PaintFlags* flags) + : bounds(bounds ? *bounds : kUnsetRect) { + if (flags) + this->flags = *flags; + } + void RasterWithFlags(SkCanvas* canvas, const PaintFlags& flags) const; + + SkRect bounds; + PaintFlags flags; +}; + +struct CC_PAINT_EXPORT SaveLayerAlphaOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::SaveLayerAlpha; + SaveLayerAlphaOp(const SkRect* bounds, uint8_t alpha) + : bounds(bounds ? *bounds : kUnsetRect), alpha(alpha) {} + void Raster(SkCanvas* canvas) const; + + SkRect bounds; + uint8_t alpha; +}; + +struct CC_PAINT_EXPORT ScaleOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::Scale; + ScaleOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {} + void Raster(SkCanvas* canvas) const; + + SkScalar sx; + SkScalar sy; +}; + +struct CC_PAINT_EXPORT SetMatrixOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::SetMatrix; + explicit SetMatrixOp(const SkMatrix& matrix) : matrix(matrix) {} + // This is the only op that needs the original ctm of the SkCanvas + // used for raster (since SetMatrix is relative to the recording origin and + // shouldn't clobber the SkCanvas raster origin). + // + // TODO(enne): Find some cleaner way to do this, possibly by making + // all SetMatrix calls Concat?? + void Raster(SkCanvas* canvas, const SkMatrix& original_ctm) const; + + ThreadsafeMatrix matrix; +}; + +struct CC_PAINT_EXPORT TranslateOp final : PaintOp { + static constexpr PaintOpType kType = PaintOpType::Translate; + TranslateOp(SkScalar dx, SkScalar dy) : dx(dx), dy(dy) {} + void Raster(SkCanvas* canvas) const; + + SkScalar dx; + SkScalar dy; +}; + +using LargestPaintOp = DrawDRRectOp; + +class CC_PAINT_EXPORT PaintOpBuffer : public SkRefCnt { + public: + enum { kInitialBufferSize = 4096 }; + // It's not necessarily the case that the op with the maximum alignment + // requirements is also the biggest op, but for now that's true. + static constexpr size_t PaintOpAlign = ALIGNOF(DrawDRRectOp); + + PaintOpBuffer(); + explicit PaintOpBuffer(const SkRect& cull_rect); + ~PaintOpBuffer() override; + + void Reset(); + + void playback(SkCanvas* canvas) const; + void playback(SkCanvas* canvas, SkPicture::AbortCallback* callback) const; + + // TODO(enne): These are no longer approximate. Rename these. + int approximateOpCount() const { return op_count_; } + size_t approximateBytesUsed() const { + return sizeof(*this) + reserved_ + subrecord_bytes_used_; + } + int numSlowPaths() const { return num_slow_paths_; } + + // Resize the PaintOpBuffer to exactly fit the current amount of used space. + void ShrinkToFit(); + + const SkRect& cullRect() const { return cull_rect_; } + + PaintOp* GetFirstOp() const { + return const_cast(first_op_.data_as()); + } + + template + void push(Args&&... args) { + static_assert(std::is_convertible::value, "T not a PaintOp."); + static_assert(!std::is_convertible::value, + "Type needs to use push_with_data"); + push_internal(0, std::forward(args)...); + } + + template + void push_with_data(const void* data, size_t bytes, Args&&... args) { + static_assert(std::is_convertible::value, + "T is not a PaintOpWithData"); + static_assert(!std::is_convertible::value, + "Type needs to use push_with_array"); + DCHECK_GE(bytes, 0u); + T* op = push_internal(bytes, bytes, std::forward(args)...); + memcpy(op->GetData(), data, bytes); + +#if DCHECK_IS_ON() + // Double check the data fits between op and next op and doesn't clobber. + char* op_start = reinterpret_cast(op); + char* op_end = op_start + sizeof(T); + char* next_op = op_start + op->skip; + char* data_start = reinterpret_cast(op->GetData()); + char* data_end = data_start + bytes; + DCHECK_GE(data_start, op_end); + DCHECK_LT(data_start, next_op); + DCHECK_LE(data_end, next_op); +#endif + } + + template + void push_with_array(const void* data, + size_t bytes, + const M* array, + size_t count, + Args&&... args) { + static_assert(std::is_convertible>::value, + "T is not a PaintOpWithArray"); + DCHECK_GE(bytes, 0u); + DCHECK_GE(count, 0u); + size_t array_size = sizeof(M) * count; + size_t total_size = bytes + array_size; + T* op = + push_internal(total_size, bytes, count, std::forward(args)...); + memcpy(op->GetData(), data, bytes); + memcpy(op->GetArray(), array, array_size); + +#if DCHECK_IS_ON() + // Double check data and array don't clobber op, next op, or each other + char* op_start = reinterpret_cast(op); + char* op_end = op_start + sizeof(T); + char* array_start = reinterpret_cast(op->GetArray()); + char* array_end = array_start + array_size; + char* data_start = reinterpret_cast(op->GetData()); + char* data_end = data_start + bytes; + char* next_op = op_start + op->skip; + DCHECK_GE(array_start, op_end); + DCHECK_LE(array_start, data_start); + DCHECK_GE(data_start, array_end); + DCHECK_LE(data_end, next_op); +#endif + } + + class Iterator { + public: + explicit Iterator(const PaintOpBuffer* buffer) + : buffer_(buffer), ptr_(buffer_->data_.get()) {} + + PaintOp* operator->() const { + return op_idx_ ? reinterpret_cast(ptr_) : buffer_->GetFirstOp(); + } + PaintOp* operator*() const { return operator->(); } + Iterator begin() { return Iterator(buffer_, buffer_->data_.get(), 0); } + Iterator end() { + return Iterator(buffer_, buffer_->data_.get() + buffer_->used_, + buffer_->approximateOpCount()); + } + bool operator!=(const Iterator& other) { + // Not valid to compare iterators on different buffers. + DCHECK_EQ(other.buffer_, buffer_); + return other.op_idx_ != op_idx_; + } + Iterator& operator++() { + if (!op_idx_++) + return *this; + PaintOp* op = **this; + uint32_t type = op->type; + CHECK_LE(type, static_cast(PaintOpType::LastPaintOpType)); + ptr_ += op->skip; + return *this; + } + operator bool() const { return op_idx_ < buffer_->approximateOpCount(); } + + int op_idx() const { return op_idx_; } + + // Return the next op without advancing the iterator, or nullptr if none. + PaintOp* peek1() const { + if (op_idx_ + 1 >= buffer_->approximateOpCount()) + return nullptr; + if (!op_idx_) + return reinterpret_cast(ptr_); + return reinterpret_cast(ptr_ + (*this)->skip); + } + + // Return the op two ops ahead without advancing the iterator, or nullptr if + // none. + PaintOp* peek2() const { + if (op_idx_ + 2 >= buffer_->approximateOpCount()) + return nullptr; + char* next = ptr_ + reinterpret_cast(ptr_)->skip; + PaintOp* next_op = reinterpret_cast(next); + if (!op_idx_) + return next_op; + return reinterpret_cast(next + next_op->skip); + } + + private: + Iterator(const PaintOpBuffer* buffer, char* ptr, int op_idx) + : buffer_(buffer), ptr_(ptr), op_idx_(op_idx) {} + + const PaintOpBuffer* buffer_ = nullptr; + char* ptr_ = nullptr; + int op_idx_ = 0; + }; + + private: + template + struct CountSlowPathsFromFlags { + static int Count(const T* op) { return 0; } + }; + + template + struct CountSlowPathsFromFlags { + static int Count(const T* op) { return op->flags.getPathEffect() ? 1 : 0; } + }; + + void ReallocBuffer(size_t new_size); + + template + T* push_internal(size_t bytes, Args&&... args) { + // Compute a skip such that all ops in the buffer are aligned to the + // maximum required alignment of all ops. + size_t skip = MathUtil::UncheckedRoundUp(sizeof(T) + bytes, PaintOpAlign); + DCHECK_LT(skip, static_cast(1) << 24); + if (used_ + skip > reserved_ || !op_count_) { + if (!op_count_) { + if (bytes) { + // Internal first_op buffer doesn't have room for extra data. + // If the op wants extra bytes, then we'll just store a Noop + // in the first_op and proceed from there. This seems unlikely + // to be a common case. + push(); + } else { + auto* op = reinterpret_cast(first_op_.data_as()); + new (op) T{std::forward(args)...}; + op->type = static_cast(T::kType); + op->skip = 0; + AnalyzeAddedOp(op); + op_count_++; + return op; + } + } + + // Start reserved_ at kInitialBufferSize and then double. + // ShrinkToFit can make this smaller afterwards. + size_t new_size = reserved_ ? reserved_ : kInitialBufferSize; + while (used_ + skip > new_size) + new_size *= 2; + ReallocBuffer(new_size); + } + DCHECK_LE(used_ + skip, reserved_); + + T* op = reinterpret_cast(data_.get() + used_); + used_ += skip; + new (op) T(std::forward(args)...); + op->type = static_cast(T::kType); + op->skip = skip; + AnalyzeAddedOp(op); + op_count_++; + return op; + } + + template + void AnalyzeAddedOp(const T* op) { + num_slow_paths_ += CountSlowPathsFromFlags::Count(op); + num_slow_paths_ += op->CountSlowPaths(); + + subrecord_bytes_used_ += op->AdditionalBytesUsed(); + } + + // As a performance optimization because n=1 is an extremely common case just + // store the first op in the PaintOpBuffer itself to avoid an extra alloc. + base::AlignedMemory first_op_; + std::unique_ptr data_; + size_t used_ = 0; + size_t reserved_ = 0; + int op_count_ = 0; + + // Record paths for veto-to-msaa for gpu raster. + int num_slow_paths_ = 0; + // Record additional bytes used by referenced sub-records and display lists. + size_t subrecord_bytes_used_ = 0; + SkRect cull_rect_; + + DISALLOW_COPY_AND_ASSIGN(PaintOpBuffer); +}; + +} // namespace cc + +#endif // CC_PAINT_PAINT_OP_BUFFER_H_ diff --git a/cc/paint/paint_op_buffer_unittest.cc b/cc/paint/paint_op_buffer_unittest.cc new file mode 100644 index 00000000000000..9f77775e509b6e --- /dev/null +++ b/cc/paint/paint_op_buffer_unittest.cc @@ -0,0 +1,420 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cc/paint/paint_op_buffer.h" +#include "cc/test/test_skcanvas.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +template +void CheckRefCnt(const T& obj, int32_t count) { +// Skia doesn't define getRefCnt in all builds. +#ifdef SK_DEBUG + EXPECT_EQ(obj->getRefCnt(), count); +#endif +} + +} // namespace + +namespace cc { + +TEST(PaintOpBufferTest, Empty) { + PaintOpBuffer buffer; + EXPECT_EQ(buffer.approximateOpCount(), 0); + EXPECT_EQ(buffer.approximateBytesUsed(), sizeof(PaintOpBuffer)); + EXPECT_EQ(PaintOpBuffer::Iterator(&buffer), false); + + buffer.Reset(); + EXPECT_EQ(buffer.approximateOpCount(), 0); + EXPECT_EQ(buffer.approximateBytesUsed(), sizeof(PaintOpBuffer)); + EXPECT_EQ(PaintOpBuffer::Iterator(&buffer), false); +} + +TEST(PaintOpBufferTest, SimpleAppend) { + SkRect rect = SkRect::MakeXYWH(2, 3, 4, 5); + PaintFlags flags; + flags.setColor(SK_ColorMAGENTA); + flags.setAlpha(100); + SkColor draw_color = SK_ColorRED; + SkBlendMode blend = SkBlendMode::kSrc; + + PaintOpBuffer buffer; + buffer.push(&rect, &flags); + buffer.push(); + buffer.push(draw_color, blend); + buffer.push(); + + EXPECT_EQ(buffer.approximateOpCount(), 4); + + PaintOpBuffer::Iterator iter(&buffer); + ASSERT_EQ(iter->GetType(), PaintOpType::SaveLayer); + SaveLayerOp* save_op = static_cast(*iter); + EXPECT_EQ(save_op->bounds, rect); + EXPECT_TRUE(save_op->flags == flags); + ++iter; + + ASSERT_EQ(iter->GetType(), PaintOpType::Save); + ++iter; + + ASSERT_EQ(iter->GetType(), PaintOpType::DrawColor); + DrawColorOp* op = static_cast(*iter); + EXPECT_EQ(op->color, draw_color); + EXPECT_EQ(op->mode, blend); + ++iter; + + ASSERT_EQ(iter->GetType(), PaintOpType::Restore); + ++iter; + + EXPECT_FALSE(iter); +} + +// PaintOpBuffer has a special case for first ops stored locally, so +// make sure that appending different kind of ops as a first op works +// properly, as well as resetting and reusing the first local op. +TEST(PaintOpBufferTest, FirstOpWithAndWithoutData) { + PaintOpBuffer buffer; + char text[] = "asdf"; + + // Use a color filter and its ref count to verify that the destructor + // is called on ops after reset. + PaintFlags flags; + sk_sp filter = + SkColorFilter::MakeModeFilter(SK_ColorMAGENTA, SkBlendMode::kSrcOver); + flags.setColorFilter(filter); + CheckRefCnt(filter, 2); + + buffer.push_with_data(text, arraysize(text), 0.f, 0.f, flags); + CheckRefCnt(filter, 3); + + // Verify that when the first op has data, which may not fit in the + // PaintRecord internal buffer, that it adds a noop as the first op + // and then appends the "op with data" into the heap buffer. + ASSERT_EQ(buffer.approximateOpCount(), 2); + EXPECT_EQ(buffer.GetFirstOp()->GetType(), PaintOpType::Noop); + + // Verify iteration behavior and brief smoke test of op state. + { + PaintOpBuffer::Iterator iter(&buffer); + PaintOp* noop = *iter; + EXPECT_EQ(buffer.GetFirstOp(), noop); + ++iter; + + PaintOp* op = *iter; + ASSERT_EQ(op->GetType(), PaintOpType::DrawText); + DrawTextOp* draw_text_op = static_cast(op); + EXPECT_EQ(draw_text_op->bytes, arraysize(text)); + + const void* data = draw_text_op->GetData(); + EXPECT_EQ(memcmp(data, text, arraysize(text)), 0); + + ++iter; + EXPECT_FALSE(iter); + } + + // Reset, verify state, and append an op that will fit in the first slot. + buffer.Reset(); + CheckRefCnt(filter, 2); + + ASSERT_EQ(buffer.approximateOpCount(), 0); + EXPECT_EQ(PaintOpBuffer::Iterator(&buffer), false); + + SkRect rect = SkRect::MakeXYWH(1, 2, 3, 4); + buffer.push(rect, flags); + CheckRefCnt(filter, 3); + + ASSERT_EQ(buffer.approximateOpCount(), 1); + EXPECT_EQ(buffer.GetFirstOp()->GetType(), PaintOpType::DrawRect); + + PaintOpBuffer::Iterator iter(&buffer); + ASSERT_EQ(iter->GetType(), PaintOpType::DrawRect); + DrawRectOp* draw_rect_op = static_cast(*iter); + EXPECT_EQ(draw_rect_op->rect, rect); + + ++iter; + EXPECT_FALSE(iter); + + buffer.Reset(); + ASSERT_EQ(buffer.approximateOpCount(), 0); + CheckRefCnt(filter, 2); +} + +TEST(PaintOpBufferTest, Peek) { + PaintOpBuffer buffer; + + uint8_t alpha = 100; + buffer.push(nullptr, alpha); + PaintFlags draw_flags; + buffer.push(SkRect::MakeXYWH(1, 2, 3, 4), draw_flags); + buffer.push(); + buffer.push(); + buffer.push(); + buffer.push(); + + PaintOpBuffer::Iterator init_iter(&buffer); + PaintOp* peek[2] = {*init_iter, init_iter.peek1()}; + + // Expect that while iterating that next = current.peek1() and that + // next.peek1() == current.peek2(). + for (PaintOpBuffer::Iterator iter(&buffer); iter; ++iter) { + EXPECT_EQ(*iter, peek[0]) << iter.op_idx(); + EXPECT_EQ(iter.peek1(), peek[1]) << iter.op_idx(); + + peek[0] = iter.peek1(); + peek[1] = iter.peek2(); + } +} + +// Verify that PaintOps with data are stored properly. +TEST(PaintOpBufferTest, PaintOpData) { + PaintOpBuffer buffer; + + buffer.push(); + PaintFlags flags; + char text1[] = "asdfasdf"; + buffer.push_with_data(text1, arraysize(text1), 0.f, 0.f, flags); + + char text2[] = "qwerty"; + buffer.push_with_data(text2, arraysize(text2), 0.f, 0.f, flags); + + ASSERT_EQ(buffer.approximateOpCount(), 3); + + // Verify iteration behavior and brief smoke test of op state. + PaintOpBuffer::Iterator iter(&buffer); + PaintOp* save_op = *iter; + EXPECT_EQ(save_op->GetType(), PaintOpType::Save); + ++iter; + + PaintOp* op1 = *iter; + ASSERT_EQ(op1->GetType(), PaintOpType::DrawText); + DrawTextOp* draw_text_op1 = static_cast(op1); + EXPECT_EQ(draw_text_op1->bytes, arraysize(text1)); + const void* data1 = draw_text_op1->GetData(); + EXPECT_EQ(memcmp(data1, text1, arraysize(text1)), 0); + ++iter; + + PaintOp* op2 = *iter; + ASSERT_EQ(op2->GetType(), PaintOpType::DrawText); + DrawTextOp* draw_text_op2 = static_cast(op2); + EXPECT_EQ(draw_text_op2->bytes, arraysize(text2)); + const void* data2 = draw_text_op2->GetData(); + EXPECT_EQ(memcmp(data2, text2, arraysize(text2)), 0); + ++iter; + + EXPECT_FALSE(iter); +} + +// Verify that PaintOps with arrays are stored properly. +TEST(PaintOpBufferTest, PaintOpArray) { + PaintOpBuffer buffer; + buffer.push(); + + // arbitrary data + std::string texts[] = {"xyz", "abcdefg", "thingerdoo"}; + SkPoint point1[] = {SkPoint::Make(1, 2), SkPoint::Make(2, 3), + SkPoint::Make(3, 4)}; + SkPoint point2[] = {SkPoint::Make(8, -12)}; + SkPoint point3[] = {SkPoint::Make(0, 0), SkPoint::Make(5, 6), + SkPoint::Make(-1, -1), SkPoint::Make(9, 9), + SkPoint::Make(50, 50), SkPoint::Make(100, 100)}; + SkPoint* points[] = {point1, point2, point3}; + size_t counts[] = {arraysize(point1), arraysize(point2), arraysize(point3)}; + + for (size_t i = 0; i < arraysize(texts); ++i) { + PaintFlags flags; + flags.setAlpha(i); + buffer.push_with_array(texts[i].c_str(), texts[i].length(), + points[i], counts[i], flags); + } + + PaintOpBuffer::Iterator iter(&buffer); + PaintOp* save_op = *iter; + EXPECT_EQ(save_op->GetType(), PaintOpType::Save); + ++iter; + + for (size_t i = 0; i < arraysize(texts); ++i) { + ASSERT_EQ(iter->GetType(), PaintOpType::DrawPosText); + DrawPosTextOp* op = static_cast(*iter); + + EXPECT_EQ(op->flags.getAlpha(), i); + + EXPECT_EQ(op->bytes, texts[i].length()); + const void* data = op->GetData(); + EXPECT_EQ(memcmp(data, texts[i].c_str(), op->bytes), 0); + + EXPECT_EQ(op->count, counts[i]); + const SkPoint* op_points = op->GetArray(); + for (size_t k = 0; k < op->count; ++k) + EXPECT_EQ(op_points[k], points[i][k]); + + ++iter; + } + + EXPECT_FALSE(iter); +} + +TEST(PaintOpBufferTest, PeekEmpty) { + PaintOpBuffer empty; + PaintOpBuffer::Iterator empty_iter(&empty); + EXPECT_EQ(nullptr, empty_iter.peek1()); + EXPECT_EQ(nullptr, empty_iter.peek2()); +} + +// Verify that a SaveLayerAlpha / Draw / Restore can be optimized to just +// a draw with opacity. +TEST(PaintOpBufferTest, SaveDrawRestore) { + PaintOpBuffer buffer; + + uint8_t alpha = 100; + buffer.push(nullptr, alpha); + + PaintFlags draw_flags; + draw_flags.setColor(SK_ColorMAGENTA); + draw_flags.setAlpha(50); + EXPECT_TRUE(draw_flags.SupportsFoldingAlpha()); + SkRect rect = SkRect::MakeXYWH(1, 2, 3, 4); + buffer.push(rect, draw_flags); + buffer.push(); + + SaveCountingCanvas canvas; + buffer.playback(&canvas); + + EXPECT_EQ(0, canvas.save_count_); + EXPECT_EQ(0, canvas.restore_count_); + EXPECT_EQ(rect, canvas.draw_rect_); + + // Expect the alpha from the draw and the save layer to be folded together. + // Since alpha is stored in a uint8_t and gets rounded, so use tolerance. + float expected_alpha = alpha * 50 / 255.f; + EXPECT_LE(std::abs(expected_alpha - canvas.paint_.getAlpha()), 1.f); +} + +// The same as SaveDrawRestore, but test that the optimization doesn't apply +// when the drawing op's flags are not compatible with being folded into the +// save layer with opacity. +TEST(PaintOpBufferTest, SaveDrawRestoreFail_BadFlags) { + PaintOpBuffer buffer; + + uint8_t alpha = 100; + buffer.push(nullptr, alpha); + + PaintFlags draw_flags; + draw_flags.setColor(SK_ColorMAGENTA); + draw_flags.setAlpha(50); + draw_flags.setBlendMode(SkBlendMode::kSrc); + EXPECT_FALSE(draw_flags.SupportsFoldingAlpha()); + SkRect rect = SkRect::MakeXYWH(1, 2, 3, 4); + buffer.push(rect, draw_flags); + buffer.push(); + + SaveCountingCanvas canvas; + buffer.playback(&canvas); + + EXPECT_EQ(1, canvas.save_count_); + EXPECT_EQ(1, canvas.restore_count_); + EXPECT_EQ(rect, canvas.draw_rect_); + EXPECT_EQ(draw_flags.getAlpha(), canvas.paint_.getAlpha()); +} + +// The same as SaveDrawRestore, but test that the optimization doesn't apply +// when there are more than one ops between the save and restore. +TEST(PaintOpBufferTest, SaveDrawRestoreFail_TooManyOps) { + PaintOpBuffer buffer; + + uint8_t alpha = 100; + buffer.push(nullptr, alpha); + + PaintFlags draw_flags; + draw_flags.setColor(SK_ColorMAGENTA); + draw_flags.setAlpha(50); + draw_flags.setBlendMode(SkBlendMode::kSrcOver); + EXPECT_TRUE(draw_flags.SupportsFoldingAlpha()); + SkRect rect = SkRect::MakeXYWH(1, 2, 3, 4); + buffer.push(rect, draw_flags); + buffer.push(); + buffer.push(); + + SaveCountingCanvas canvas; + buffer.playback(&canvas); + + EXPECT_EQ(1, canvas.save_count_); + EXPECT_EQ(1, canvas.restore_count_); + EXPECT_EQ(rect, canvas.draw_rect_); + EXPECT_EQ(draw_flags.getAlpha(), canvas.paint_.getAlpha()); +} + +// Verify that the save draw restore code works with a single op +// that's not a draw op, and the optimization does not kick in. +TEST(PaintOpBufferTest, SaveDrawRestore_SingleOpNotADrawOp) { + PaintOpBuffer buffer; + + uint8_t alpha = 100; + buffer.push(nullptr, alpha); + + buffer.push(); + buffer.push(); + + SaveCountingCanvas canvas; + buffer.playback(&canvas); + + EXPECT_EQ(1, canvas.save_count_); + EXPECT_EQ(1, canvas.restore_count_); +} + +// Test that the save/draw/restore optimization applies if the single op +// is a DrawRecord that itself has a single draw op. +TEST(PaintOpBufferTest, SaveDrawRestore_SingleOpRecordWithSingleOp) { + sk_sp record = sk_make_sp(); + + PaintFlags draw_flags; + draw_flags.setColor(SK_ColorMAGENTA); + draw_flags.setAlpha(50); + EXPECT_TRUE(draw_flags.SupportsFoldingAlpha()); + SkRect rect = SkRect::MakeXYWH(1, 2, 3, 4); + record->push(rect, draw_flags); + EXPECT_EQ(record->approximateOpCount(), 1); + + PaintOpBuffer buffer; + + uint8_t alpha = 100; + buffer.push(nullptr, alpha); + buffer.push(std::move(record)); + buffer.push(); + + SaveCountingCanvas canvas; + buffer.playback(&canvas); + + EXPECT_EQ(0, canvas.save_count_); + EXPECT_EQ(0, canvas.restore_count_); + EXPECT_EQ(rect, canvas.draw_rect_); + + float expected_alpha = alpha * 50 / 255.f; + EXPECT_LE(std::abs(expected_alpha - canvas.paint_.getAlpha()), 1.f); +} + +// The same as the above SingleOpRecord test, but the single op is not +// a draw op. So, there's no way to fold in the save layer optimization. +// Verify that the optimization doesn't apply and that this doesn't crash. +// See: http://crbug.com/712093. +TEST(PaintOpBufferTest, SaveDrawRestore_SingleOpRecordWithSingleNonDrawOp) { + sk_sp record = sk_make_sp(); + record->push(); + EXPECT_EQ(record->approximateOpCount(), 1); + EXPECT_FALSE(record->GetFirstOp()->IsDrawOp()); + + PaintOpBuffer buffer; + + uint8_t alpha = 100; + buffer.push(nullptr, alpha); + buffer.push(std::move(record)); + buffer.push(); + + SaveCountingCanvas canvas; + buffer.playback(&canvas); + + EXPECT_EQ(1, canvas.save_count_); + EXPECT_EQ(1, canvas.restore_count_); +} + +} // namespace cc diff --git a/cc/paint/paint_record.cc b/cc/paint/paint_record.cc new file mode 100644 index 00000000000000..52cb2524acc728 --- /dev/null +++ b/cc/paint/paint_record.cc @@ -0,0 +1,26 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cc/paint/paint_record.h" + +#include "cc/paint/paint_op_buffer.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" + +namespace cc { + +sk_sp ToSkPicture(sk_sp record) { + SkPictureRecorder recorder; + SkCanvas* canvas = recorder.beginRecording(record->cullRect()); + record->playback(canvas); + return recorder.finishRecordingAsPicture(); +} + +sk_sp ToSkPicture(sk_sp record) { + SkPictureRecorder recorder; + SkCanvas* canvas = recorder.beginRecording(record->cullRect()); + record->playback(canvas); + return recorder.finishRecordingAsPicture(); +} + +} // namespace cc diff --git a/cc/paint/paint_record.h b/cc/paint/paint_record.h index 8506606b59f2d7..daeee0046bf503 100644 --- a/cc/paint/paint_record.h +++ b/cc/paint/paint_record.h @@ -5,19 +5,22 @@ #ifndef CC_PAINT_PAINT_RECORD_H_ #define CC_PAINT_PAINT_RECORD_H_ +#include "cc/paint/paint_export.h" +#include "cc/paint/paint_op_buffer.h" #include "third_party/skia/include/core/SkPicture.h" namespace cc { -using PaintRecord = SkPicture; +// TODO(enne): Don't want to rename the world for this. Using these as the +// same types for now prevents an extra allocation. Probably PaintRecord +// will become an interface in the future. +using PaintRecord = PaintOpBuffer; -inline sk_sp ToSkPicture(sk_sp record) { - return record; -} +// TODO(enne): Remove these if possible, they are really expensive. +CC_PAINT_EXPORT sk_sp ToSkPicture(sk_sp record); -inline sk_sp ToSkPicture(sk_sp record) { - return record; -} +CC_PAINT_EXPORT sk_sp ToSkPicture( + sk_sp record); } // namespace cc diff --git a/cc/paint/paint_recorder.cc b/cc/paint/paint_recorder.cc index 672f0712725d4f..2ed17c74e29d02 100644 --- a/cc/paint/paint_recorder.cc +++ b/cc/paint/paint_recorder.cc @@ -4,9 +4,36 @@ #include "cc/paint/paint_recorder.h" +#include "cc/paint/paint_op_buffer.h" + namespace cc { PaintRecorder::PaintRecorder() = default; + PaintRecorder::~PaintRecorder() = default; +PaintCanvas* PaintRecorder::beginRecording(const SkRect& bounds) { + buffer_.reset(new PaintOpBuffer(bounds)); + canvas_.emplace(buffer_.get()); + return getRecordingCanvas(); +} + +sk_sp PaintRecorder::finishRecordingAsPicture() { + // SkPictureRecorder users expect that their saves are automatically + // closed for them. + // + // NOTE: Blink paint in general doesn't appear to need this, but the + // RecordingImageBufferSurface::fallBackToRasterCanvas finishing off the + // current frame depends on this. Maybe we could remove this assumption and + // just have callers do it. + canvas_->restoreToCount(1); + + // Some users (e.g. printing) use the existence of the recording canvas + // to know if recording is finished, so reset it here. + canvas_.reset(); + + buffer_->ShrinkToFit(); + return std::move(buffer_); +} + } // namespace cc diff --git a/cc/paint/paint_recorder.h b/cc/paint/paint_recorder.h index 2bbea83b981f11..7f582b851913a0 100644 --- a/cc/paint/paint_recorder.h +++ b/cc/paint/paint_recorder.h @@ -9,47 +9,36 @@ #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/optional.h" -#include "cc/paint/paint_canvas.h" #include "cc/paint/paint_record.h" -#include "cc/paint/skia_paint_canvas.h" -#include "third_party/skia/include/core/SkPictureRecorder.h" +#include "cc/paint/record_paint_canvas.h" namespace cc { +class PaintOpBuffer; + class CC_PAINT_EXPORT PaintRecorder { public: PaintRecorder(); ~PaintRecorder(); - ALWAYS_INLINE PaintCanvas* beginRecording(const SkRect& bounds) { - uint32_t record_flags = 0; - canvas_.emplace(recorder_.beginRecording(bounds, nullptr, record_flags)); - return getRecordingCanvas(); - } + PaintCanvas* beginRecording(const SkRect& bounds); - ALWAYS_INLINE PaintCanvas* beginRecording(SkScalar width, SkScalar height) { - uint32_t record_flags = 0; - canvas_.emplace( - recorder_.beginRecording(width, height, nullptr, record_flags)); - return getRecordingCanvas(); + // TODO(enne): should make everything go through the non-rect version. + // See comments in RecordPaintCanvas ctor for why. + PaintCanvas* beginRecording(SkScalar width, SkScalar height) { + return beginRecording(SkRect::MakeWH(width, height)); } // Only valid between between and finish recording. - ALWAYS_INLINE PaintCanvas* getRecordingCanvas() { + ALWAYS_INLINE RecordPaintCanvas* getRecordingCanvas() { return canvas_.has_value() ? &canvas_.value() : nullptr; } - ALWAYS_INLINE sk_sp finishRecordingAsPicture() { - sk_sp picture = recorder_.finishRecordingAsPicture(); - // Some users (e.g. printing) use the existence of the recording canvas - // to know if recording is finished, so reset it here. - canvas_.reset(); - return sk_ref_sp(static_cast(picture.get())); - } + sk_sp finishRecordingAsPicture(); private: - SkPictureRecorder recorder_; - base::Optional canvas_; + sk_sp buffer_; + base::Optional canvas_; DISALLOW_COPY_AND_ASSIGN(PaintRecorder); }; diff --git a/cc/paint/record_paint_canvas.cc b/cc/paint/record_paint_canvas.cc new file mode 100644 index 00000000000000..5e768a00de07da --- /dev/null +++ b/cc/paint/record_paint_canvas.cc @@ -0,0 +1,371 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cc/paint/record_paint_canvas.h" + +#include "base/memory/ptr_util.h" +#include "cc/paint/display_item_list.h" +#include "cc/paint/paint_op_buffer.h" +#include "cc/paint/paint_record.h" +#include "cc/paint/paint_recorder.h" +#include "third_party/skia/include/core/SkAnnotation.h" +#include "third_party/skia/include/core/SkMetaData.h" +#include "third_party/skia/include/utils/SkNWayCanvas.h" + +namespace cc { + +RecordPaintCanvas::RecordPaintCanvas(PaintOpBuffer* buffer) : buffer_(buffer) { + DCHECK(buffer_); +} + +RecordPaintCanvas::~RecordPaintCanvas() = default; + +SkMetaData& RecordPaintCanvas::getMetaData() { + // This could just be SkMetaData owned by RecordPaintCanvas, but since + // SkCanvas already has one, we might as well use it directly. + return GetCanvas()->getMetaData(); +} + +SkImageInfo RecordPaintCanvas::imageInfo() const { + return GetCanvas()->imageInfo(); +} + +void RecordPaintCanvas::flush() { + // This is a noop when recording. +} + +int RecordPaintCanvas::save() { + buffer_->push(); + return GetCanvas()->save(); +} + +int RecordPaintCanvas::saveLayer(const SkRect* bounds, + const PaintFlags* flags) { + if (flags) { + if (flags->IsSimpleOpacity()) { + // TODO(enne): maybe more callers should know this and call + // saveLayerAlpha instead of needing to check here. + uint8_t alpha = SkColorGetA(flags->getColor()); + return saveLayerAlpha(bounds, alpha); + } + + // TODO(enne): it appears that image filters affect matrices and color + // matrices affect transparent flags on SkCanvas layers, but it's not clear + // whether those are actually needed and we could just skip ToSkPaint here. + buffer_->push(bounds, flags); + const SkPaint& paint = ToSkPaint(*flags); + return GetCanvas()->saveLayer(bounds, &paint); + } + buffer_->push(bounds, flags); + return GetCanvas()->saveLayer(bounds, nullptr); +} + +int RecordPaintCanvas::saveLayerAlpha(const SkRect* bounds, uint8_t alpha) { + buffer_->push(bounds, alpha); + return GetCanvas()->saveLayerAlpha(bounds, alpha); +} + +void RecordPaintCanvas::restore() { + buffer_->push(); + GetCanvas()->restore(); +} + +int RecordPaintCanvas::getSaveCount() const { + return GetCanvas()->getSaveCount(); +} + +void RecordPaintCanvas::restoreToCount(int save_count) { + if (!canvas_) { + DCHECK_EQ(save_count, 1); + return; + } + + DCHECK_GE(save_count, 1); + int diff = GetCanvas()->getSaveCount() - save_count; + DCHECK_GE(diff, 0); + for (int i = 0; i < diff; ++i) + restore(); +} + +void RecordPaintCanvas::translate(SkScalar dx, SkScalar dy) { + buffer_->push(dx, dy); + GetCanvas()->translate(dx, dy); +} + +void RecordPaintCanvas::scale(SkScalar sx, SkScalar sy) { + buffer_->push(sx, sy); + GetCanvas()->scale(sx, sy); +} + +void RecordPaintCanvas::rotate(SkScalar degrees) { + buffer_->push(degrees); + GetCanvas()->rotate(degrees); +} + +void RecordPaintCanvas::concat(const SkMatrix& matrix) { + buffer_->push(matrix); + GetCanvas()->concat(matrix); +} + +void RecordPaintCanvas::setMatrix(const SkMatrix& matrix) { + buffer_->push(matrix); + GetCanvas()->setMatrix(matrix); +} + +void RecordPaintCanvas::clipRect(const SkRect& rect, + SkClipOp op, + bool antialias) { + buffer_->push(rect, op, antialias); + GetCanvas()->clipRect(rect, op, antialias); +} + +void RecordPaintCanvas::clipRRect(const SkRRect& rrect, + SkClipOp op, + bool antialias) { + // TODO(enne): does this happen? Should the caller know this? + if (rrect.isRect()) { + clipRect(rrect.getBounds(), op, antialias); + return; + } + buffer_->push(rrect, op, antialias); + GetCanvas()->clipRRect(rrect, op, antialias); +} + +void RecordPaintCanvas::clipPath(const SkPath& path, + SkClipOp op, + bool antialias) { + if (!path.isInverseFillType() && + GetCanvas()->getTotalMatrix().rectStaysRect()) { + // TODO(enne): do these cases happen? should the caller know that this isn't + // a path? + SkRect rect; + if (path.isRect(&rect)) { + clipRect(rect, op, antialias); + return; + } + SkRRect rrect; + if (path.isOval(&rect)) { + rrect.setOval(rect); + clipRRect(rrect, op, antialias); + return; + } + if (path.isRRect(&rrect)) { + clipRRect(rrect, op, antialias); + return; + } + } + + buffer_->push(path, op, antialias); + GetCanvas()->clipPath(path, op, antialias); + return; +} + +bool RecordPaintCanvas::quickReject(const SkRect& rect) const { + return GetCanvas()->quickReject(rect); +} + +bool RecordPaintCanvas::quickReject(const SkPath& path) const { + return GetCanvas()->quickReject(path); +} + +SkRect RecordPaintCanvas::getLocalClipBounds() const { + return GetCanvas()->getLocalClipBounds(); +} + +bool RecordPaintCanvas::getLocalClipBounds(SkRect* bounds) const { + return GetCanvas()->getLocalClipBounds(bounds); +} + +SkIRect RecordPaintCanvas::getDeviceClipBounds() const { + return GetCanvas()->getDeviceClipBounds(); +} + +bool RecordPaintCanvas::getDeviceClipBounds(SkIRect* bounds) const { + return GetCanvas()->getDeviceClipBounds(bounds); +} + +void RecordPaintCanvas::drawColor(SkColor color, SkBlendMode mode) { + buffer_->push(color, mode); +} + +void RecordPaintCanvas::clear(SkColor color) { + buffer_->push(color, SkBlendMode::kSrc); +} + +void RecordPaintCanvas::drawLine(SkScalar x0, + SkScalar y0, + SkScalar x1, + SkScalar y1, + const PaintFlags& flags) { + buffer_->push(x0, y0, x1, y1, flags); +} + +void RecordPaintCanvas::drawRect(const SkRect& rect, const PaintFlags& flags) { + buffer_->push(rect, flags); +} + +void RecordPaintCanvas::drawIRect(const SkIRect& rect, + const PaintFlags& flags) { + buffer_->push(rect, flags); +} + +void RecordPaintCanvas::drawOval(const SkRect& oval, const PaintFlags& flags) { + buffer_->push(oval, flags); +} + +void RecordPaintCanvas::drawRRect(const SkRRect& rrect, + const PaintFlags& flags) { + buffer_->push(rrect, flags); +} + +void RecordPaintCanvas::drawDRRect(const SkRRect& outer, + const SkRRect& inner, + const PaintFlags& flags) { + if (outer.isEmpty()) + return; + if (inner.isEmpty()) { + drawRRect(outer, flags); + return; + } + buffer_->push(outer, inner, flags); +} + +void RecordPaintCanvas::drawCircle(SkScalar cx, + SkScalar cy, + SkScalar radius, + const PaintFlags& flags) { + buffer_->push(cx, cy, radius, flags); +} + +void RecordPaintCanvas::drawArc(const SkRect& oval, + SkScalar start_angle, + SkScalar sweep_angle, + bool use_center, + const PaintFlags& flags) { + buffer_->push(oval, start_angle, sweep_angle, use_center, flags); +} + +void RecordPaintCanvas::drawRoundRect(const SkRect& rect, + SkScalar rx, + SkScalar ry, + const PaintFlags& flags) { + // TODO(enne): move this into base class? + if (rx > 0 && ry > 0) { + SkRRect rrect; + rrect.setRectXY(rect, rx, ry); + drawRRect(rrect, flags); + } else { + drawRect(rect, flags); + } +} + +void RecordPaintCanvas::drawPath(const SkPath& path, const PaintFlags& flags) { + buffer_->push(path, flags); +} + +void RecordPaintCanvas::drawImage(const PaintImage& image, + SkScalar left, + SkScalar top, + const PaintFlags* flags) { + buffer_->push(image, left, top, flags); +} + +void RecordPaintCanvas::drawImageRect(const PaintImage& image, + const SkRect& src, + const SkRect& dst, + const PaintFlags* flags, + SrcRectConstraint constraint) { + buffer_->push(image, src, dst, flags, constraint); +} + +void RecordPaintCanvas::drawBitmap(const SkBitmap& bitmap, + SkScalar left, + SkScalar top, + const PaintFlags* flags) { + // TODO(enne): Move into base class? + if (bitmap.drawsNothing()) + return; + drawImage(PaintImage(SkImage::MakeFromBitmap(bitmap), + PaintImage::AnimationType::UNKNOWN, + PaintImage::CompletionState::UNKNOWN), + left, top, flags); +} + +void RecordPaintCanvas::drawText(const void* text, + size_t byte_length, + SkScalar x, + SkScalar y, + const PaintFlags& flags) { + buffer_->push_with_data(text, byte_length, x, y, flags); +} + +void RecordPaintCanvas::drawPosText(const void* text, + size_t byte_length, + const SkPoint pos[], + const PaintFlags& flags) { + size_t count = ToSkPaint(flags).countText(text, byte_length); + buffer_->push_with_array(text, byte_length, pos, count, flags); +} + +void RecordPaintCanvas::drawTextBlob(sk_sp blob, + SkScalar x, + SkScalar y, + const PaintFlags& flags) { + buffer_->push(blob, x, y, flags); +} + +void RecordPaintCanvas::drawDisplayItemList( + scoped_refptr list) { + buffer_->push(list); +} + +void RecordPaintCanvas::drawPicture(sk_sp record) { + // TODO(enne): If this is small, maybe flatten it? + buffer_->push(record); +} + +bool RecordPaintCanvas::isClipEmpty() const { + return GetCanvas()->isClipEmpty(); +} + +bool RecordPaintCanvas::isClipRect() const { + return GetCanvas()->isClipRect(); +} + +const SkMatrix& RecordPaintCanvas::getTotalMatrix() const { + return GetCanvas()->getTotalMatrix(); +} + +void RecordPaintCanvas::Annotate(AnnotationType type, + const SkRect& rect, + sk_sp data) { + buffer_->push(type, rect, data); +} + +void RecordPaintCanvas::PlaybackPaintRecord(sk_sp record) { + drawPicture(record); +} + +const SkNoDrawCanvas* RecordPaintCanvas::GetCanvas() const { + return const_cast(this)->GetCanvas(); +} + +SkNoDrawCanvas* RecordPaintCanvas::GetCanvas() { + if (canvas_) + return &*canvas_; + + SkIRect rect = buffer_->cullRect().roundOut(); + canvas_.emplace(rect.right(), rect.bottom()); + + // This is part of the "recording canvases have a size, but why" dance. + // By creating a canvas of size (right x bottom) and then clipping it, + // It makes getDeviceClipBounds return the original cull rect, which code + // in GraphicsContextCanvas on Mac expects. (Just creating an SkNoDrawCanvas + // with the cull_rect makes a canvas of size (width x height) instead + // which is incorrect. SkRecorder cheats with private resetForNextCanvas. + canvas_->clipRect(SkRect::Make(rect), SkClipOp::kIntersect, false); + return &*canvas_; +} + +} // namespace cc diff --git a/cc/paint/record_paint_canvas.h b/cc/paint/record_paint_canvas.h new file mode 100644 index 00000000000000..4677512a9b88ec --- /dev/null +++ b/cc/paint/record_paint_canvas.h @@ -0,0 +1,159 @@ +// Copyright 2017 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_PAINT_RECORD_PAINT_CANVAS_H_ +#define CC_PAINT_RECORD_PAINT_CANVAS_H_ + +#include + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/macros.h" +#include "base/optional.h" +#include "build/build_config.h" +#include "cc/paint/paint_canvas.h" +#include "cc/paint/paint_flags.h" +#include "cc/paint/paint_record.h" +#include "third_party/skia/include/utils/SkNoDrawCanvas.h" + +namespace cc { + +class PaintOpBuffer; +class PaintFlags; + +class CC_PAINT_EXPORT RecordPaintCanvas final : public PaintCanvas { + public: + explicit RecordPaintCanvas(PaintOpBuffer* buffer); + ~RecordPaintCanvas() override; + + SkMetaData& getMetaData() override; + SkImageInfo imageInfo() const override; + + void flush() override; + + int save() override; + int saveLayer(const SkRect* bounds, const PaintFlags* flags) override; + int saveLayerAlpha(const SkRect* bounds, uint8_t alpha) override; + + void restore() override; + int getSaveCount() const override; + void restoreToCount(int save_count) override; + void translate(SkScalar dx, SkScalar dy) override; + void scale(SkScalar sx, SkScalar sy) override; + void rotate(SkScalar degrees) override; + void concat(const SkMatrix& matrix) override; + void setMatrix(const SkMatrix& matrix) override; + + void clipRect(const SkRect& rect, SkClipOp op, bool antialias) override; + void clipRRect(const SkRRect& rrect, SkClipOp op, bool antialias) override; + void clipPath(const SkPath& path, SkClipOp op, bool antialias) override; + bool quickReject(const SkRect& rect) const override; + bool quickReject(const SkPath& path) const override; + SkRect getLocalClipBounds() const override; + bool getLocalClipBounds(SkRect* bounds) const override; + SkIRect getDeviceClipBounds() const override; + bool getDeviceClipBounds(SkIRect* bounds) const override; + void drawColor(SkColor color, SkBlendMode mode) override; + void clear(SkColor color) override; + + void drawLine(SkScalar x0, + SkScalar y0, + SkScalar x1, + SkScalar y1, + const PaintFlags& flags) override; + void drawRect(const SkRect& rect, const PaintFlags& flags) override; + void drawIRect(const SkIRect& rect, const PaintFlags& flags) override; + void drawOval(const SkRect& oval, const PaintFlags& flags) override; + void drawRRect(const SkRRect& rrect, const PaintFlags& flags) override; + void drawDRRect(const SkRRect& outer, + const SkRRect& inner, + const PaintFlags& flags) override; + void drawCircle(SkScalar cx, + SkScalar cy, + SkScalar radius, + const PaintFlags& flags) override; + void drawArc(const SkRect& oval, + SkScalar start_angle, + SkScalar sweep_angle, + bool use_center, + const PaintFlags& flags) override; + void drawRoundRect(const SkRect& rect, + SkScalar rx, + SkScalar ry, + const PaintFlags& flags) override; + void drawPath(const SkPath& path, const PaintFlags& flags) override; + void drawImage(const PaintImage& image, + SkScalar left, + SkScalar top, + const PaintFlags* flags) override; + void drawImageRect(const PaintImage& image, + const SkRect& src, + const SkRect& dst, + const PaintFlags* flags, + SrcRectConstraint constraint) override; + void drawBitmap(const SkBitmap& bitmap, + SkScalar left, + SkScalar top, + const PaintFlags* flags) override; + + void drawText(const void* text, + size_t byte_length, + SkScalar x, + SkScalar y, + const PaintFlags& flags) override; + void drawPosText(const void* text, + size_t byte_length, + const SkPoint pos[], + const PaintFlags& flags) override; + void drawTextBlob(sk_sp blob, + SkScalar x, + SkScalar y, + const PaintFlags& flags) override; + + void drawDisplayItemList( + scoped_refptr display_item_list) override; + + void drawPicture(sk_sp record) override; + + bool isClipEmpty() const override; + bool isClipRect() const override; + const SkMatrix& getTotalMatrix() const override; + + void Annotate(AnnotationType type, + const SkRect& rect, + sk_sp data) override; + + void PlaybackPaintRecord(sk_sp record) override; + + // Don't shadow non-virtual helper functions. + using PaintCanvas::clipRect; + using PaintCanvas::clipRRect; + using PaintCanvas::clipPath; + using PaintCanvas::drawBitmap; + using PaintCanvas::drawColor; + using PaintCanvas::drawImage; + using PaintCanvas::drawPicture; + + private: + const SkNoDrawCanvas* GetCanvas() const; + SkNoDrawCanvas* GetCanvas(); + + PaintOpBuffer* buffer_; + + // TODO(enne): Although RecordPaintCanvas is mostly a write-only interface + // where paint commands are stored, occasionally users of PaintCanvas want + // to ask stateful questions mid-stream of clip and transform state. + // To avoid duplicating all this code (for now?), just forward to an SkCanvas + // that's not backed by anything but can answer these questions. + // + // This is mutable so that const functions (e.g. quickReject) that may + // lazy initialize the canvas can still be const. + mutable base::Optional canvas_; + + DISALLOW_COPY_AND_ASSIGN(RecordPaintCanvas); +}; + +} // namespace cc + +#endif // CC_PAINT_RECORD_PAINT_CANVAS_H_ diff --git a/cc/paint/skia_paint_canvas.cc b/cc/paint/skia_paint_canvas.cc index 9c1a20aed32c0f..47953bd7ccf15f 100644 --- a/cc/paint/skia_paint_canvas.cc +++ b/cc/paint/skia_paint_canvas.cc @@ -22,7 +22,6 @@ SkiaPaintCanvas::SkiaPaintCanvas(const SkBitmap& bitmap, const SkSurfaceProps& props) : canvas_(new SkCanvas(bitmap, props)), owned_(canvas_) {} -SkiaPaintCanvas::SkiaPaintCanvas(SkiaPaintCanvas&& other) = default; SkiaPaintCanvas::~SkiaPaintCanvas() = default; SkMetaData& SkiaPaintCanvas::getMetaData() { @@ -45,7 +44,7 @@ int SkiaPaintCanvas::saveLayer(const SkRect* bounds, const PaintFlags* flags) { return canvas_->saveLayer(bounds, ToSkPaint(flags)); } -int SkiaPaintCanvas::saveLayerAlpha(const SkRect* bounds, U8CPU alpha) { +int SkiaPaintCanvas::saveLayerAlpha(const SkRect* bounds, uint8_t alpha) { return canvas_->saveLayerAlpha(bounds, alpha); } diff --git a/cc/paint/skia_paint_canvas.h b/cc/paint/skia_paint_canvas.h index 8e016f0db5159c..720104df7dba40 100644 --- a/cc/paint/skia_paint_canvas.h +++ b/cc/paint/skia_paint_canvas.h @@ -28,11 +28,8 @@ class CC_PAINT_EXPORT SkiaPaintCanvas final : public PaintCanvas { explicit SkiaPaintCanvas(SkCanvas* canvas); explicit SkiaPaintCanvas(const SkBitmap& bitmap); explicit SkiaPaintCanvas(const SkBitmap& bitmap, const SkSurfaceProps& props); - explicit SkiaPaintCanvas(SkiaPaintCanvas&& other); ~SkiaPaintCanvas() override; - SkiaPaintCanvas& operator=(SkiaPaintCanvas&& other) = default; - SkMetaData& getMetaData() override; SkImageInfo imageInfo() const override; @@ -40,7 +37,7 @@ class CC_PAINT_EXPORT SkiaPaintCanvas final : public PaintCanvas { int save() override; int saveLayer(const SkRect* bounds, const PaintFlags* flags) override; - int saveLayerAlpha(const SkRect* bounds, U8CPU alpha) override; + int saveLayerAlpha(const SkRect* bounds, uint8_t alpha) override; void restore() override; int getSaveCount() const override; diff --git a/cc/test/test_skcanvas.cc b/cc/test/test_skcanvas.cc new file mode 100644 index 00000000000000..e45f42de45b109 --- /dev/null +++ b/cc/test/test_skcanvas.cc @@ -0,0 +1,26 @@ +// Copyright 2017 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/test/test_skcanvas.h" + +namespace cc { + +SaveCountingCanvas::SaveCountingCanvas() : SkNoDrawCanvas(100, 100) {} + +SkCanvas::SaveLayerStrategy SaveCountingCanvas::getSaveLayerStrategy( + const SaveLayerRec& rec) { + save_count_++; + return SkNoDrawCanvas::getSaveLayerStrategy(rec); +} + +void SaveCountingCanvas::willRestore() { + restore_count_++; +} + +void SaveCountingCanvas::onDrawRect(const SkRect& rect, const SkPaint& paint) { + draw_rect_ = rect; + paint_ = paint; +} + +} // namespace cc diff --git a/cc/test/test_skcanvas.h b/cc/test/test_skcanvas.h new file mode 100644 index 00000000000000..2b130a4ccee6e3 --- /dev/null +++ b/cc/test/test_skcanvas.h @@ -0,0 +1,31 @@ +// Copyright 2017 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_TEST_TEST_SKCANVAS_H_ +#define CC_TEST_TEST_SKCANVAS_H_ + +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/utils/SkNoDrawCanvas.h" + +namespace cc { + +class SaveCountingCanvas : public SkNoDrawCanvas { + public: + SaveCountingCanvas(); + + // Note: getSaveLayerStrategy is used as "willSave", as willSave + // is not always called. + SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override; + void willRestore() override; + void onDrawRect(const SkRect& rect, const SkPaint& paint) override; + + int save_count_ = 0; + int restore_count_ = 0; + SkRect draw_rect_; + SkPaint paint_; +}; + +} // namespace cc + +#endif // CC_TEST_TEST_SKCANVAS_H_ diff --git a/chrome/browser/ui/views/tabs/tab.cc b/chrome/browser/ui/views/tabs/tab.cc index 9935918534e08b..901375ad3a8482 100644 --- a/chrome/browser/ui/views/tabs/tab.cc +++ b/chrome/browser/ui/views/tabs/tab.cc @@ -16,6 +16,7 @@ #include "build/build_config.h" #include "cc/paint/paint_flags.h" #include "cc/paint/paint_recorder.h" +#include "cc/paint/paint_shader.h" #include "chrome/app/vector_icons/vector_icons.h" #include "chrome/browser/themes/theme_properties.h" #include "chrome/browser/ui/browser.h" diff --git a/printing/pdf_metafile_skia.cc b/printing/pdf_metafile_skia.cc index dd9149483fad62..4e154845a55aaa 100644 --- a/printing/pdf_metafile_skia.cc +++ b/printing/pdf_metafile_skia.cc @@ -12,9 +12,9 @@ #include "base/files/file.h" #include "base/memory/ptr_util.h" #include "base/time/time.h" -#include "cc/paint/paint_canvas.h" #include "cc/paint/paint_record.h" #include "cc/paint/paint_recorder.h" +#include "cc/paint/skia_paint_canvas.h" #include "printing/print_settings.h" #include "third_party/skia/include/core/SkDocument.h" #include "third_party/skia/include/core/SkStream.h" diff --git a/third_party/WebKit/LayoutTests/fast/multicol/layers-in-multicol-expected.html b/third_party/WebKit/LayoutTests/fast/multicol/layers-in-multicol-expected.html deleted file mode 100644 index 97d5761f8c1847..00000000000000 --- a/third_party/WebKit/LayoutTests/fast/multicol/layers-in-multicol-expected.html +++ /dev/null @@ -1,87 +0,0 @@ - - -

- Test layers which are fully contained within a single column. -

-LTR: -
-
- line1
- line2
- line3
- line4
- line5
-
-
- line6
-
line7
- line8
- relative9
- line10
-
-
- line11
- line12
- - opacity13
- line14 -
-
- -RTL: -
-
- line1
- line2
- line3
- line4
- line5
-
-
- line6
-
line7
- line8
- relative9
- line10
-
-
- line11
- line12
- - opacity13
- line14 -
-
diff --git a/third_party/WebKit/LayoutTests/fast/multicol/layers-split-across-columns-expected.html b/third_party/WebKit/LayoutTests/fast/multicol/layers-split-across-columns-expected.html deleted file mode 100644 index 8bbd9203195f84..00000000000000 --- a/third_party/WebKit/LayoutTests/fast/multicol/layers-split-across-columns-expected.html +++ /dev/null @@ -1,90 +0,0 @@ - - -
- Overflow: -
-
-
-
-
-
-
-
-
-
- Transforms: -
-
-
-
-
-
-
-
-
-
- Relative Pos.: -
-
-
-
-
-
-
-
-
-
- Opacity: -
-
- -
 
-
 
-
 
-
 
-
-
-
diff --git a/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.html b/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.html deleted file mode 100644 index 0b67873e3ec45d..00000000000000 --- a/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.html +++ /dev/null @@ -1,6 +0,0 @@ -
- -
 
-
- diff --git a/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.png b/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.png new file mode 100644 index 00000000000000..1ebb6e84dfd6e4 Binary files /dev/null and b/third_party/WebKit/LayoutTests/fast/multicol/transform-inside-opacity-expected.png differ diff --git a/third_party/WebKit/LayoutTests/paint/invalidation/column-float-under-stacked-inline-expected.png b/third_party/WebKit/LayoutTests/paint/invalidation/column-float-under-stacked-inline-expected.png index a92e6a80d40998..c6cabcbb87f769 100644 Binary files a/third_party/WebKit/LayoutTests/paint/invalidation/column-float-under-stacked-inline-expected.png and b/third_party/WebKit/LayoutTests/paint/invalidation/column-float-under-stacked-inline-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-in-multicol-expected.png b/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-in-multicol-expected.png new file mode 100644 index 00000000000000..44fc4a4d57bde7 Binary files /dev/null and b/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-in-multicol-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-split-across-columns-expected.png b/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-split-across-columns-expected.png new file mode 100644 index 00000000000000..27fbf585fd97a8 Binary files /dev/null and b/third_party/WebKit/LayoutTests/platform/linux/fast/multicol/layers-split-across-columns-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png index b024e71195d6b2..5e1fff8a814199 100644 Binary files a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png and b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/tabgroup-expected.png index 47485d6107c069..ab92965539b692 100644 Binary files a/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/tabgroup-expected.png and b/third_party/WebKit/LayoutTests/platform/linux/paint/invalidation/svg/tabgroup-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/linux/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/linux/transforms/2d/hindi-rotated-expected.png index 8d5f6fd310ff70..6f98684067cbe0 100644 Binary files a/third_party/WebKit/LayoutTests/platform/linux/transforms/2d/hindi-rotated-expected.png and b/third_party/WebKit/LayoutTests/platform/linux/transforms/2d/hindi-rotated-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png index b024e71195d6b2..5e1fff8a814199 100644 Binary files a/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png and b/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png index 47485d6107c069..ab92965539b692 100644 Binary files a/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png and b/third_party/WebKit/LayoutTests/platform/linux/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/transforms/2d/hindi-rotated-expected.png index 3a881330927711..0acea108d73370 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac-mac10.10/transforms/2d/hindi-rotated-expected.png and b/third_party/WebKit/LayoutTests/platform/mac-mac10.10/transforms/2d/hindi-rotated-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png index 07e019ce5d0c4b..991aaf7a6235b1 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png and b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/2d/hindi-rotated-expected.png index 11b5d71b880509..84b89da888d0d5 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/2d/hindi-rotated-expected.png and b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/2d/hindi-rotated-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/transformed-focused-text-input-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/transformed-focused-text-input-expected.png index f409f9ea71e380..1693941d4db7f6 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/transformed-focused-text-input-expected.png and b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/transforms/transformed-focused-text-input-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png index 07e019ce5d0c4b..991aaf7a6235b1 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png and b/third_party/WebKit/LayoutTests/platform/mac-mac10.9/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-in-multicol-expected.png b/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-in-multicol-expected.png new file mode 100644 index 00000000000000..9243efa337dd12 Binary files /dev/null and b/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-in-multicol-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-split-across-columns-expected.png b/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-split-across-columns-expected.png new file mode 100644 index 00000000000000..86cf37958c354b Binary files /dev/null and b/third_party/WebKit/LayoutTests/platform/mac/fast/multicol/layers-split-across-columns-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png index 56d7e380c1ebcb..af00c64c04d28a 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png and b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/tabgroup-expected.png index 300a55b1436493..aa3d62632bbfae 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/tabgroup-expected.png and b/third_party/WebKit/LayoutTests/platform/mac/paint/invalidation/svg/tabgroup-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac/svg/as-image/img-preserveAspectRatio-support-1-expected.png b/third_party/WebKit/LayoutTests/platform/mac/svg/as-image/img-preserveAspectRatio-support-1-expected.png index 7e1243a1320acf..d5d8ba499b1b26 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac/svg/as-image/img-preserveAspectRatio-support-1-expected.png and b/third_party/WebKit/LayoutTests/platform/mac/svg/as-image/img-preserveAspectRatio-support-1-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/mac/transforms/2d/hindi-rotated-expected.png index 497919fc2445c1..70f55312ac0bc1 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac/transforms/2d/hindi-rotated-expected.png and b/third_party/WebKit/LayoutTests/platform/mac/transforms/2d/hindi-rotated-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac/transforms/transformed-focused-text-input-expected.png b/third_party/WebKit/LayoutTests/platform/mac/transforms/transformed-focused-text-input-expected.png index 38e68702eb61e0..c57f417bb49436 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac/transforms/transformed-focused-text-input-expected.png and b/third_party/WebKit/LayoutTests/platform/mac/transforms/transformed-focused-text-input-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png index 56d7e380c1ebcb..af00c64c04d28a 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png and b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png index 300a55b1436493..aa3d62632bbfae 100644 Binary files a/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png and b/third_party/WebKit/LayoutTests/platform/mac/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-in-multicol-expected.png b/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-in-multicol-expected.png new file mode 100644 index 00000000000000..62b98eac919ef3 Binary files /dev/null and b/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-in-multicol-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-split-across-columns-expected.png b/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-split-across-columns-expected.png new file mode 100644 index 00000000000000..608b610872a1cc Binary files /dev/null and b/third_party/WebKit/LayoutTests/platform/win/fast/multicol/layers-split-across-columns-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png index 8829276efb033d..6eb1bf65778445 100644 Binary files a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png and b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/tabgroup-expected.png index a281c42ba6ce36..7733c1942676e8 100644 Binary files a/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/tabgroup-expected.png and b/third_party/WebKit/LayoutTests/platform/win/paint/invalidation/svg/tabgroup-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/win/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/win/transforms/2d/hindi-rotated-expected.png index 6837fedd5f9e26..5b597ed7b74e54 100644 Binary files a/third_party/WebKit/LayoutTests/platform/win/transforms/2d/hindi-rotated-expected.png and b/third_party/WebKit/LayoutTests/platform/win/transforms/2d/hindi-rotated-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png index 8829276efb033d..6eb1bf65778445 100644 Binary files a/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png and b/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png b/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png index a281c42ba6ce36..7733c1942676e8 100644 Binary files a/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png and b/third_party/WebKit/LayoutTests/platform/win/virtual/disable-spinvalidation/paint/invalidation/svg/tabgroup-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/win7/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/win7/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png index bbb82789778964..27dad6f34feab3 100644 Binary files a/third_party/WebKit/LayoutTests/platform/win7/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png and b/third_party/WebKit/LayoutTests/platform/win7/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/win7/transforms/2d/hindi-rotated-expected.png b/third_party/WebKit/LayoutTests/platform/win7/transforms/2d/hindi-rotated-expected.png index 7e9778db23b6a9..855bfdf83eb3f6 100644 Binary files a/third_party/WebKit/LayoutTests/platform/win7/transforms/2d/hindi-rotated-expected.png and b/third_party/WebKit/LayoutTests/platform/win7/transforms/2d/hindi-rotated-expected.png differ diff --git a/third_party/WebKit/LayoutTests/platform/win7/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png b/third_party/WebKit/LayoutTests/platform/win7/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png index bbb82789778964..27dad6f34feab3 100644 Binary files a/third_party/WebKit/LayoutTests/platform/win7/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png and b/third_party/WebKit/LayoutTests/platform/win7/virtual/disable-spinvalidation/paint/invalidation/svg/scrolling-embedded-svg-file-image-repaint-problem-expected.png differ diff --git a/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp b/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp index c72135b1eac9a7..a57d2f7ce6434e 100644 --- a/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp +++ b/third_party/WebKit/Source/core/svg/graphics/SVGImage.cpp @@ -365,7 +365,7 @@ bool SVGImage::ApplyShaderInternal(PaintFlags& flags, FloatRect float_bounds(FloatPoint(), size); const SkRect bounds(float_bounds); - flags.setShader(SkShader::MakePictureShader( + flags.setShader(MakePaintShaderRecord( PaintRecordForCurrentFrame(float_bounds, url), SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &local_matrix, &bounds)); diff --git a/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp b/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp index 75f9fdd2052be7..a215a535327924 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp +++ b/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.cpp @@ -23,11 +23,12 @@ void BeginClipPathDisplayItem::AppendToWebDisplayItemList( list->AppendClipPathItem(clip_path_, true); } -void BeginClipPathDisplayItem::AnalyzeForGpuRasterization( - SkPictureGpuAnalyzer& analyzer) const { +int BeginClipPathDisplayItem::NumberOfSlowPaths() const { // Temporarily disabled (pref regressions due to GPU veto stickiness: // http://crbug.com/603969). // analyzer.analyzeClipPath(m_clipPath, SkRegion::kIntersect_Op, true); + // TODO(enne): fixup this code to return an int. + return 0; } void EndClipPathDisplayItem::Replay(GraphicsContext& context) const { diff --git a/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.h b/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.h index 42434aefaad48e..3952c877c51a57 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.h +++ b/third_party/WebKit/Source/platform/graphics/paint/ClipPathDisplayItem.h @@ -24,7 +24,7 @@ class PLATFORM_EXPORT BeginClipPathDisplayItem final void AppendToWebDisplayItemList(const IntRect&, WebDisplayItemList*) const override; - void AnalyzeForGpuRasterization(SkPictureGpuAnalyzer&) const override; + int NumberOfSlowPaths() const override; private: #ifndef NDEBUG diff --git a/third_party/WebKit/Source/platform/graphics/paint/CompositingRecorder.cpp b/third_party/WebKit/Source/platform/graphics/paint/CompositingRecorder.cpp index cdcd8fd07f413c..7eac3491469a70 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/CompositingRecorder.cpp +++ b/third_party/WebKit/Source/platform/graphics/paint/CompositingRecorder.cpp @@ -29,58 +29,8 @@ CompositingRecorder::CompositingRecorder(GraphicsContext& graphics_context, CompositingRecorder::~CompositingRecorder() { if (RuntimeEnabledFeatures::slimmingPaintV2Enabled()) return; - // If the end of the current display list is of the form - // [BeginCompositingDisplayItem] [DrawingDisplayItem], then fold the - // BeginCompositingDisplayItem into a new DrawingDisplayItem that replaces - // them both. This allows Skia to optimize for the case when the - // BeginCompositingDisplayItem represents a simple opacity/color that can be - // merged into the opacity/color of the drawing. See crbug.com/628831 for more - // details. - PaintController& paint_controller = graphics_context_.GetPaintController(); - const DisplayItem* last_display_item = paint_controller.LastDisplayItem(0); - const DisplayItem* second_to_last_display_item = - paint_controller.LastDisplayItem(1); - // TODO(chrishtr): remove the call to LastDisplayItemIsSubsequenceEnd when - // https://codereview.chromium.org/2768143002 lands. - if (!RuntimeEnabledFeatures::slimmingPaintV2Enabled() && last_display_item && - second_to_last_display_item && last_display_item->DrawsContent() && - second_to_last_display_item->GetType() == - DisplayItem::kBeginCompositing && - !paint_controller.LastDisplayItemIsSubsequenceEnd()) { - FloatRect cull_rect( - ((DrawingDisplayItem*)last_display_item)->GetPaintRecord()->cullRect()); - const DisplayItemClient& display_item_client = last_display_item->Client(); - DisplayItem::Type display_item_type = last_display_item->GetType(); - - // Re-record the last two DisplayItems into a new drawing. The new item - // cannot be cached, because it is a mutation of the DisplayItem the client - // thought it was painting. - paint_controller.BeginSkippingCache(); - { -#if DCHECK_IS_ON() - // In the recorder's scope we remove the last two display items which - // are combined into a new drawing. - DisableListModificationCheck disabler; -#endif - DrawingRecorder new_recorder(graphics_context_, display_item_client, - display_item_type, cull_rect); - DCHECK(!DrawingRecorder::UseCachedDrawingIfPossible( - graphics_context_, display_item_client, display_item_type)); - - second_to_last_display_item->Replay(graphics_context_); - last_display_item->Replay(graphics_context_); - EndCompositingDisplayItem(client_).Replay(graphics_context_); - - // Remove the DrawingDisplayItem. - paint_controller.RemoveLastDisplayItem(); - // Remove the BeginCompositingDisplayItem. - paint_controller.RemoveLastDisplayItem(); - } - paint_controller.EndSkippingCache(); - } else { - graphics_context_.GetPaintController().EndItem( - client_); - } + graphics_context_.GetPaintController().EndItem( + client_); } } // namespace blink diff --git a/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h b/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h index a9afcd15e08904..d5f344e605f4b5 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h +++ b/third_party/WebKit/Source/platform/graphics/paint/DisplayItem.h @@ -17,8 +17,6 @@ #include "platform/wtf/text/WTFString.h" #endif -class SkPictureGpuAnalyzer; - namespace blink { class GraphicsContext; @@ -337,7 +335,7 @@ class PLATFORM_EXPORT DisplayItem { virtual bool DrawsContent() const { return false; } // Override to implement specific analysis strategies. - virtual void AnalyzeForGpuRasterization(SkPictureGpuAnalyzer&) const {} + virtual int NumberOfSlowPaths() const { return 0; } #ifndef NDEBUG static WTF::String TypeAsDebugString(DisplayItem::Type); diff --git a/third_party/WebKit/Source/platform/graphics/paint/DisplayItemList.cpp b/third_party/WebKit/Source/platform/graphics/paint/DisplayItemList.cpp index 8691f50c6614a5..694762a2d4c9fb 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/DisplayItemList.cpp +++ b/third_party/WebKit/Source/platform/graphics/paint/DisplayItemList.cpp @@ -7,7 +7,6 @@ #include "platform/graphics/LoggingCanvas.h" #include "platform/graphics/paint/DrawingDisplayItem.h" #include "platform/graphics/paint/PaintChunk.h" -#include "third_party/skia/include/core/SkPictureAnalyzer.h" #ifndef NDEBUG #include "platform/wtf/text/WTFString.h" diff --git a/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.cpp b/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.cpp index d88ea7c99cf429..6a76f3be319f87 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.cpp +++ b/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.cpp @@ -10,7 +10,6 @@ #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkData.h" -#include "third_party/skia/include/core/SkPictureAnalyzer.h" namespace blink { @@ -30,14 +29,8 @@ bool DrawingDisplayItem::DrawsContent() const { return record_.get(); } -void DrawingDisplayItem::AnalyzeForGpuRasterization( - SkPictureGpuAnalyzer& analyzer) const { - // TODO(enne): Need an SkPictureGpuAnalyzer on PictureRecord. - // This is a bit overkill to ToSkPicture a record just to get - // numSlowPaths. - if (!record_) - return; - analyzer.analyzePicture(ToSkPicture(record_).get()); +int DrawingDisplayItem::NumberOfSlowPaths() const { + return record_ ? record_->numSlowPaths() : 0; } #ifndef NDEBUG diff --git a/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.h b/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.h index e5f989f8cea2c5..3dedbcbdd54c3c 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.h +++ b/third_party/WebKit/Source/platform/graphics/paint/DrawingDisplayItem.h @@ -41,7 +41,7 @@ class PLATFORM_EXPORT DrawingDisplayItem final : public DisplayItem { return known_to_be_opaque_; } - void AnalyzeForGpuRasterization(SkPictureGpuAnalyzer&) const override; + int NumberOfSlowPaths() const override; private: #ifndef NDEBUG diff --git a/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp b/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp index a703076d843449..0f9980266fa559 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp +++ b/third_party/WebKit/Source/platform/graphics/paint/PaintController.cpp @@ -16,6 +16,8 @@ #include #endif +static constexpr int kMaxNumberOfSlowPathsBeforeVeto = 5; + namespace blink { void PaintController::SetTracksRasterInvalidations(bool value) { @@ -536,7 +538,7 @@ void PaintController::CommitNewDisplayItems( !new_display_item_list_.IsEmpty()) GenerateChunkRasterInvalidationRects(new_paint_chunks_.LastChunk()); - SkPictureGpuAnalyzer gpu_analyzer; + int num_slow_paths = 0; current_cache_generation_ = DisplayItemClient::CacheGenerationOrInvalidationReason::Next(); @@ -555,8 +557,8 @@ void PaintController::CommitNewDisplayItems( Vector skipped_cache_clients; for (const auto& item : new_display_item_list_) { // No reason to continue the analysis once we have a veto. - if (gpu_analyzer.suitableForGpuRasterization()) - item.AnalyzeForGpuRasterization(gpu_analyzer); + if (num_slow_paths <= kMaxNumberOfSlowPathsBeforeVeto) + num_slow_paths += item.NumberOfSlowPaths(); // TODO(wkorman): Only compute and append visual rect for drawings. new_display_item_list_.AppendVisualRect( @@ -594,7 +596,7 @@ void PaintController::CommitNewDisplayItems( new_display_item_list_.ShrinkToFit(); current_paint_artifact_ = PaintArtifact( std::move(new_display_item_list_), new_paint_chunks_.ReleasePaintChunks(), - gpu_analyzer.suitableForGpuRasterization()); + num_slow_paths <= kMaxNumberOfSlowPathsBeforeVeto); ResetCurrentListIndices(); out_of_order_item_indices_.clear(); out_of_order_chunk_indices_.clear(); diff --git a/third_party/WebKit/Source/platform/graphics/paint/PaintControllerTest.cpp b/third_party/WebKit/Source/platform/graphics/paint/PaintControllerTest.cpp index c497fbade374ab..9904efebe90c1d 100644 --- a/third_party/WebKit/Source/platform/graphics/paint/PaintControllerTest.cpp +++ b/third_party/WebKit/Source/platform/graphics/paint/PaintControllerTest.cpp @@ -2269,40 +2269,6 @@ class PaintControllerUnderInvalidationTest } GetPaintController().CommitNewDisplayItems(); -#if CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS - DisplayItemClient::EndShouldKeepAliveAllClients(); -#endif - } - - void TestFoldCompositingDrawingInSubsequence() { - FakeDisplayItemClient container("container"); - FakeDisplayItemClient content("content"); - GraphicsContext context(GetPaintController()); - - { - SubsequenceRecorder subsequence(context, container); - CompositingRecorder compositing(context, content, SkBlendMode::kSrc, 0.5); - DrawRect(context, content, kBackgroundDrawingType, - FloatRect(100, 100, 300, 300)); - } - GetPaintController().CommitNewDisplayItems(); - EXPECT_EQ( - 1u, - GetPaintController().GetPaintArtifact().GetDisplayItemList().size()); - - { - EXPECT_FALSE(SubsequenceRecorder::UseCachedSubsequenceIfPossible( - context, container)); - SubsequenceRecorder subsequence(context, container); - CompositingRecorder compositing(context, content, SkBlendMode::kSrc, 0.5); - DrawRect(context, content, kBackgroundDrawingType, - FloatRect(100, 100, 300, 300)); - } - GetPaintController().CommitNewDisplayItems(); - EXPECT_EQ( - 1u, - GetPaintController().GetPaintArtifact().GetDisplayItemList().size()); - #if CHECK_DISPLAY_ITEM_CLIENT_ALIVENESS DisplayItemClient::EndShouldKeepAliveAllClients(); #endif @@ -2356,11 +2322,6 @@ TEST_F(PaintControllerUnderInvalidationTest, InvalidationInSubsequence) { TestInvalidationInSubsequence(); } -TEST_F(PaintControllerUnderInvalidationTest, - FoldCompositingDrawingInSubsequence) { - TestFoldCompositingDrawingInSubsequence(); -} - #endif // defined(GTEST_HAS_DEATH_TEST) && !OS(ANDROID) } // namespace blink diff --git a/third_party/WebKit/Source/platform/mac/LocalCurrentGraphicsContext.mm b/third_party/WebKit/Source/platform/mac/LocalCurrentGraphicsContext.mm index 6379d6ada991bd..e3eada5229789c 100644 --- a/third_party/WebKit/Source/platform/mac/LocalCurrentGraphicsContext.mm +++ b/third_party/WebKit/Source/platform/mac/LocalCurrentGraphicsContext.mm @@ -24,6 +24,7 @@ #include "platform/graphics/paint/PaintCanvas.h" #include "platform/mac/ThemeMac.h" #include "platform_canvas.h" +#include "third_party/skia/include/core/SkRegion.h" namespace blink { diff --git a/ui/gfx/canvas.cc b/ui/gfx/canvas.cc index 31ef57fbd294b0..80eeda5a61c5f5 100644 --- a/ui/gfx/canvas.cc +++ b/ui/gfx/canvas.cc @@ -584,7 +584,7 @@ cc::PaintCanvas* Canvas::CreateOwnedCanvas(const Size& size, bool is_opaque) { // Ensure that the bitmap is zeroed, since the code expects that. memset(bitmap_->getPixels(), 0, bitmap_->getSafeSize()); - owned_canvas_ = cc::SkiaPaintCanvas(bitmap_.value()); + owned_canvas_.emplace(bitmap_.value()); return &owned_canvas_.value(); } diff --git a/ui/views/controls/scrollbar/cocoa_scroll_bar.mm b/ui/views/controls/scrollbar/cocoa_scroll_bar.mm index c77bdf4b525e49..ce75522de52602 100644 --- a/ui/views/controls/scrollbar/cocoa_scroll_bar.mm +++ b/ui/views/controls/scrollbar/cocoa_scroll_bar.mm @@ -5,6 +5,7 @@ #import "ui/views/controls/scrollbar/cocoa_scroll_bar.h" #import "base/mac/sdk_forward_declarations.h" +#include "cc/paint/paint_shader.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/effects/SkGradientShader.h" #include "ui/compositor/layer.h"