diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 7ba3273d60c11..b3bb258f3faad 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -36,6 +36,14 @@ FILE: ../../../flutter/flow/compositor_context.cc FILE: ../../../flutter/flow/compositor_context.h FILE: ../../../flutter/flow/diff_context.cc FILE: ../../../flutter/flow/diff_context.h +FILE: ../../../flutter/flow/display_list.cc +FILE: ../../../flutter/flow/display_list.h +FILE: ../../../flutter/flow/display_list_canvas.cc +FILE: ../../../flutter/flow/display_list_canvas.h +FILE: ../../../flutter/flow/display_list_canvas_unittests.cc +FILE: ../../../flutter/flow/display_list_unittests.cc +FILE: ../../../flutter/flow/display_list_utils.cc +FILE: ../../../flutter/flow/display_list_utils.h FILE: ../../../flutter/flow/embedded_view_params_unittests.cc FILE: ../../../flutter/flow/embedded_views.cc FILE: ../../../flutter/flow/embedded_views.h @@ -67,6 +75,9 @@ FILE: ../../../flutter/flow/layers/color_filter_layer_unittests.cc FILE: ../../../flutter/flow/layers/container_layer.cc FILE: ../../../flutter/flow/layers/container_layer.h FILE: ../../../flutter/flow/layers/container_layer_unittests.cc +FILE: ../../../flutter/flow/layers/display_list_layer.cc +FILE: ../../../flutter/flow/layers/display_list_layer.h +FILE: ../../../flutter/flow/layers/display_list_layer_unittests.cc FILE: ../../../flutter/flow/layers/image_filter_layer.cc FILE: ../../../flutter/flow/layers/image_filter_layer.h FILE: ../../../flutter/flow/layers/image_filter_layer_unittests.cc diff --git a/common/settings.h b/common/settings.h index 4b9ff999dc541..cc496285ba771 100644 --- a/common/settings.h +++ b/common/settings.h @@ -163,6 +163,9 @@ struct Settings { // Selects the SkParagraph implementation of the text layout engine. bool enable_skparagraph = false; + // Selects the DisplayList for storage of rendering operations. + bool enable_display_list = false; + // All shells in the process share the same VM. The last shell to shutdown // should typically shut down the VM as well. However, applications depend on // the behavior of "warming-up" the VM by creating a shell that does not do diff --git a/flow/BUILD.gn b/flow/BUILD.gn index 29df9651107a5..b6c881f902f7d 100644 --- a/flow/BUILD.gn +++ b/flow/BUILD.gn @@ -12,6 +12,12 @@ source_set("flow") { "compositor_context.h", "diff_context.cc", "diff_context.h", + "display_list.cc", + "display_list.h", + "display_list_canvas.cc", + "display_list_canvas.h", + "display_list_utils.cc", + "display_list_utils.h", "embedded_views.cc", "embedded_views.h", "frame_timings.cc", @@ -30,6 +36,8 @@ source_set("flow") { "layers/color_filter_layer.h", "layers/container_layer.cc", "layers/container_layer.h", + "layers/display_list_layer.cc", + "layers/display_list_layer.h", "layers/image_filter_layer.cc", "layers/image_filter_layer.h", "layers/layer.cc", @@ -123,6 +131,8 @@ if (enable_unittests) { testonly = true sources = [ + "display_list_canvas_unittests.cc", + "display_list_unittests.cc", "embedded_view_params_unittests.cc", "flow_run_all_unittests.cc", "flow_test_utils.cc", @@ -136,6 +146,7 @@ if (enable_unittests) { "layers/clip_rrect_layer_unittests.cc", "layers/color_filter_layer_unittests.cc", "layers/container_layer_unittests.cc", + "layers/display_list_layer_unittests.cc", "layers/image_filter_layer_unittests.cc", "layers/layer_tree_unittests.cc", "layers/opacity_layer_unittests.cc", diff --git a/flow/display_list.cc b/flow/display_list.cc new file mode 100644 index 0000000000000..1390a161f90e5 --- /dev/null +++ b/flow/display_list.cc @@ -0,0 +1,1368 @@ +// Copyright 2013 The Flutter 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 + +#include "flutter/flow/display_list.h" +#include "flutter/flow/display_list_canvas.h" +#include "flutter/flow/display_list_utils.h" +#include "flutter/fml/logging.h" + +#include "third_party/skia/include/core/SkImageFilter.h" +#include "third_party/skia/include/core/SkMaskFilter.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkRSXform.h" +#include "third_party/skia/include/core/SkTextBlob.h" + +namespace flutter { + +const SkSamplingOptions DisplayList::NearestSampling = + SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone); +const SkSamplingOptions DisplayList::LinearSampling = + SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone); +const SkSamplingOptions DisplayList::MipmapSampling = + SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear); +const SkSamplingOptions DisplayList::CubicSampling = + SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f}); + +// Most Ops can be bulk compared using memcmp because they contain +// only numeric values or constructs that are constructed from numeric +// values. +// +// Some contain sk_sp<> references which can also be bulk compared +// to see if they are pointing to the same reference. (Note that +// two sk_sp<> that refer to the same object are themselves ==.) +// +// Only a DLOp that wants to do a deep compare needs to override the +// DLOp::equals() method and return a value of kEqual or kNotEqual. +enum class DisplayListCompare { + // The Op is deferring comparisons to a bulk memcmp performed lazily + // across all bulk-comparable ops. + kUseBulkCompare, + + // The Op provided a specific equals method that spotted a difference + kNotEqual, + + // The Op provided a specific equals method that saw no differences + kEqual, +}; + +#pragma pack(push, DLOp_Alignment, 8) + +// Assuming a 64-bit platform (most of our platforms at this time?) +// the following comments are a "worst case" assessment of how well +// these structures pack into memory. They may be packed more tightly +// on some of the 32-bit platforms that we see in older phones. +// +// Struct allocation in the DL memory is aligned to a void* boundary +// which means that the minimum (aligned) struct size will be 8 bytes. +// The DLOp base uses 4 bytes so each Op-specific struct gets 4 bytes +// of data for "free" and works best when it packs well into an 8-byte +// aligned size. +struct DLOp { + DisplayListOpType type : 8; + uint32_t size : 24; + + DisplayListCompare equals(const DLOp* other) const { + return DisplayListCompare::kUseBulkCompare; + } +}; + +// 4 byte header + 4 byte payload packs into minimum 8 bytes +#define DEFINE_SET_BOOL_OP(name) \ + struct Set##name##Op final : DLOp { \ + static const auto kType = DisplayListOpType::kSet##name; \ + \ + Set##name##Op(bool value) : value(value) {} \ + \ + const bool value; \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + dispatcher.set##name(value); \ + } \ + }; +DEFINE_SET_BOOL_OP(AA) +DEFINE_SET_BOOL_OP(Dither) +DEFINE_SET_BOOL_OP(InvertColors) +#undef DEFINE_SET_BOOL_OP + +// 4 byte header + 4 byte payload packs into minimum 8 bytes +#define DEFINE_SET_ENUM_OP(name) \ + struct Set##name##s##Op final : DLOp { \ + static const auto kType = DisplayListOpType::kSet##name##s; \ + \ + Set##name##s##Op(SkPaint::name value) : value(value) {} \ + \ + const SkPaint::name value; \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + dispatcher.set##name##s(value); \ + } \ + }; +DEFINE_SET_ENUM_OP(Cap) +DEFINE_SET_ENUM_OP(Join) +#undef DEFINE_SET_ENUM_OP + +// 4 byte header + 4 byte payload packs into minimum 8 bytes +struct SetDrawStyleOp final : DLOp { + static const auto kType = DisplayListOpType::kSetDrawStyle; + + SetDrawStyleOp(SkPaint::Style style) : style(style) {} + + const SkPaint::Style style; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.setDrawStyle(style); + } +}; +// 4 byte header + 4 byte payload packs into minimum 8 bytes +struct SetStrokeWidthOp final : DLOp { + static const auto kType = DisplayListOpType::kSetStrokeWidth; + + SetStrokeWidthOp(SkScalar width) : width(width) {} + + const SkScalar width; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.setStrokeWidth(width); + } +}; +// 4 byte header + 4 byte payload packs into minimum 8 bytes +struct SetMiterLimitOp final : DLOp { + static const auto kType = DisplayListOpType::kSetMiterLimit; + + SetMiterLimitOp(SkScalar limit) : limit(limit) {} + + const SkScalar limit; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.setMiterLimit(limit); + } +}; + +// 4 byte header + 4 byte payload packs into minimum 8 bytes +struct SetColorOp final : DLOp { + static const auto kType = DisplayListOpType::kSetColor; + + SetColorOp(SkColor color) : color(color) {} + + const SkColor color; + + void dispatch(Dispatcher& dispatcher) const { dispatcher.setColor(color); } +}; +// 4 byte header + 4 byte payload packs into minimum 8 bytes +struct SetBlendModeOp final : DLOp { + static const auto kType = DisplayListOpType::kSetBlendMode; + + SetBlendModeOp(SkBlendMode mode) : mode(mode) {} + + const SkBlendMode mode; + + void dispatch(Dispatcher& dispatcher) const { dispatcher.setBlendMode(mode); } +}; + +// 4 byte header + 4 byte payload packs into minimum 8 bytes +struct SetFilterQualityOp final : DLOp { + static const auto kType = DisplayListOpType::kSetFilterQuality; + + SetFilterQualityOp(SkFilterQuality quality) : quality(quality) {} + + const SkFilterQuality quality; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.setFilterQuality(quality); + } +}; + +// Clear: 4 byte header + unused 4 byte payload uses 8 bytes +// (4 bytes unused) +// Set: 4 byte header + an sk_sp (ptr) uses 16 bytes due to the +// alignment of the ptr. +// (4 bytes unused) +#define DEFINE_SET_CLEAR_SKREF_OP(name, field) \ + struct Clear##name##Op final : DLOp { \ + static const auto kType = DisplayListOpType::kClear##name; \ + \ + Clear##name##Op() {} \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + dispatcher.set##name(nullptr); \ + } \ + }; \ + struct Set##name##Op final : DLOp { \ + static const auto kType = DisplayListOpType::kSet##name; \ + \ + Set##name##Op(sk_sp field) : field(std::move(field)) {} \ + \ + sk_sp field; \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + dispatcher.set##name(field); \ + } \ + }; +DEFINE_SET_CLEAR_SKREF_OP(Shader, shader) +DEFINE_SET_CLEAR_SKREF_OP(ImageFilter, filter) +DEFINE_SET_CLEAR_SKREF_OP(ColorFilter, filter) +DEFINE_SET_CLEAR_SKREF_OP(MaskFilter, filter) +#undef DEFINE_SET_CLEAR_SKREF_OP + +// 4 byte header + 4 byte payload packs into minimum 8 bytes +// Note that the "blur style" is packed into the OpType to prevent +// needing an additional 8 bytes for a 4-value enum. +#define DEFINE_MASK_BLUR_FILTER_OP(name, style) \ + struct SetMaskBlurFilter##name##Op final : DLOp { \ + static const auto kType = DisplayListOpType::kSetMaskBlurFilter##name; \ + \ + SetMaskBlurFilter##name##Op(SkScalar sigma) : sigma(sigma) {} \ + \ + SkScalar sigma; \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + dispatcher.setMaskBlurFilter(style, sigma); \ + } \ + }; +DEFINE_MASK_BLUR_FILTER_OP(Normal, kNormal_SkBlurStyle) +DEFINE_MASK_BLUR_FILTER_OP(Solid, kSolid_SkBlurStyle) +DEFINE_MASK_BLUR_FILTER_OP(Inner, kInner_SkBlurStyle) +DEFINE_MASK_BLUR_FILTER_OP(Outer, kOuter_SkBlurStyle) +#undef DEFINE_MASK_BLUR_FILTER_OP + +// 4 byte header + no payload uses minimum 8 bytes (4 bytes unused) +struct SaveOp final : DLOp { + static const auto kType = DisplayListOpType::kSave; + + SaveOp() {} + + void dispatch(Dispatcher& dispatcher) const { dispatcher.save(); } +}; +// 4 byte header + 4 byte payload packs into minimum 8 bytes +struct SaveLayerOp final : DLOp { + static const auto kType = DisplayListOpType::kSaveLayer; + + SaveLayerOp(bool with_paint) : with_paint(with_paint) {} + + bool with_paint; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.saveLayer(nullptr, with_paint); + } +}; +// 4 byte header + 20 byte payload packs evenly into 24 bytes +struct SaveLayerBoundsOp final : DLOp { + static const auto kType = DisplayListOpType::kSaveLayerBounds; + + SaveLayerBoundsOp(SkRect rect, bool with_paint) + : with_paint(with_paint), rect(rect) {} + + bool with_paint; + const SkRect rect; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.saveLayer(&rect, with_paint); + } +}; +// 4 byte header + no payload uses minimum 8 bytes (4 bytes unused) +struct RestoreOp final : DLOp { + static const auto kType = DisplayListOpType::kRestore; + + RestoreOp() {} + + void dispatch(Dispatcher& dispatcher) const { dispatcher.restore(); } +}; + +// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes +// (4 bytes unused) +struct TranslateOp final : DLOp { + static const auto kType = DisplayListOpType::kTranslate; + + TranslateOp(SkScalar tx, SkScalar ty) : tx(tx), ty(ty) {} + + const SkScalar tx; + const SkScalar ty; + + void dispatch(Dispatcher& dispatcher) const { dispatcher.translate(tx, ty); } +}; +// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes +// (4 bytes unused) +struct ScaleOp final : DLOp { + static const auto kType = DisplayListOpType::kScale; + + ScaleOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {} + + const SkScalar sx; + const SkScalar sy; + + void dispatch(Dispatcher& dispatcher) const { dispatcher.scale(sx, sy); } +}; +// 4 byte header + 4 byte payload packs into minimum 8 bytes +struct RotateOp final : DLOp { + static const auto kType = DisplayListOpType::kRotate; + + RotateOp(SkScalar degrees) : degrees(degrees) {} + + const SkScalar degrees; + + void dispatch(Dispatcher& dispatcher) const { dispatcher.rotate(degrees); } +}; +// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes +// (4 bytes unused) +struct SkewOp final : DLOp { + static const auto kType = DisplayListOpType::kSkew; + + SkewOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {} + + const SkScalar sx; + const SkScalar sy; + + void dispatch(Dispatcher& dispatcher) const { dispatcher.skew(sx, sy); } +}; +// 4 byte header + 24 byte payload uses 28 bytes but is rounded up to 32 bytes +// (4 bytes unused) +struct Transform2x3Op final : DLOp { + static const auto kType = DisplayListOpType::kTransform2x3; + + Transform2x3Op(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt) + : mxx(mxx), mxy(mxy), mxt(mxt), myx(myx), myy(myy), myt(myt) {} + + const SkScalar mxx, mxy, mxt; + const SkScalar myx, myy, myt; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.transform2x3(mxx, mxy, mxt, myx, myy, myt); + } +}; +// 4 byte header + 36 byte payload packs evenly into 40 bytes +struct Transform3x3Op final : DLOp { + static const auto kType = DisplayListOpType::kTransform3x3; + + Transform3x3Op(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt, + SkScalar px, + SkScalar py, + SkScalar pt) + : mxx(mxx), + mxy(mxy), + mxt(mxt), + myx(myx), + myy(myy), + myt(myt), + px(px), + py(py), + pt(pt) {} + + const SkScalar mxx, mxy, mxt; + const SkScalar myx, myy, myt; + const SkScalar px, py, pt; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.transform3x3(mxx, mxy, mxt, myx, myy, myt, px, py, pt); + } +}; + +// 4 byte header + 4 byte common payload packs into minimum 8 bytes +// SkRect is 16 more bytes, which packs efficiently into 24 bytes total +// SkRRect is 52 more bytes, which rounds up to 56 bytes (4 bytes unused) +// which packs into 64 bytes total +// SkPath is 16 more bytes, which packs efficiently into 24 bytes total +// +// We could pack the clip_op and the bool both into the free 4 bytes after +// the header, but the Windows compiler keeps wanting to expand that +// packing into more bytes than needed (even when they are declared as +// packed bit fields!) +#define DEFINE_CLIP_SHAPE_OP(shapetype, clipop) \ + struct Clip##clipop##shapetype##Op final : DLOp { \ + static const auto kType = DisplayListOpType::kClip##clipop##shapetype; \ + \ + Clip##clipop##shapetype##Op(Sk##shapetype shape, bool is_aa) \ + : is_aa(is_aa), shape(shape) {} \ + \ + const bool is_aa; \ + const Sk##shapetype shape; \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + dispatcher.clip##shapetype(shape, is_aa, SkClipOp::k##clipop); \ + } \ + }; +DEFINE_CLIP_SHAPE_OP(Rect, Intersect) +DEFINE_CLIP_SHAPE_OP(RRect, Intersect) +DEFINE_CLIP_SHAPE_OP(Rect, Difference) +DEFINE_CLIP_SHAPE_OP(RRect, Difference) +#undef DEFINE_CLIP_SHAPE_OP + +#define DEFINE_CLIP_PATH_OP(clipop) \ + struct Clip##clipop##PathOp final : DLOp { \ + static const auto kType = DisplayListOpType::kClip##clipop##Path; \ + \ + Clip##clipop##PathOp(SkPath path, bool is_aa) \ + : is_aa(is_aa), path(path) {} \ + \ + const bool is_aa; \ + const SkPath path; \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + dispatcher.clipPath(path, is_aa, SkClipOp::k##clipop); \ + } \ + \ + DisplayListCompare equals(const Clip##clipop##PathOp* other) const { \ + return is_aa == other->is_aa && path == other->path \ + ? DisplayListCompare::kEqual \ + : DisplayListCompare::kNotEqual; \ + } \ + }; +DEFINE_CLIP_PATH_OP(Intersect) +DEFINE_CLIP_PATH_OP(Difference) +#undef DEFINE_CLIP_PATH_OP + +// 4 byte header + no payload uses minimum 8 bytes (4 bytes unused) +struct DrawPaintOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawPaint; + + DrawPaintOp() {} + + void dispatch(Dispatcher& dispatcher) const { dispatcher.drawPaint(); } +}; +// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes +// (4 bytes unused) +struct DrawColorOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawColor; + + DrawColorOp(SkColor color, SkBlendMode mode) : color(color), mode(mode) {} + + const SkColor color; + const SkBlendMode mode; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.drawColor(color, mode); + } +}; + +// The common data is a 4 byte header with an unused 4 bytes +// SkRect is 16 more bytes, using 20 bytes which rounds up to 24 bytes total +// (4 bytes unused) +// SkOval is same as SkRect +// SkRRect is 52 more bytes, which packs efficiently into 56 bytes total +#define DEFINE_DRAW_1ARG_OP(op_name, arg_type, arg_name) \ + struct Draw##op_name##Op final : DLOp { \ + static const auto kType = DisplayListOpType::kDraw##op_name; \ + \ + Draw##op_name##Op(arg_type arg_name) : arg_name(arg_name) {} \ + \ + const arg_type arg_name; \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + dispatcher.draw##op_name(arg_name); \ + } \ + }; +DEFINE_DRAW_1ARG_OP(Rect, SkRect, rect) +DEFINE_DRAW_1ARG_OP(Oval, SkRect, oval) +DEFINE_DRAW_1ARG_OP(RRect, SkRRect, rrect) +#undef DEFINE_DRAW_1ARG_OP + +// 4 byte header + 16 byte payload uses 20 bytes but is rounded up to 24 bytes +// (4 bytes unused) +struct DrawPathOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawPath; + + DrawPathOp(SkPath path) : path(path) {} + + const SkPath path; + + void dispatch(Dispatcher& dispatcher) const { dispatcher.drawPath(path); } + + DisplayListCompare equals(const DrawPathOp* other) const { + return path == other->path ? DisplayListCompare::kEqual + : DisplayListCompare::kNotEqual; + } +}; + +// The common data is a 4 byte header with an unused 4 bytes +// 2 x SkPoint is 16 more bytes, using 20 bytes rounding up to 24 bytes total +// (4 bytes unused) +// SkPoint + SkScalar is 12 more bytes, packing efficiently into 16 bytes total +// 2 x SkRRect is 104 more bytes, using 108 and rounding up to 112 bytes total +// (4 bytes unused) +#define DEFINE_DRAW_2ARG_OP(op_name, type1, name1, type2, name2) \ + struct Draw##op_name##Op final : DLOp { \ + static const auto kType = DisplayListOpType::kDraw##op_name; \ + \ + Draw##op_name##Op(type1 name1, type2 name2) \ + : name1(name1), name2(name2) {} \ + \ + const type1 name1; \ + const type2 name2; \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + dispatcher.draw##op_name(name1, name2); \ + } \ + }; +DEFINE_DRAW_2ARG_OP(Line, SkPoint, p0, SkPoint, p1) +DEFINE_DRAW_2ARG_OP(Circle, SkPoint, center, SkScalar, radius) +DEFINE_DRAW_2ARG_OP(DRRect, SkRRect, outer, SkRRect, inner) +#undef DEFINE_DRAW_2ARG_OP + +// 4 byte header + 28 byte payload packs efficiently into 32 bytes +struct DrawArcOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawArc; + + DrawArcOp(SkRect bounds, SkScalar start, SkScalar sweep, bool center) + : bounds(bounds), start(start), sweep(sweep), center(center) {} + + const SkRect bounds; + const SkScalar start; + const SkScalar sweep; + const bool center; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.drawArc(bounds, start, sweep, center); + } +}; + +// 4 byte header + 4 byte fixed payload packs efficiently into 8 bytes +// But then there is a list of points following the structure which +// is guaranteed to be a multiple of 8 bytes (SkPoint is 8 bytes) +// so this op will always pack efficiently +// The point type is packed into 3 different OpTypes to avoid expanding +// the fixed payload beyond the 8 bytes +#define DEFINE_DRAW_POINTS_OP(name, mode) \ + struct Draw##name##Op final : DLOp { \ + static const auto kType = DisplayListOpType::kDraw##name; \ + \ + Draw##name##Op(uint32_t count) : count(count) {} \ + \ + const uint32_t count; \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + const SkPoint* pts = reinterpret_cast(this + 1); \ + dispatcher.drawPoints(SkCanvas::PointMode::mode, count, pts); \ + } \ + }; +DEFINE_DRAW_POINTS_OP(Points, kPoints_PointMode); +DEFINE_DRAW_POINTS_OP(Lines, kLines_PointMode); +DEFINE_DRAW_POINTS_OP(Polygon, kPolygon_PointMode); +#undef DEFINE_DRAW_POINTS_OP + +// 4 byte header + 12 byte payload packs efficiently into 16 bytes +struct DrawVerticesOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawVertices; + + DrawVerticesOp(sk_sp vertices, SkBlendMode mode) + : mode(mode), vertices(std::move(vertices)) {} + + const SkBlendMode mode; + const sk_sp vertices; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.drawVertices(vertices, mode); + } +}; + +// 4 byte header + 36 byte payload packs efficiently into 40 bytes +struct DrawImageOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawImage; + + DrawImageOp(const sk_sp image, + const SkPoint& point, + const SkSamplingOptions& sampling) + : point(point), sampling(sampling), image(std::move(image)) {} + + const SkPoint point; + const SkSamplingOptions sampling; + const sk_sp image; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.drawImage(image, point, sampling); + } +}; + +// 4 byte header + 60 byte payload packs efficiently into 64 bytes +// +// The constraint could be stored in the struct, but it would not pack +// efficiently so 2 variants are defined instead. +#define DEFINE_DRAW_IMAGE_RECT_OP(name, constraint) \ + struct Draw##name##Op final : DLOp { \ + static const auto kType = DisplayListOpType::kDraw##name; \ + \ + Draw##name##Op(const sk_sp image, \ + const SkRect& src, \ + const SkRect& dst, \ + const SkSamplingOptions& sampling) \ + : src(src), dst(dst), sampling(sampling), image(std::move(image)) {} \ + \ + const SkRect src; \ + const SkRect dst; \ + const SkSamplingOptions sampling; \ + const sk_sp image; \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + dispatcher.drawImageRect(image, src, dst, sampling, constraint); \ + } \ + }; +DEFINE_DRAW_IMAGE_RECT_OP(ImageRectStrict, SkCanvas::kStrict_SrcRectConstraint) +DEFINE_DRAW_IMAGE_RECT_OP(ImageRectFast, SkCanvas::kFast_SrcRectConstraint) +#undef DEFINE_DRAW_IMAGE_RECT_OP + +// 4 byte header + 44 byte payload packs efficiently into 48 bytes +struct DrawImageNineOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawImageNine; + + DrawImageNineOp(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter) + : center(center), dst(dst), filter(filter), image(std::move(image)) {} + + const SkIRect center; + const SkRect dst; + const SkFilterMode filter; + const sk_sp image; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.drawImageNine(image, center, dst, filter); + } +}; + +// 4 byte header + 60 byte payload packs evenly into 64 bytes +struct DrawImageLatticeOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawImageLattice; + + DrawImageLatticeOp(const sk_sp image, + int x_count, + int y_count, + int cell_count, + const SkIRect& src, + const SkRect& dst, + SkFilterMode filter, + bool with_paint) + : with_paint(with_paint), + x_count(x_count), + y_count(y_count), + cell_count(cell_count), + filter(filter), + src(src), + dst(dst), + image(std::move(image)) {} + + const bool with_paint; + const int x_count; + const int y_count; + const int cell_count; + const SkFilterMode filter; + const SkIRect src; + const SkRect dst; + const sk_sp image; + + void dispatch(Dispatcher& dispatcher) const { + const int* xDivs = reinterpret_cast(this + 1); + const int* yDivs = reinterpret_cast(xDivs + x_count); + const SkColor* colors = + (cell_count == 0) ? nullptr + : reinterpret_cast(yDivs + y_count); + const SkCanvas::Lattice::RectType* types = + (cell_count == 0) + ? nullptr + : reinterpret_cast(colors + + cell_count); + dispatcher.drawImageLattice( + image, {xDivs, yDivs, types, x_count, y_count, &src, colors}, dst, + filter, with_paint); + } +}; + +#define DRAW_ATLAS_NO_COLORS_ARRAY(tex, count) nullptr +#define DRAW_ATLAS_HAS_COLORS_ARRAY(tex, count) \ + reinterpret_cast(tex + count) + +#define DRAW_ATLAS_NO_CULLING_ARGS \ + const sk_sp atlas, int count, SkBlendMode mode, \ + const SkSamplingOptions &sampling +#define DRAW_ATLAS_NO_CULLING_INIT \ + count(count), mode(mode), sampling(sampling), atlas(std::move(atlas)) +#define DRAW_ATLAS_NO_CULLING_FIELDS \ + const int count; \ + const SkBlendMode mode; \ + const SkSamplingOptions sampling; \ + const sk_sp atlas +#define DRAW_ATLAS_NO_CULLING_P_ARG nullptr + +#define DRAW_ATLAS_HAS_CULLING_ARGS \ + DRAW_ATLAS_NO_CULLING_ARGS, const SkRect& cull +#define DRAW_ATLAS_HAS_CULLING_INIT DRAW_ATLAS_NO_CULLING_INIT, cull(cull) +#define DRAW_ATLAS_HAS_CULLING_FIELDS \ + DRAW_ATLAS_NO_CULLING_FIELDS; \ + const SkRect cull +#define DRAW_ATLAS_HAS_CULLING_P_ARG &cull + +// 4 byte header + 36 byte common payload packs efficiently into 40 bytes +// Culling version has an additional 16 bytes of payload for 56 bytes +// So all 4 versions of the base structure pack well. +// Each of these is then followed by a number of lists. +// SkRSXform list is a multiple of 16 bytes so it is always packed well +// SkRect list is also a multiple of 16 bytes so it also packs well +// SkColor list only packs well if the count is even, otherwise there +// can be 4 unusued bytes at the end. +#define DEFINE_DRAW_ATLAS_OP(name, colors, cull) \ + struct Draw##name##Op final : DLOp { \ + static const auto kType = DisplayListOpType::kDraw##name; \ + \ + Draw##name##Op(DRAW_ATLAS_##cull##_ARGS) : DRAW_ATLAS_##cull##_INIT {} \ + \ + DRAW_ATLAS_##cull##_FIELDS; \ + \ + void dispatch(Dispatcher& dispatcher) const { \ + const SkRSXform* xform = reinterpret_cast(this + 1); \ + const SkRect* tex = reinterpret_cast(xform + count); \ + const SkColor* colors = DRAW_ATLAS_##colors##_ARRAY(tex, count); \ + dispatcher.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, \ + DRAW_ATLAS_##cull##_P_ARG); \ + } \ + }; +DEFINE_DRAW_ATLAS_OP(Atlas, NO_COLORS, NO_CULLING) +DEFINE_DRAW_ATLAS_OP(AtlasColored, HAS_COLORS, NO_CULLING) +DEFINE_DRAW_ATLAS_OP(AtlasCulled, NO_COLORS, HAS_CULLING) +DEFINE_DRAW_ATLAS_OP(AtlasColoredCulled, HAS_COLORS, HAS_CULLING) +#undef DEFINE_DRAW_ATLAS_OP +#undef DRAW_ATLAS_NO_COLORS_ARRAY +#undef DRAW_ATLAS_HAS_COLORS_ARRAY +#undef DRAW_ATLAS_NO_CULLING_ARGS +#undef DRAW_ATLAS_NO_CULLING_INIT +#undef DRAW_ATLAS_NO_CULLING_FIELDS +#undef DRAW_ATLAS_NO_CULLING_P_ARG +#undef DRAW_ATLAS_HAS_CULLING_ARGS +#undef DRAW_ATLAS_HAS_CULLING_INIT +#undef DRAW_ATLAS_HAS_CULLING_FIELDS +#undef DRAW_ATLAS_HAS_CULLING_P_ARG + +// 4 byte header + 12 byte payload packs evenly into 16 bytes +struct DrawSkPictureOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawSkPicture; + + DrawSkPictureOp(sk_sp picture, bool with_layer) + : with_layer(with_layer), picture(std::move(picture)) {} + + const bool with_layer; + const sk_sp picture; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.drawPicture(picture, nullptr, with_layer); + } +}; + +// 4 byte header + 52 byte payload packs evenly into 56 bytes +struct DrawSkPictureMatrixOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawSkPictureMatrix; + + DrawSkPictureMatrixOp(sk_sp picture, + const SkMatrix matrix, + bool with_layer) + : with_layer(with_layer), picture(std::move(picture)), matrix(matrix) {} + + const bool with_layer; + const sk_sp picture; + const SkMatrix matrix; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.drawPicture(picture, &matrix, with_layer); + } +}; + +// 4 byte header + ptr aligned payload uses 12 bytes rounde up to 16 +// (4 bytes unused) +struct DrawDisplayListOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawDisplayList; + + DrawDisplayListOp(const sk_sp display_list) + : display_list(std::move(display_list)) {} + + sk_sp display_list; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.drawDisplayList(display_list); + } +}; + +// 4 byte header + 8 payload bytes + an aligned pointer take 24 bytes +// (4 unused to align the pointer) +struct DrawTextBlobOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawTextBlob; + + DrawTextBlobOp(const sk_sp blob, SkScalar x, SkScalar y) + : x(x), y(y), blob(std::move(blob)) {} + + const SkScalar x; + const SkScalar y; + const sk_sp blob; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.drawTextBlob(blob, x, y); + } +}; + +// 4 byte header + 28 byte payload packs evenly into 32 bytes +struct DrawShadowOp final : DLOp { + static const auto kType = DisplayListOpType::kDrawShadow; + + DrawShadowOp(const SkPath& path, + SkColor color, + SkScalar elevation, + bool occludes) + : color(color), elevation(elevation), occludes(occludes), path(path) {} + + const SkColor color; + const SkScalar elevation; + const bool occludes; + const SkPath path; + + void dispatch(Dispatcher& dispatcher) const { + dispatcher.drawShadow(path, color, elevation, occludes); + } +}; + +#pragma pack(pop, DLOp_Alignment) + +void DisplayList::ComputeBounds() { + DisplayListBoundsCalculator calculator(bounds_cull_); + Dispatch(calculator); + bounds_ = calculator.getBounds(); +} + +void DisplayList::Dispatch(Dispatcher& dispatcher, + uint8_t* ptr, + uint8_t* end) const { + while (ptr < end) { + auto op = (const DLOp*)ptr; + ptr += op->size; + FML_DCHECK(ptr <= end); + switch (op->type) { +#define DL_OP_DISPATCH(name) \ + case DisplayListOpType::k##name: \ + static_cast(op)->dispatch(dispatcher); \ + break; + + FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPATCH) + +#undef DL_OP_DISPATCH + + default: + FML_DCHECK(false); + return; + } + } +} + +static void DisposeOps(uint8_t* ptr, uint8_t* end) { + while (ptr < end) { + auto op = (const DLOp*)ptr; + ptr += op->size; + FML_DCHECK(ptr <= end); + switch (op->type) { +#define DL_OP_DISPOSE(name) \ + case DisplayListOpType::k##name: \ + if (!std::is_trivially_destructible_v) { \ + static_cast(op)->~name##Op(); \ + } \ + break; + + FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPOSE) + +#undef DL_OP_DISPATCH + + default: + FML_DCHECK(false); + return; + } + } +} + +static bool CompareOps(uint8_t* ptrA, + uint8_t* endA, + uint8_t* ptrB, + uint8_t* endB) { + // These conditions are checked by the caller... + FML_DCHECK((endA - ptrA) == (endB - ptrB)); + FML_DCHECK(ptrA != ptrB); + uint8_t* bulkStartA = ptrA; + uint8_t* bulkStartB = ptrB; + while (ptrA < endA && ptrB < endB) { + auto opA = (const DLOp*)ptrA; + auto opB = (const DLOp*)ptrB; + if (opA->type != opB->type || opA->size != opB->size) { + return false; + } + ptrA += opA->size; + ptrB += opB->size; + FML_DCHECK(ptrA <= endA); + FML_DCHECK(ptrB <= endB); + DisplayListCompare result; + switch (opA->type) { +#define DL_OP_EQUALS(name) \ + case DisplayListOpType::k##name: \ + result = static_cast(opA)->equals( \ + static_cast(opB)); \ + break; + + FOR_EACH_DISPLAY_LIST_OP(DL_OP_EQUALS) + +#undef DL_OP_DISPATCH + + default: + FML_DCHECK(false); + return false; + } + switch (result) { + case DisplayListCompare::kNotEqual: + return false; + case DisplayListCompare::kUseBulkCompare: + break; + case DisplayListCompare::kEqual: + // Check if we have a backlog of bytes to bulk compare and then + // reset the bulk compare pointers to the address following this op + auto bulkBytes = reinterpret_cast(opA) - bulkStartA; + if (bulkBytes > 0) { + if (memcmp(bulkStartA, bulkStartB, bulkBytes) != 0) { + return false; + } + } + bulkStartA = ptrA; + bulkStartB = ptrB; + break; + } + } + if (ptrA != endA || ptrB != endB) { + return false; + } + if (bulkStartA < ptrA) { + // Perform a final bulk compare if we have remaining bytes waiting + if (memcmp(bulkStartA, bulkStartB, ptrA - bulkStartA) != 0) { + return false; + } + } + return true; +} + +void DisplayList::RenderTo(SkCanvas* canvas) const { + DisplayListCanvasDispatcher dispatcher(canvas); + Dispatch(dispatcher); +} + +bool DisplayList::Equals(const DisplayList& other) const { + if (used_ != other.used_ || op_count_ != other.op_count_) { + return false; + } + if (ptr_ == other.ptr_) { + return true; + } + return CompareOps(ptr_, ptr_ + used_, other.ptr_, other.ptr_ + other.used_); +} + +DisplayList::DisplayList(uint8_t* ptr, + size_t used, + int op_count, + const SkRect& cull) + : ptr_(ptr), + used_(used), + op_count_(op_count), + bounds_({0, 0, -1, -1}), + bounds_cull_(cull) { + static std::atomic nextID{1}; + do { + unique_id_ = nextID.fetch_add(+1, std::memory_order_relaxed); + } while (unique_id_ == 0); +} + +DisplayList::~DisplayList() { + DisposeOps(ptr_, ptr_ + used_); +} + +#define DL_BUILDER_PAGE 4096 + +// CopyV(dst, src,n, src,n, ...) copies any number of typed srcs into dst. +static void CopyV(void* dst) {} + +template +static void CopyV(void* dst, const S* src, int n, Rest&&... rest) { + FML_DCHECK(((uintptr_t)dst & (alignof(S) - 1)) == 0) + << "Expected " << dst << " to be aligned for at least " << alignof(S) + << " bytes."; + sk_careful_memcpy(dst, src, n * sizeof(S)); + CopyV(SkTAddOffset(dst, n * sizeof(S)), std::forward(rest)...); +} + +template +void* DisplayListBuilder::Push(size_t pod, Args&&... args) { + size_t size = SkAlignPtr(sizeof(T) + pod); + FML_DCHECK(size < (1 << 24)); + if (used_ + size > allocated_) { + static_assert(SkIsPow2(DL_BUILDER_PAGE), + "This math needs updating for non-pow2."); + // Next greater multiple of DL_BUILDER_PAGE. + allocated_ = (used_ + size + DL_BUILDER_PAGE) & ~(DL_BUILDER_PAGE - 1); + storage_.realloc(allocated_); + FML_DCHECK(storage_.get()); + memset(storage_.get() + used_, 0, allocated_ - used_); + } + FML_DCHECK(used_ + size <= allocated_); + auto op = (T*)(storage_.get() + used_); + used_ += size; + new (op) T{std::forward(args)...}; + op->type = T::kType; + op->size = size; + op_count_++; + return op + 1; +} + +sk_sp DisplayListBuilder::Build() { + while (save_level_ > 0) { + restore(); + } + size_t used = used_; + int count = op_count_; + used_ = allocated_ = op_count_ = 0; + storage_.realloc(used); + return sk_sp( + new DisplayList(storage_.release(), used, count, cull_)); +} + +DisplayListBuilder::DisplayListBuilder(const SkRect& cull) : cull_(cull) {} + +DisplayListBuilder::~DisplayListBuilder() { + uint8_t* ptr = storage_.get(); + if (ptr) { + DisposeOps(ptr, ptr + used_); + } +} + +void DisplayListBuilder::setAA(bool aa) { + Push(0, aa); +} +void DisplayListBuilder::setDither(bool dither) { + Push(0, dither); +} +void DisplayListBuilder::setInvertColors(bool invert) { + Push(0, invert); +} +void DisplayListBuilder::setCaps(SkPaint::Cap cap) { + Push(0, cap); +} +void DisplayListBuilder::setJoins(SkPaint::Join join) { + Push(0, join); +} +void DisplayListBuilder::setDrawStyle(SkPaint::Style style) { + Push(0, style); +} +void DisplayListBuilder::setStrokeWidth(SkScalar width) { + Push(0, width); +} +void DisplayListBuilder::setMiterLimit(SkScalar limit) { + Push(0, limit); +} +void DisplayListBuilder::setColor(SkColor color) { + Push(0, color); +} +void DisplayListBuilder::setBlendMode(SkBlendMode mode) { + Push(0, mode); +} +void DisplayListBuilder::setFilterQuality(SkFilterQuality quality) { + Push(0, quality); +} +void DisplayListBuilder::setShader(sk_sp shader) { + shader // + ? Push(0, std::move(shader)) + : Push(0); +} +void DisplayListBuilder::setImageFilter(sk_sp filter) { + filter // + ? Push(0, std::move(filter)) + : Push(0); +} +void DisplayListBuilder::setColorFilter(sk_sp filter) { + filter // + ? Push(0, std::move(filter)) + : Push(0); +} +void DisplayListBuilder::setMaskFilter(sk_sp filter) { + Push(0, std::move(filter)); +} +void DisplayListBuilder::setMaskBlurFilter(SkBlurStyle style, SkScalar sigma) { + switch (style) { + case kNormal_SkBlurStyle: + Push(0, sigma); + break; + case kSolid_SkBlurStyle: + Push(0, sigma); + break; + case kOuter_SkBlurStyle: + Push(0, sigma); + break; + case kInner_SkBlurStyle: + Push(0, sigma); + break; + } +} + +void DisplayListBuilder::save() { + save_level_++; + Push(0); +} +void DisplayListBuilder::restore() { + if (save_level_ > 0) { + Push(0); + save_level_--; + } +} +void DisplayListBuilder::saveLayer(const SkRect* bounds, bool with_paint) { + save_level_++; + bounds // + ? Push(0, *bounds, with_paint) + : Push(0, with_paint); +} + +void DisplayListBuilder::translate(SkScalar tx, SkScalar ty) { + Push(0, tx, ty); +} +void DisplayListBuilder::scale(SkScalar sx, SkScalar sy) { + Push(0, sx, sy); +} +void DisplayListBuilder::rotate(SkScalar degrees) { + Push(0, degrees); +} +void DisplayListBuilder::skew(SkScalar sx, SkScalar sy) { + Push(0, sx, sy); +} +void DisplayListBuilder::transform2x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt) { + Push(0, mxx, mxy, mxt, myx, myy, myt); +} +void DisplayListBuilder::transform3x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt, + SkScalar px, + SkScalar py, + SkScalar pt) { + Push(0, mxx, mxy, mxt, myx, myy, myt, px, py, pt); +} + +void DisplayListBuilder::clipRect(const SkRect& rect, + bool is_aa, + SkClipOp clip_op) { + clip_op == SkClipOp::kIntersect // + ? Push(0, rect, is_aa) + : Push(0, rect, is_aa); +} +void DisplayListBuilder::clipRRect(const SkRRect& rrect, + bool is_aa, + SkClipOp clip_op) { + if (rrect.isRect()) { + clipRect(rrect.rect(), is_aa, clip_op); + } else { + clip_op == SkClipOp::kIntersect // + ? Push(0, rrect, is_aa) + : Push(0, rrect, is_aa); + } +} +void DisplayListBuilder::clipPath(const SkPath& path, + bool is_aa, + SkClipOp clip_op) { + if (!path.isInverseFillType()) { + SkRect rect; + if (path.isRect(&rect)) { + this->clipRect(rect, is_aa, clip_op); + return; + } + SkRRect rrect; + if (path.isOval(&rect)) { + rrect.setOval(rect); + this->clipRRect(rrect, is_aa, clip_op); + return; + } + if (path.isRRect(&rrect)) { + this->clipRRect(rrect, is_aa, clip_op); + return; + } + } + clip_op == SkClipOp::kIntersect // + ? Push(0, path, is_aa) + : Push(0, path, is_aa); +} + +void DisplayListBuilder::drawPaint() { + Push(0); +} +void DisplayListBuilder::drawColor(SkColor color, SkBlendMode mode) { + Push(0, color, mode); +} +void DisplayListBuilder::drawLine(const SkPoint& p0, const SkPoint& p1) { + Push(0, p0, p1); +} +void DisplayListBuilder::drawRect(const SkRect& rect) { + Push(0, rect); +} +void DisplayListBuilder::drawOval(const SkRect& bounds) { + Push(0, bounds); +} +void DisplayListBuilder::drawCircle(const SkPoint& center, SkScalar radius) { + Push(0, center, radius); +} +void DisplayListBuilder::drawRRect(const SkRRect& rrect) { + if (rrect.isRect()) { + drawRect(rrect.rect()); + } else if (rrect.isOval()) { + drawOval(rrect.rect()); + } else { + Push(0, rrect); + } +} +void DisplayListBuilder::drawDRRect(const SkRRect& outer, + const SkRRect& inner) { + Push(0, outer, inner); +} +void DisplayListBuilder::drawPath(const SkPath& path) { + Push(0, path); +} + +void DisplayListBuilder::drawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter) { + Push(0, bounds, start, sweep, useCenter); +} +void DisplayListBuilder::drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint pts[]) { + void* data_ptr; + FML_DCHECK(count < MaxDrawPointsCount); + int bytes = count * sizeof(SkPoint); + switch (mode) { + case SkCanvas::PointMode::kPoints_PointMode: + data_ptr = Push(bytes, count); + break; + case SkCanvas::PointMode::kLines_PointMode: + data_ptr = Push(bytes, count); + break; + case SkCanvas::PointMode::kPolygon_PointMode: + data_ptr = Push(bytes, count); + break; + default: + FML_DCHECK(false); + return; + } + CopyV(data_ptr, pts, count); +} +void DisplayListBuilder::drawVertices(const sk_sp vertices, + SkBlendMode mode) { + Push(0, std::move(vertices), mode); +} + +void DisplayListBuilder::drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling) { + Push(0, std::move(image), point, sampling); +} +void DisplayListBuilder::drawImageRect(const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + SkCanvas::SrcRectConstraint constraint) { + constraint == SkCanvas::kFast_SrcRectConstraint // + ? Push(0, std::move(image), src, dst, sampling) + : Push(0, std::move(image), src, dst, sampling); +} +void DisplayListBuilder::drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter) { + Push(0, std::move(image), center, dst, filter); +} +void DisplayListBuilder::drawImageLattice(const sk_sp image, + const SkCanvas::Lattice& lattice, + const SkRect& dst, + SkFilterMode filter, + bool with_paint) { + int xDivCount = lattice.fXCount; + int yDivCount = lattice.fYCount; + FML_DCHECK((lattice.fRectTypes == nullptr) || (lattice.fColors != nullptr)); + int cellCount = lattice.fRectTypes && lattice.fColors + ? (xDivCount + 1) * (yDivCount + 1) + : 0; + size_t bytes = + (xDivCount + yDivCount) * sizeof(int) + + cellCount * (sizeof(SkColor) + sizeof(SkCanvas::Lattice::RectType)); + SkIRect src = lattice.fBounds ? *lattice.fBounds : image->bounds(); + void* pod = this->Push(bytes, std::move(image), xDivCount, + yDivCount, cellCount, src, dst, + filter, with_paint); + CopyV(pod, lattice.fXDivs, xDivCount, lattice.fYDivs, yDivCount, + lattice.fColors, cellCount, lattice.fRectTypes, cellCount); +} +void DisplayListBuilder::drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + SkBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cullRect) { + int bytes = count * (sizeof(SkRSXform) + sizeof(SkRect)); + void* data_ptr; + if (colors) { + bytes += count * sizeof(SkColor); + if (cullRect) { + data_ptr = Push(bytes, std::move(atlas), count, + mode, sampling, *cullRect); + } else { + data_ptr = Push(bytes, std::move(atlas), count, mode, + sampling); + } + CopyV(data_ptr, xform, count, tex, count, colors, count); + } else { + if (cullRect) { + data_ptr = Push(bytes, std::move(atlas), count, mode, + sampling, *cullRect); + } else { + data_ptr = + Push(bytes, std::move(atlas), count, mode, sampling); + } + CopyV(data_ptr, xform, count, tex, count); + } +} + +void DisplayListBuilder::drawPicture(const sk_sp picture, + const SkMatrix* matrix, + bool with_layer) { + matrix // + ? Push(0, std::move(picture), *matrix, with_layer) + : Push(0, std::move(picture), with_layer); +} +void DisplayListBuilder::drawDisplayList( + const sk_sp display_list) { + Push(0, std::move(display_list)); +} +void DisplayListBuilder::drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) { + Push(0, std::move(blob), x, y); +} +void DisplayListBuilder::drawShadow(const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool occludes) { + Push(0, path, color, elevation, occludes); +} + +} // namespace flutter diff --git a/flow/display_list.h b/flow/display_list.h new file mode 100644 index 0000000000000..7fde2a8480728 --- /dev/null +++ b/flow/display_list.h @@ -0,0 +1,452 @@ +// Copyright 2013 The Flutter 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 FLUTTER_FLOW_DISPLAY_LIST_H_ +#define FLUTTER_FLOW_DISPLAY_LIST_H_ + +#include "third_party/skia/include/core/SkBlurTypes.h" +#include "third_party/skia/include/core/SkCanvas.h" +#include "third_party/skia/include/core/SkColorFilter.h" +#include "third_party/skia/include/core/SkImage.h" +#include "third_party/skia/include/core/SkImageFilter.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "third_party/skia/include/core/SkShader.h" +#include "third_party/skia/include/core/SkVertices.h" + +// The Flutter DisplayList mechanism encapsulates a persistent sequence of +// rendering operations. +// +// This file contains the definitions for: +// DisplayList: the base class that holds the information about the +// sequence of operations and can dispatch them to a Dispatcher +// Dispatcher: a pure virtual interface which can be implemented to field +// the requests for purposes such as sending them to an SkCanvas +// or detecting various rendering optimization scenarios +// DisplayListBuilder: a class for constructing a DisplayList from the same +// calls defined in the Dispatcher +// +// Other files include various class definitions for dealing with display +// lists, such as: +// display_list_canvas.h: classes to interact between SkCanvas and DisplayList +// (SkCanvas->DisplayList adapter and vice versa) +// +// display_list_utils.h: various utility classes to ease implementing +// a Dispatcher, including NOP implementations of +// the attribute, clip, and transform methods, +// classes to track attributes, clips, and transforms +// and a class to compute the bounds of a DisplayList +// Any class implementing Dispatcher can inherit from +// these utility classes to simplify its creation +// +// The Flutter DisplayList mechanism can be used in place of the Skia +// SkPicture mechanism. The primary means of communication into and out +// of the DisplayList is through the Dispatcher virtual class which +// provides a nearly 1:1 translation between the records of the DisplayList +// to method calls. +// +// A DisplayList can be created directly using a DisplayListBuilder and +// the Dispatcher methods that it implements, or it can be created from +// a sequence of SkCanvas calls using the DisplayListCanvasRecorder class. +// +// A DisplayList can be read back by implementing the Dispatcher virtual +// methods (with help from some of the classes in the utils file) and +// passing an instance to the dispatch() method, or it can be rendered +// to Skia using a DisplayListCanvasDispatcher or simply by passing an +// SkCanvas pointer to its renderTo() method. +// +// The mechanism is inspired by the SkLiteDL class that is not directly +// supported by Skia, but has been recommended as a basis for custom +// display lists for a number of their customers. + +namespace flutter { + +#define FOR_EACH_DISPLAY_LIST_OP(V) \ + V(SetAA) \ + V(SetDither) \ + V(SetInvertColors) \ + \ + V(SetCaps) \ + V(SetJoins) \ + \ + V(SetDrawStyle) \ + V(SetStrokeWidth) \ + V(SetMiterLimit) \ + \ + V(SetColor) \ + V(SetBlendMode) \ + \ + V(SetFilterQuality) \ + \ + V(SetShader) \ + V(ClearShader) \ + V(SetColorFilter) \ + V(ClearColorFilter) \ + V(SetImageFilter) \ + V(ClearImageFilter) \ + \ + V(ClearMaskFilter) \ + V(SetMaskFilter) \ + V(SetMaskBlurFilterNormal) \ + V(SetMaskBlurFilterSolid) \ + V(SetMaskBlurFilterOuter) \ + V(SetMaskBlurFilterInner) \ + \ + V(Save) \ + V(SaveLayer) \ + V(SaveLayerBounds) \ + V(Restore) \ + \ + V(Translate) \ + V(Scale) \ + V(Rotate) \ + V(Skew) \ + V(Transform2x3) \ + V(Transform3x3) \ + \ + V(ClipIntersectRect) \ + V(ClipIntersectRRect) \ + V(ClipIntersectPath) \ + V(ClipDifferenceRect) \ + V(ClipDifferenceRRect) \ + V(ClipDifferencePath) \ + \ + V(DrawPaint) \ + V(DrawColor) \ + \ + V(DrawLine) \ + V(DrawRect) \ + V(DrawOval) \ + V(DrawCircle) \ + V(DrawRRect) \ + V(DrawDRRect) \ + V(DrawArc) \ + V(DrawPath) \ + \ + V(DrawPoints) \ + V(DrawLines) \ + V(DrawPolygon) \ + V(DrawVertices) \ + \ + V(DrawImage) \ + V(DrawImageRectStrict) \ + V(DrawImageRectFast) \ + V(DrawImageNine) \ + V(DrawImageLattice) \ + V(DrawAtlas) \ + V(DrawAtlasColored) \ + V(DrawAtlasCulled) \ + V(DrawAtlasColoredCulled) \ + \ + V(DrawSkPicture) \ + V(DrawSkPictureMatrix) \ + V(DrawDisplayList) \ + V(DrawTextBlob) \ + \ + V(DrawShadow) + +#define DL_OP_TO_ENUM_VALUE(name) k##name, +enum class DisplayListOpType { FOR_EACH_DISPLAY_LIST_OP(DL_OP_TO_ENUM_VALUE) }; +#undef DL_OP_TO_ENUM_VALUE + +class Dispatcher; +class DisplayListBuilder; + +// The base class that contains a sequence of rendering operations +// for dispatch to a Dispatcher. These objects must be instantiated +// through an instance of DisplayListBuilder::build(). +class DisplayList : public SkRefCnt { + public: + static const SkSamplingOptions NearestSampling; + static const SkSamplingOptions LinearSampling; + static const SkSamplingOptions MipmapSampling; + static const SkSamplingOptions CubicSampling; + + DisplayList() + : ptr_(nullptr), + used_(0), + op_count_(0), + unique_id_(0), + bounds_({0, 0, 0, 0}), + bounds_cull_({0, 0, 0, 0}) {} + + ~DisplayList(); + + void Dispatch(Dispatcher& ctx) const { Dispatch(ctx, ptr_, ptr_ + used_); } + + void RenderTo(SkCanvas* canvas) const; + + size_t bytes() const { return used_; } + int op_count() const { return op_count_; } + uint32_t unique_id() const { return unique_id_; } + + const SkRect& bounds() { + if (bounds_.width() < 0.0) { + // ComputeBounds() will leave the variable with a + // non-negative width and height + ComputeBounds(); + } + return bounds_; + } + + bool Equals(const DisplayList& other) const; + + private: + DisplayList(uint8_t* ptr, size_t used, int op_count, const SkRect& cull_rect); + + uint8_t* ptr_; + size_t used_; + int op_count_; + + uint32_t unique_id_; + SkRect bounds_; + + // Only used for drawPaint() and drawColor() + SkRect bounds_cull_; + + void ComputeBounds(); + void Dispatch(Dispatcher& ctx, uint8_t* ptr, uint8_t* end) const; + + friend class DisplayListBuilder; +}; + +// The pure virtual interface for interacting with a display list. +// This interface represents the methods used to build a list +// through the DisplayListBuilder and also the methods that will +// be invoked through the DisplayList::dispatch() method. +class Dispatcher { + public: + // MaxDrawPointsCount * sizeof(SkPoint) must be less than 1 << 32 + static constexpr int MaxDrawPointsCount = ((1 << 29) - 1); + + virtual void setAA(bool aa) = 0; + virtual void setDither(bool dither) = 0; + virtual void setInvertColors(bool invert) = 0; + virtual void setCaps(SkPaint::Cap cap) = 0; + virtual void setJoins(SkPaint::Join join) = 0; + virtual void setDrawStyle(SkPaint::Style style) = 0; + virtual void setStrokeWidth(SkScalar width) = 0; + virtual void setMiterLimit(SkScalar limit) = 0; + virtual void setColor(SkColor color) = 0; + virtual void setBlendMode(SkBlendMode mode) = 0; + virtual void setFilterQuality(SkFilterQuality quality) = 0; + virtual void setShader(const sk_sp shader) = 0; + virtual void setImageFilter(const sk_sp filter) = 0; + virtual void setColorFilter(const sk_sp filter) = 0; + virtual void setMaskFilter(const sk_sp filter) = 0; + virtual void setMaskBlurFilter(SkBlurStyle style, SkScalar sigma) = 0; + + virtual void save() = 0; + virtual void restore() = 0; + virtual void saveLayer(const SkRect* bounds, bool restoreWithPaint) = 0; + + virtual void translate(SkScalar tx, SkScalar ty) = 0; + virtual void scale(SkScalar sx, SkScalar sy) = 0; + virtual void rotate(SkScalar degrees) = 0; + virtual void skew(SkScalar sx, SkScalar sy) = 0; + virtual void transform2x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt) = 0; + virtual void transform3x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt, + SkScalar px, + SkScalar py, + SkScalar pt) = 0; + + virtual void clipRect(const SkRect& rect, bool isAA, SkClipOp clip_op) = 0; + virtual void clipRRect(const SkRRect& rrect, bool isAA, SkClipOp clip_op) = 0; + virtual void clipPath(const SkPath& path, bool isAA, SkClipOp clip_op) = 0; + + virtual void drawPaint() = 0; + virtual void drawColor(SkColor color, SkBlendMode mode) = 0; + virtual void drawLine(const SkPoint& p0, const SkPoint& p1) = 0; + virtual void drawRect(const SkRect& rect) = 0; + virtual void drawOval(const SkRect& bounds) = 0; + virtual void drawCircle(const SkPoint& center, SkScalar radius) = 0; + virtual void drawRRect(const SkRRect& rrect) = 0; + virtual void drawDRRect(const SkRRect& outer, const SkRRect& inner) = 0; + virtual void drawPath(const SkPath& path) = 0; + virtual void drawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter) = 0; + virtual void drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint pts[]) = 0; + virtual void drawVertices(const sk_sp vertices, + SkBlendMode mode) = 0; + virtual void drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling) = 0; + virtual void drawImageRect(const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + SkCanvas::SrcRectConstraint constraint) = 0; + virtual void drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter) = 0; + virtual void drawImageLattice(const sk_sp image, + const SkCanvas::Lattice& lattice, + const SkRect& dst, + SkFilterMode filter, + bool with_paint) = 0; + virtual void drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + SkBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cullRect) = 0; + virtual void drawPicture(const sk_sp picture, + const SkMatrix* matrix, + bool with_save_layer) = 0; + virtual void drawDisplayList(const sk_sp display_list) = 0; + virtual void drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) = 0; + virtual void drawShadow(const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool occludes) = 0; +}; + +// The primary class used to build a display list. The list of methods +// here matches the list of methods invoked during dispatch(). +// If there is some code that already renders to an SkCanvas object, +// those rendering commands can be captured into a DisplayList using +// the DisplayListCanvasRecorder class. +class DisplayListBuilder final : public virtual Dispatcher, public SkRefCnt { + public: + DisplayListBuilder(const SkRect& cull = SkRect::MakeEmpty()); + ~DisplayListBuilder(); + + void setAA(bool aa) override; + void setDither(bool dither) override; + void setInvertColors(bool invert) override; + void setCaps(SkPaint::Cap cap) override; + void setJoins(SkPaint::Join join) override; + void setDrawStyle(SkPaint::Style style) override; + void setStrokeWidth(SkScalar width) override; + void setMiterLimit(SkScalar limit) override; + void setColor(SkColor color) override; + void setBlendMode(SkBlendMode mode) override; + void setFilterQuality(SkFilterQuality quality) override; + void setShader(sk_sp shader) override; + void setImageFilter(sk_sp filter) override; + void setColorFilter(sk_sp filter) override; + void setMaskFilter(sk_sp filter) override; + void setMaskBlurFilter(SkBlurStyle style, SkScalar sigma) override; + + void save() override; + void restore() override; + void saveLayer(const SkRect* bounds, bool restoreWithPaint) override; + + void translate(SkScalar tx, SkScalar ty) override; + void scale(SkScalar sx, SkScalar sy) override; + void rotate(SkScalar degrees) override; + void skew(SkScalar sx, SkScalar sy) override; + void transform2x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt) override; + void transform3x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt, + SkScalar px, + SkScalar py, + SkScalar pt) override; + + void clipRect(const SkRect& rect, bool isAA, SkClipOp clip_op) override; + void clipRRect(const SkRRect& rrect, bool isAA, SkClipOp clip_op) override; + void clipPath(const SkPath& path, bool isAA, SkClipOp clip_op) override; + + void drawPaint() override; + void drawColor(SkColor color, SkBlendMode mode) override; + void drawLine(const SkPoint& p0, const SkPoint& p1) override; + void drawRect(const SkRect& rect) override; + void drawOval(const SkRect& bounds) override; + void drawCircle(const SkPoint& center, SkScalar radius) override; + void drawRRect(const SkRRect& rrect) override; + void drawDRRect(const SkRRect& outer, const SkRRect& inner) override; + void drawPath(const SkPath& path) override; + void drawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter) override; + void drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint pts[]) override; + void drawVertices(const sk_sp vertices, + SkBlendMode mode) override; + void drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling) override; + void drawImageRect( + const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + SkCanvas::SrcRectConstraint constraint = + SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint) override; + void drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter) override; + void drawImageLattice(const sk_sp image, + const SkCanvas::Lattice& lattice, + const SkRect& dst, + SkFilterMode filter, + bool with_paint) override; + void drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + SkBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cullRect) override; + void drawPicture(const sk_sp picture, + const SkMatrix* matrix, + bool with_save_layer) override; + void drawDisplayList(const sk_sp display_list) override; + void drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) override; + void drawShadow(const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool occludes) override; + + sk_sp Build(); + + private: + SkAutoTMalloc storage_; + size_t used_ = 0; + size_t allocated_ = 0; + int op_count_ = 0; + int save_level_ = 0; + + SkRect cull_; + + template + void* Push(size_t extra, Args&&... args); +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_DISPLAY_LIST_H_ diff --git a/flow/display_list_canvas.cc b/flow/display_list_canvas.cc new file mode 100644 index 0000000000000..c6070df8b198a --- /dev/null +++ b/flow/display_list_canvas.cc @@ -0,0 +1,481 @@ +// Copyright 2013 The Flutter 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 "flutter/flow/display_list_canvas.h" + +#include "flutter/flow/layers/physical_shape_layer.h" + +#include "third_party/skia/include/core/SkMaskFilter.h" +#include "third_party/skia/include/core/SkTextBlob.h" + +namespace flutter { + +void DisplayListCanvasDispatcher::save() { + canvas_->save(); +} +void DisplayListCanvasDispatcher::restore() { + canvas_->restore(); +} +void DisplayListCanvasDispatcher::saveLayer(const SkRect* bounds, + bool restore_with_paint) { + canvas_->saveLayer(bounds, restore_with_paint ? &paint() : nullptr); +} + +void DisplayListCanvasDispatcher::translate(SkScalar tx, SkScalar ty) { + canvas_->translate(tx, ty); +} +void DisplayListCanvasDispatcher::scale(SkScalar sx, SkScalar sy) { + canvas_->scale(sx, sy); +} +void DisplayListCanvasDispatcher::rotate(SkScalar degrees) { + canvas_->rotate(degrees); +} +void DisplayListCanvasDispatcher::skew(SkScalar sx, SkScalar sy) { + canvas_->skew(sx, sy); +} +void DisplayListCanvasDispatcher::transform2x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt) { + canvas_->concat(SkMatrix::MakeAll(mxx, mxy, mxt, myx, myy, myt, 0, 0, 1)); +} +void DisplayListCanvasDispatcher::transform3x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt, + SkScalar px, + SkScalar py, + SkScalar pt) { + canvas_->concat(SkMatrix::MakeAll(mxx, mxy, mxt, myx, myy, myt, px, py, pt)); +} + +void DisplayListCanvasDispatcher::clipRect(const SkRect& rect, + bool isAA, + SkClipOp clip_op) { + canvas_->clipRect(rect, clip_op, isAA); +} +void DisplayListCanvasDispatcher::clipRRect(const SkRRect& rrect, + bool isAA, + SkClipOp clip_op) { + canvas_->clipRRect(rrect, clip_op, isAA); +} +void DisplayListCanvasDispatcher::clipPath(const SkPath& path, + bool isAA, + SkClipOp clip_op) { + canvas_->clipPath(path, clip_op, isAA); +} + +void DisplayListCanvasDispatcher::drawPaint() { + canvas_->drawPaint(paint()); +} +void DisplayListCanvasDispatcher::drawColor(SkColor color, SkBlendMode mode) { + canvas_->drawColor(color, mode); +} +void DisplayListCanvasDispatcher::drawLine(const SkPoint& p0, + const SkPoint& p1) { + canvas_->drawLine(p0, p1, paint()); +} +void DisplayListCanvasDispatcher::drawRect(const SkRect& rect) { + canvas_->drawRect(rect, paint()); +} +void DisplayListCanvasDispatcher::drawOval(const SkRect& bounds) { + canvas_->drawOval(bounds, paint()); +} +void DisplayListCanvasDispatcher::drawCircle(const SkPoint& center, + SkScalar radius) { + canvas_->drawCircle(center, radius, paint()); +} +void DisplayListCanvasDispatcher::drawRRect(const SkRRect& rrect) { + canvas_->drawRRect(rrect, paint()); +} +void DisplayListCanvasDispatcher::drawDRRect(const SkRRect& outer, + const SkRRect& inner) { + canvas_->drawDRRect(outer, inner, paint()); +} +void DisplayListCanvasDispatcher::drawPath(const SkPath& path) { + canvas_->drawPath(path, paint()); +} +void DisplayListCanvasDispatcher::drawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter) { + canvas_->drawArc(bounds, start, sweep, useCenter, paint()); +} +void DisplayListCanvasDispatcher::drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint pts[]) { + canvas_->drawPoints(mode, count, pts, paint()); +} +void DisplayListCanvasDispatcher::drawVertices(const sk_sp vertices, + SkBlendMode mode) { + canvas_->drawVertices(vertices, mode, paint()); +} +void DisplayListCanvasDispatcher::drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling) { + canvas_->drawImage(image, point.fX, point.fY, sampling, &paint()); +} +void DisplayListCanvasDispatcher::drawImageRect( + const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + SkCanvas::SrcRectConstraint constraint) { + canvas_->drawImageRect(image, src, dst, sampling, &paint(), constraint); +} +void DisplayListCanvasDispatcher::drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter) { + canvas_->drawImageNine(image.get(), center, dst, filter, &paint()); +} +void DisplayListCanvasDispatcher::drawImageLattice( + const sk_sp image, + const SkCanvas::Lattice& lattice, + const SkRect& dst, + SkFilterMode filter, + bool with_paint) { + canvas_->drawImageLattice(image.get(), lattice, dst, filter, + with_paint ? &paint() : nullptr); +} +void DisplayListCanvasDispatcher::drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + SkBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cullRect) { + canvas_->drawAtlas(atlas.get(), xform, tex, colors, count, mode, sampling, + cullRect, &paint()); +} +void DisplayListCanvasDispatcher::drawPicture(const sk_sp picture, + const SkMatrix* matrix, + bool with_save_layer) { + canvas_->drawPicture(picture, matrix, with_save_layer ? &paint() : nullptr); +} +void DisplayListCanvasDispatcher::drawDisplayList( + const sk_sp display_list) { + int save_count = canvas_->save(); + { + DisplayListCanvasDispatcher dispatcher(canvas_); + display_list->Dispatch(dispatcher); + } + canvas_->restoreToCount(save_count); +} +void DisplayListCanvasDispatcher::drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) { + canvas_->drawTextBlob(blob, x, y, paint()); +} +void DisplayListCanvasDispatcher::drawShadow(const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool occludes) { + flutter::PhysicalShapeLayer::DrawShadow(canvas_, path, color, elevation, + occludes, 1.0); +} + +DisplayListCanvasRecorder::DisplayListCanvasRecorder(const SkRect& bounds) + : SkCanvasVirtualEnforcer(bounds.width(), bounds.height()), + builder_(sk_make_sp(bounds)) {} + +sk_sp DisplayListCanvasRecorder::Build() { + sk_sp display_list = builder_->Build(); + builder_.reset(); + return display_list; +} + +void DisplayListCanvasRecorder::didConcat44(const SkM44& m44) { + SkMatrix m = m44.asM33(); + if (m.hasPerspective()) { + builder_->transform3x3(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], + m[8]); + } else { + builder_->transform2x3(m[0], m[1], m[2], m[3], m[4], m[5]); + } +} +void DisplayListCanvasRecorder::didTranslate(SkScalar tx, SkScalar ty) { + builder_->translate(tx, ty); +} +void DisplayListCanvasRecorder::didScale(SkScalar sx, SkScalar sy) { + builder_->scale(sx, sy); +} + +void DisplayListCanvasRecorder::onClipRect(const SkRect& rect, + SkClipOp clip_op, + ClipEdgeStyle edgeStyle) { + builder_->clipRect(rect, edgeStyle == ClipEdgeStyle::kSoft_ClipEdgeStyle, + clip_op); +} +void DisplayListCanvasRecorder::onClipRRect(const SkRRect& rrect, + SkClipOp clip_op, + ClipEdgeStyle edgeStyle) { + builder_->clipRRect(rrect, edgeStyle == ClipEdgeStyle::kSoft_ClipEdgeStyle, + clip_op); +} +void DisplayListCanvasRecorder::onClipPath(const SkPath& path, + SkClipOp clip_op, + ClipEdgeStyle edgeStyle) { + builder_->clipPath(path, edgeStyle == ClipEdgeStyle::kSoft_ClipEdgeStyle, + clip_op); +} + +void DisplayListCanvasRecorder::willSave() { + builder_->save(); +} +SkCanvas::SaveLayerStrategy DisplayListCanvasRecorder::getSaveLayerStrategy( + const SaveLayerRec& rec) { + if (rec.fPaint) { + RecordPaintAttributes(rec.fPaint, DrawType::kSaveLayerOpType); + builder_->saveLayer(rec.fBounds, true); + } else { + builder_->saveLayer(rec.fBounds, false); + } + return SaveLayerStrategy::kNoLayer_SaveLayerStrategy; +} +void DisplayListCanvasRecorder::didRestore() { + builder_->restore(); +} + +void DisplayListCanvasRecorder::onDrawPaint(const SkPaint& paint) { + RecordPaintAttributes(&paint, DrawType::kFillOpType); + builder_->drawPaint(); +} +void DisplayListCanvasRecorder::onDrawRect(const SkRect& rect, + const SkPaint& paint) { + RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->drawRect(rect); +} +void DisplayListCanvasRecorder::onDrawRRect(const SkRRect& rrect, + const SkPaint& paint) { + RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->drawRRect(rrect); +} +void DisplayListCanvasRecorder::onDrawDRRect(const SkRRect& outer, + const SkRRect& inner, + const SkPaint& paint) { + RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->drawDRRect(outer, inner); +} +void DisplayListCanvasRecorder::onDrawOval(const SkRect& rect, + const SkPaint& paint) { + RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->drawOval(rect); +} +void DisplayListCanvasRecorder::onDrawArc(const SkRect& rect, + SkScalar startAngle, + SkScalar sweepAngle, + bool useCenter, + const SkPaint& paint) { + RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->drawArc(rect, startAngle, sweepAngle, useCenter); +} +void DisplayListCanvasRecorder::onDrawPath(const SkPath& path, + const SkPaint& paint) { + RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->drawPath(path); +} + +void DisplayListCanvasRecorder::onDrawPoints(SkCanvas::PointMode mode, + size_t count, + const SkPoint pts[], + const SkPaint& paint) { + RecordPaintAttributes(&paint, DrawType::kStrokeOpType); + if (mode == SkCanvas::PointMode::kLines_PointMode && count == 2) { + builder_->drawLine(pts[0], pts[1]); + } else { + uint32_t count32 = static_cast(count); + // TODO(flar): depending on the mode we could break it down into + // multiple calls to drawPoints, but how much do we really want + // to support more than a couple billion points? + FML_DCHECK(count32 == count); + builder_->drawPoints(mode, count32, pts); + } +} +void DisplayListCanvasRecorder::onDrawVerticesObject(const SkVertices* vertices, + SkBlendMode mode, + const SkPaint& paint) { + RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->drawVertices(sk_ref_sp(vertices), mode); +} + +void DisplayListCanvasRecorder::onDrawImage2(const SkImage* image, + SkScalar dx, + SkScalar dy, + const SkSamplingOptions& sampling, + const SkPaint* paint) { + RecordPaintAttributes(paint, DrawType::kImageOpType); + builder_->drawImage(sk_ref_sp(image), SkPoint::Make(dx, dy), sampling); +} +void DisplayListCanvasRecorder::onDrawImageRect2( + const SkImage* image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + const SkPaint* paint, + SrcRectConstraint constraint) { + RecordPaintAttributes(paint, DrawType::kImageRectOpType); + builder_->drawImageRect(sk_ref_sp(image), src, dst, sampling, constraint); +} +void DisplayListCanvasRecorder::onDrawImageLattice2(const SkImage* image, + const Lattice& lattice, + const SkRect& dst, + SkFilterMode filter, + const SkPaint* paint) { + if (paint != nullptr) { + // SkCanvas will always construct a paint, + // though it is a default paint most of the time + SkPaint default_paint; + if (*paint == default_paint) { + paint = nullptr; + } else { + RecordPaintAttributes(paint, DrawType::kImageOpType); + } + } + builder_->drawImageLattice(sk_ref_sp(image), lattice, dst, filter, + paint != nullptr); +} +void DisplayListCanvasRecorder::onDrawAtlas2(const SkImage* image, + const SkRSXform xform[], + const SkRect src[], + const SkColor colors[], + int count, + SkBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cull, + const SkPaint* paint) { + RecordPaintAttributes(paint, DrawType::kImageOpType); + builder_->drawAtlas(sk_ref_sp(image), xform, src, colors, count, mode, + sampling, cull); +} + +void DisplayListCanvasRecorder::onDrawTextBlob(const SkTextBlob* blob, + SkScalar x, + SkScalar y, + const SkPaint& paint) { + RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->drawTextBlob(sk_ref_sp(blob), x, y); +} +void DisplayListCanvasRecorder::onDrawShadowRec(const SkPath& path, + const SkDrawShadowRec& rec) { + // Skia does not expose the SkDrawShadowRec structure in a public + // header file so we cannot record this operation. + // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125 + FML_DCHECK(false); +} + +void DisplayListCanvasRecorder::onDrawPicture(const SkPicture* picture, + const SkMatrix* matrix, + const SkPaint* paint) { + if (paint) { + RecordPaintAttributes(paint, DrawType::kSaveLayerOpType); + } + builder_->drawPicture(sk_ref_sp(picture), matrix, paint != nullptr); +} + +void DisplayListCanvasRecorder::RecordPaintAttributes(const SkPaint* paint, + DrawType type) { + int dataNeeded; + switch (type) { + case DrawType::kDrawOpType: + dataNeeded = kDrawMask_; + break; + case DrawType::kFillOpType: + dataNeeded = kPaintMask_; + break; + case DrawType::kStrokeOpType: + dataNeeded = kStrokeMask_; + break; + case DrawType::kImageOpType: + dataNeeded = kImageMask_; + break; + case DrawType::kImageRectOpType: + dataNeeded = kImageRectMask_; + break; + case DrawType::kSaveLayerOpType: + dataNeeded = kSaveLayerMask_; + break; + default: + FML_DCHECK(false); + return; + } + if (paint == nullptr) { + paint = new SkPaint(); + } + if ((dataNeeded & kAaNeeded_) != 0 && current_aa_ != paint->isAntiAlias()) { + builder_->setAA(current_aa_ = paint->isAntiAlias()); + } + if ((dataNeeded & kDitherNeeded_) != 0 && + current_dither_ != paint->isDither()) { + builder_->setDither(current_dither_ = paint->isDither()); + } + if ((dataNeeded & kColorNeeded_) != 0 && + current_color_ != paint->getColor()) { + builder_->setColor(current_color_ = paint->getColor()); + } + if ((dataNeeded & kBlendNeeded_) != 0 && + current_blend_ != paint->getBlendMode()) { + builder_->setBlendMode(current_blend_ = paint->getBlendMode()); + } + // invert colors is a Flutter::Paint thing, not an SkPaint thing + // if ((dataNeeded & invertColorsNeeded_) != 0 && + // currentInvertColors_ != paint->???) { + // currentInvertColors_ = paint->invertColors; + // addOp_(currentInvertColors_ + // ? _CanvasOp.setInvertColors + // : _CanvasOp.clearInvertColors, 0); + // } + if ((dataNeeded & kPaintStyleNeeded_) != 0) { + if (current_style_ != paint->getStyle()) { + builder_->setDrawStyle(current_style_ = paint->getStyle()); + } + if (current_style_ == SkPaint::Style::kStroke_Style) { + dataNeeded |= kStrokeStyleNeeded_; + } + } + if ((dataNeeded & kStrokeStyleNeeded_) != 0) { + if (current_stroke_width_ != paint->getStrokeWidth()) { + builder_->setStrokeWidth(current_stroke_width_ = paint->getStrokeWidth()); + } + if (current_cap_ != paint->getStrokeCap()) { + builder_->setCaps(current_cap_ = paint->getStrokeCap()); + } + if (current_join_ != paint->getStrokeJoin()) { + builder_->setJoins(current_join_ = paint->getStrokeJoin()); + } + if (current_miter_limit_ != paint->getStrokeMiter()) { + builder_->setMiterLimit(current_miter_limit_ = paint->getStrokeMiter()); + } + } + if ((dataNeeded & kFilterQualityNeeded_) != 0 && + current_fq_ != paint->getFilterQuality()) { + builder_->setFilterQuality(current_fq_ = paint->getFilterQuality()); + } + if ((dataNeeded & kShaderNeeded_) != 0 && + current_shader_.get() != paint->getShader()) { + builder_->setShader(current_shader_ = sk_ref_sp(paint->getShader())); + } + if ((dataNeeded & kColorFilterNeeded_) != 0 && + current_color_filter_.get() != paint->getColorFilter()) { + builder_->setColorFilter(current_color_filter_ = + sk_ref_sp(paint->getColorFilter())); + } + if ((dataNeeded & kImageFilterNeeded_) != 0 && + current_image_filter_.get() != paint->getImageFilter()) { + builder_->setImageFilter(current_image_filter_ = + sk_ref_sp(paint->getImageFilter())); + } + if ((dataNeeded & kMaskFilterNeeded_) != 0 && + current_mask_filter_.get() != paint->getMaskFilter()) { + builder_->setMaskFilter(current_mask_filter_ = + sk_ref_sp(paint->getMaskFilter())); + } +} + +} // namespace flutter diff --git a/flow/display_list_canvas.h b/flow/display_list_canvas.h new file mode 100644 index 0000000000000..da535a7d26635 --- /dev/null +++ b/flow/display_list_canvas.h @@ -0,0 +1,312 @@ +// Copyright 2013 The Flutter 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 FLUTTER_FLOW_DISPLAY_LIST_CANVAS_H_ +#define FLUTTER_FLOW_DISPLAY_LIST_CANVAS_H_ + +#include "flutter/flow/display_list.h" +#include "flutter/flow/display_list_utils.h" +#include "flutter/fml/logging.h" + +#include "third_party/skia/include/core/SkCanvasVirtualEnforcer.h" +#include "third_party/skia/include/utils/SkNoDrawCanvas.h" + +// Classes to interact between SkCanvas and DisplayList, including: +// DisplayListCanvasDispatcher: +// Can be fed to the dispatch() method of a DisplayList to feed +// the resulting rendering operations to an SkCanvas instance. +// DisplayListCanvasRecorder +// An adapter that implements an SkCanvas interface which can +// then be handed to code that outputs to an SkCanvas to capture +// the output into a Flutter DisplayList. + +namespace flutter { + +// Receives all methods on Dispatcher and sends them to an SkCanvas +class DisplayListCanvasDispatcher : public virtual Dispatcher, + public SkPaintDispatchHelper { + public: + DisplayListCanvasDispatcher(SkCanvas* canvas) : canvas_(canvas) {} + + void save() override; + void restore() override; + void saveLayer(const SkRect* bounds, bool restore_with_paint) override; + + void translate(SkScalar tx, SkScalar ty) override; + void scale(SkScalar sx, SkScalar sy) override; + void rotate(SkScalar degrees) override; + void skew(SkScalar sx, SkScalar sy) override; + void transform2x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt) override; + void transform3x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt, + SkScalar px, + SkScalar py, + SkScalar pt) override; + + void clipRect(const SkRect& rect, bool isAA, SkClipOp clip_op) override; + void clipRRect(const SkRRect& rrect, bool isAA, SkClipOp clip_op) override; + void clipPath(const SkPath& path, bool isAA, SkClipOp clip_op) override; + + void drawPaint() override; + void drawColor(SkColor color, SkBlendMode mode) override; + void drawLine(const SkPoint& p0, const SkPoint& p1) override; + void drawRect(const SkRect& rect) override; + void drawOval(const SkRect& bounds) override; + void drawCircle(const SkPoint& center, SkScalar radius) override; + void drawRRect(const SkRRect& rrect) override; + void drawDRRect(const SkRRect& outer, const SkRRect& inner) override; + void drawPath(const SkPath& path) override; + void drawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter) override; + void drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint pts[]) override; + void drawVertices(const sk_sp vertices, + SkBlendMode mode) override; + void drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling) override; + void drawImageRect(const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + SkCanvas::SrcRectConstraint constraint) override; + void drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter) override; + void drawImageLattice(const sk_sp image, + const SkCanvas::Lattice& lattice, + const SkRect& dst, + SkFilterMode filter, + bool with_paint) override; + void drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + SkBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cullRect) override; + void drawPicture(const sk_sp picture, + const SkMatrix* matrix, + bool with_save_layer) override; + void drawDisplayList(const sk_sp display_list) override; + void drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) override; + void drawShadow(const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool occludes) override; + + private: + SkCanvas* canvas_; +}; + +// Receives all methods on SkCanvas and sends them to a DisplayListBuilder +class DisplayListCanvasRecorder + : public SkCanvasVirtualEnforcer, + public SkRefCnt { + public: + DisplayListCanvasRecorder(const SkRect& bounds); + + const sk_sp builder() { return builder_; } + + sk_sp Build(); + + void didConcat44(const SkM44&) override; + void didSetM44(const SkM44&) override { FML_DCHECK(false); } + void didTranslate(SkScalar, SkScalar) override; + void didScale(SkScalar, SkScalar) override; + + void onClipRect(const SkRect& rect, + SkClipOp op, + ClipEdgeStyle edgeStyle) override; + void onClipRRect(const SkRRect& rrect, + SkClipOp op, + ClipEdgeStyle edgeStyle) override; + void onClipPath(const SkPath& path, + SkClipOp op, + ClipEdgeStyle edgeStyle) override; + + void willSave() override; + SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec&) override; + void didRestore() override; + + void onDrawPaint(const SkPaint& paint) override; + void onDrawBehind(const SkPaint&) override { FML_DCHECK(false); } + void onDrawRect(const SkRect& rect, const SkPaint& paint) override; + void onDrawRRect(const SkRRect& rrect, const SkPaint& paint) override; + void onDrawDRRect(const SkRRect& outer, + const SkRRect& inner, + const SkPaint& paint) override; + void onDrawOval(const SkRect& rect, const SkPaint& paint) override; + void onDrawArc(const SkRect& rect, + SkScalar startAngle, + SkScalar sweepAngle, + bool useCenter, + const SkPaint& paint) override; + void onDrawPath(const SkPath& path, const SkPaint& paint) override; + void onDrawRegion(const SkRegion& region, const SkPaint& paint) override { + FML_DCHECK(false); + } + + void onDrawTextBlob(const SkTextBlob* blob, + SkScalar x, + SkScalar y, + const SkPaint& paint) override; + + void onDrawPatch(const SkPoint cubics[12], + const SkColor colors[4], + const SkPoint texCoords[4], + SkBlendMode mode, + const SkPaint& paint) override { + FML_DCHECK(false); + } + void onDrawPoints(SkCanvas::PointMode mode, + size_t count, + const SkPoint pts[], + const SkPaint& paint) override; + void onDrawVerticesObject(const SkVertices* vertices, + SkBlendMode mode, + const SkPaint& paint) override; + + void onDrawImage2(const SkImage*, + SkScalar dx, + SkScalar dy, + const SkSamplingOptions&, + const SkPaint*) override; + void onDrawImageRect2(const SkImage*, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions&, + const SkPaint*, + SrcRectConstraint) override; + void onDrawImageLattice2(const SkImage*, + const Lattice&, + const SkRect& dst, + SkFilterMode, + const SkPaint*) override; + void onDrawAtlas2(const SkImage*, + const SkRSXform[], + const SkRect src[], + const SkColor[], + int count, + SkBlendMode, + const SkSamplingOptions&, + const SkRect* cull, + const SkPaint*) override; + + void onDrawEdgeAAQuad(const SkRect& rect, + const SkPoint clip[4], + SkCanvas::QuadAAFlags aaFlags, + const SkColor4f& color, + SkBlendMode mode) override { + FML_DCHECK(0); + } + + void onDrawAnnotation(const SkRect& rect, + const char key[], + SkData* value) override { + FML_DCHECK(false); + } + void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&) override; + + void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix) override { + FML_DCHECK(false); + } + void onDrawPicture(const SkPicture* picture, + const SkMatrix* matrix, + const SkPaint* paint) override; + + enum class DrawType { + // The operation will be an image operation + kImageOpType, + // The operation will be an imageRect operation + kImageRectOpType, + // The operation will be a fill or stroke depending on the paint.style + kDrawOpType, + // The operation will be a fill (ignoring paint.style) + kFillOpType, + // The operation will be a stroke (ignoring paint.style) + kStrokeOpType, + // The operation will be a saveLayer with a paint object + kSaveLayerOpType, + }; + + void RecordPaintAttributes(const SkPaint* paint, DrawType type); + + private: + sk_sp builder_; + + // Mask bits for the various attributes that might be needed for a given + // operation. + // clang-format off + static constexpr int kAaNeeded_ = 1 << 0; + static constexpr int kColorNeeded_ = 1 << 1; + static constexpr int kBlendNeeded_ = 1 << 2; + static constexpr int kInvertColorsNeeded_ = 1 << 3; + static constexpr int kFilterQualityNeeded_ = 1 << 4; + static constexpr int kPaintStyleNeeded_ = 1 << 5; + static constexpr int kStrokeStyleNeeded_ = 1 << 6; + static constexpr int kShaderNeeded_ = 1 << 7; + static constexpr int kColorFilterNeeded_ = 1 << 8; + static constexpr int kImageFilterNeeded_ = 1 << 9; + static constexpr int kMaskFilterNeeded_ = 1 << 10; + static constexpr int kDitherNeeded_ = 1 << 11; + // clang-format on + + // Combinations of the above mask bits that are common to typical "draw" + // calls. + // Note that the strokeStyle_ is handled conditionally depending on whether + // the paintStyle_ attribute value is synchronized. It can also be manually + // specified for operations that will be always stroking, like [drawLine]. + static constexpr int kPaintMask_ = kAaNeeded_ | kColorNeeded_ | + kBlendNeeded_ | kInvertColorsNeeded_ | + kColorFilterNeeded_ | kShaderNeeded_ | + kDitherNeeded_ | kImageFilterNeeded_; + static constexpr int kDrawMask_ = + kPaintMask_ | kPaintStyleNeeded_ | kMaskFilterNeeded_; + static constexpr int kStrokeMask_ = + kPaintMask_ | kStrokeStyleNeeded_ | kMaskFilterNeeded_; + static constexpr int kImageMask_ = + kColorNeeded_ | kBlendNeeded_ | kInvertColorsNeeded_ | + kColorFilterNeeded_ | kDitherNeeded_ | kImageFilterNeeded_ | + kFilterQualityNeeded_ | kMaskFilterNeeded_; + static constexpr int kImageRectMask_ = kImageMask_ | kAaNeeded_; + static constexpr int kSaveLayerMask_ = + kColorNeeded_ | kBlendNeeded_ | kInvertColorsNeeded_ | + kColorFilterNeeded_ | kImageFilterNeeded_; + + bool current_aa_ = false; + bool current_dither_ = false; + SkColor current_color_ = 0xFF000000; + SkBlendMode current_blend_ = SkBlendMode::kSrcOver; + SkPaint::Style current_style_ = SkPaint::Style::kFill_Style; + SkScalar current_stroke_width_ = 0.0; + SkScalar current_miter_limit_ = 4.0; + SkPaint::Cap current_cap_ = SkPaint::Cap::kButt_Cap; + SkPaint::Join current_join_ = SkPaint::Join::kMiter_Join; + SkFilterQuality current_fq_ = SkFilterQuality::kNone_SkFilterQuality; + sk_sp current_shader_; + sk_sp current_color_filter_; + sk_sp current_image_filter_; + sk_sp current_mask_filter_; +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_DISPLAY_LIST_CANVAS_H_ diff --git a/flow/display_list_canvas_unittests.cc b/flow/display_list_canvas_unittests.cc new file mode 100644 index 0000000000000..cd2f6ff88e620 --- /dev/null +++ b/flow/display_list_canvas_unittests.cc @@ -0,0 +1,1296 @@ +// Copyright 2013 The Flutter 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 "flutter/flow/display_list_canvas.h" +#include "flutter/flow/layers/physical_shape_layer.h" + +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" +#include "third_party/skia/include/core/SkRRect.h" +#include "third_party/skia/include/core/SkRSXform.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/core/SkTextBlob.h" +#include "third_party/skia/include/core/SkVertices.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "third_party/skia/include/effects/SkImageFilters.h" + +#include + +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +constexpr int TestWidth = 200; +constexpr int TestHeight = 200; +constexpr int RenderWidth = 100; +constexpr int RenderHeight = 100; +constexpr int RenderLeft = (TestWidth - RenderWidth) / 2; +constexpr int RenderTop = (TestHeight - RenderHeight) / 2; +constexpr int RenderRight = RenderLeft + RenderWidth; +constexpr int RenderBottom = RenderTop + RenderHeight; +constexpr int RenderCenterX = (RenderLeft + RenderRight) / 2; +constexpr int RenderCenterY = (RenderTop + RenderBottom) / 2; +constexpr SkScalar RenderRadius = std::min(RenderWidth, RenderHeight) / 2.0; +constexpr SkScalar RenderCornerRadius = RenderRadius / 5.0; + +constexpr SkPoint TestCenter = SkPoint::Make(TestWidth / 2, TestHeight / 2); +constexpr SkRect TestBounds = SkRect::MakeWH(TestWidth, TestHeight); +constexpr SkRect RenderBounds = + SkRect::MakeLTRB(RenderLeft, RenderTop, RenderRight, RenderBottom); + +class CanvasCompareTester { + public: + // If a test is using any shadow operations then we cannot currently + // record those in an SkCanvas and play it back into a DisplayList + // because internally the operation gets encapsulated in a Skia + // ShadowRec which is not exposed by their headers. For operations + // that use shadows, we can perform a lot of tests, but not the tests + // that require SkCanvas->DisplayList transfers. + // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125 + static bool UsingShadows; + + typedef const std::function CvRenderer; + typedef const std::function DlRenderer; + + static void RenderAll(CvRenderer& cv_renderer, DlRenderer& dl_renderer) { + RenderWithAttributes(cv_renderer, dl_renderer); + RenderWithTransforms(cv_renderer, dl_renderer); + RenderWithClips(cv_renderer, dl_renderer); + } + + static void RenderNoAttributes(CvRenderer& cv_renderer, + DlRenderer& dl_renderer) { + RenderWith([=](SkCanvas*, SkPaint& p) {}, // + [=](DisplayListBuilder& d) {}, // + cv_renderer, dl_renderer, "Base Test"); + RenderWithTransforms(cv_renderer, dl_renderer); + RenderWithClips(cv_renderer, dl_renderer); + } + + static void RenderWithSaveRestore(CvRenderer& cv_renderer, + DlRenderer& dl_renderer) { + SkRect clip = SkRect::MakeLTRB(0, 0, 10, 10); + SkRect rect = SkRect::MakeLTRB(5, 5, 15, 15); + SkColor save_layer_color = SkColorSetARGB(0x7f, 0x00, 0xff, 0xff); + RenderWith( + [=](SkCanvas* cv, SkPaint& p) { + cv->save(); + cv->clipRect(clip, SkClipOp::kIntersect, false); + cv->drawRect(rect, p); + cv->restore(); + }, + [=](DisplayListBuilder& b) { + b.save(); + b.clipRect(clip, false, SkClipOp::kIntersect); + b.drawRect(rect); + b.restore(); + }, + cv_renderer, dl_renderer, "With prior save/clip/restore"); + RenderWith( + [=](SkCanvas* cv, SkPaint& p) { + SkPaint save_p; + save_p.setColor(save_layer_color); + cv->saveLayer(RenderBounds, &save_p); + cv->drawRect(rect, p); + }, + [=](DisplayListBuilder& b) { + b.setColor(save_layer_color); + b.saveLayer(&RenderBounds, true); + b.setColor(SkPaint().getColor()); + b.drawRect(rect); + }, + cv_renderer, dl_renderer, "With saveLayer"); + } + + static void RenderWithAttributes(CvRenderer& cv_renderer, + DlRenderer& dl_renderer) { + RenderWith([=](SkCanvas*, SkPaint& p) {}, // + [=](DisplayListBuilder& d) {}, // + cv_renderer, dl_renderer, "Base Test"); + + RenderWith([=](SkCanvas*, SkPaint& p) { p.setAntiAlias(true); }, // + [=](DisplayListBuilder& b) { b.setAA(true); }, // + cv_renderer, dl_renderer, "AA == True"); + RenderWith([=](SkCanvas*, SkPaint& p) { p.setAntiAlias(false); }, // + [=](DisplayListBuilder& b) { b.setAA(false); }, // + cv_renderer, dl_renderer, "AA == False"); + + RenderWith([=](SkCanvas*, SkPaint& p) { p.setDither(true); }, // + [=](DisplayListBuilder& b) { b.setDither(true); }, // + cv_renderer, dl_renderer, "Dither == True"); + RenderWith([=](SkCanvas*, SkPaint& p) { p.setDither(false); }, // + [=](DisplayListBuilder& b) { b.setDither(false); }, // + cv_renderer, dl_renderer, "Dither = False"); + + RenderWith([=](SkCanvas*, SkPaint& p) { p.setColor(SK_ColorBLUE); }, // + [=](DisplayListBuilder& b) { b.setColor(SK_ColorBLUE); }, // + cv_renderer, dl_renderer, "Color == Blue"); + RenderWith([=](SkCanvas*, SkPaint& p) { p.setColor(SK_ColorGREEN); }, // + [=](DisplayListBuilder& b) { b.setColor(SK_ColorGREEN); }, // + cv_renderer, dl_renderer, "Color == Green"); + + RenderWithStrokes(cv_renderer, dl_renderer); + + // Not testing FilterQuality here because there is no SkPaint version + + { + // half opaque cyan + SkColor blendableColor = SkColorSetARGB(0x7f, 0x00, 0xff, 0xff); + SkColor bg = SK_ColorWHITE; + + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setBlendMode(SkBlendMode::kSrcIn); + p.setColor(blendableColor); + }, + [=](DisplayListBuilder& b) { + b.setBlendMode(SkBlendMode::kSrcIn); + b.setColor(blendableColor); + }, + cv_renderer, dl_renderer, "Blend == SrcIn", &bg); + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setBlendMode(SkBlendMode::kDstIn); + p.setColor(blendableColor); + }, + [=](DisplayListBuilder& b) { + b.setBlendMode(SkBlendMode::kDstIn); + b.setColor(blendableColor); + }, + cv_renderer, dl_renderer, "Blend == DstIn", &bg); + } + + { + sk_sp filter = + SkImageFilters::Blur(5.0, 5.0, SkTileMode::kDecal, nullptr, nullptr); + { + RenderWith([=](SkCanvas*, SkPaint& p) { p.setImageFilter(filter); }, + [=](DisplayListBuilder& b) { b.setImageFilter(filter); }, + cv_renderer, dl_renderer, "ImageFilter == Decal Blur 5"); + } + ASSERT_TRUE(filter->unique()) << "ImageFilter Cleanup"; + } + + { + // clang-format off + constexpr float rotate_color_matrix[20] = { + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 1, 0, 0, 0, 0, + 0, 0, 0, 1, 0, + }; + constexpr float invert_color_matrix[20] = { + -1.0, 0, 0, 1.0, 0, + 0, -1.0, 0, 1.0, 0, + 0, 0, -1.0, 1.0, 0, + 1.0, 1.0, 1.0, 1.0, 0, + }; + // clang-format on + sk_sp filter = SkColorFilters::Matrix(rotate_color_matrix); + { + SkColor bg = SK_ColorWHITE; + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setColor(SK_ColorYELLOW); + p.setColorFilter(filter); + }, + [=](DisplayListBuilder& b) { + b.setColor(SK_ColorYELLOW); + b.setColorFilter(filter); + }, + cv_renderer, dl_renderer, "ColorFilter == RotateRGB", &bg); + } + ASSERT_TRUE(filter->unique()) << "ColorFilter Cleanup"; + filter = SkColorFilters::Matrix(invert_color_matrix); + { + SkColor bg = SK_ColorWHITE; + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setColor(SK_ColorYELLOW); + p.setColorFilter(filter); + }, + [=](DisplayListBuilder& b) { + b.setColor(SK_ColorYELLOW); + b.setInvertColors(true); + }, + cv_renderer, dl_renderer, "ColorFilter == Invert", &bg); + } + ASSERT_TRUE(filter->unique()) << "ColorFilter Cleanup"; + } + + { + sk_sp filter = + SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5.0); + { + RenderWith([=](SkCanvas*, SkPaint& p) { p.setMaskFilter(filter); }, + [=](DisplayListBuilder& b) { b.setMaskFilter(filter); }, + cv_renderer, dl_renderer, "MaskFilter == Blur 5"); + } + ASSERT_TRUE(filter->unique()) << "MaskFilter Cleanup"; + { + RenderWith([=](SkCanvas*, SkPaint& p) { p.setMaskFilter(filter); }, + [=](DisplayListBuilder& b) { + b.setMaskBlurFilter(kNormal_SkBlurStyle, 5.0); + }, + cv_renderer, dl_renderer, "MaskFilter == Blur(Normal, 5.0)"); + } + ASSERT_TRUE(filter->unique()) << "MaskFilter Cleanup"; + } + + { + SkPoint end_points[] = { + SkPoint::Make(RenderBounds.fLeft, RenderBounds.fTop), + SkPoint::Make(RenderBounds.fRight, RenderBounds.fBottom), + }; + SkColor colors[] = { + SK_ColorGREEN, + SK_ColorYELLOW, + SK_ColorBLUE, + }; + float stops[] = { + 0.0, + 0.5, + 1.0, + }; + sk_sp shader = SkGradientShader::MakeLinear( + end_points, colors, stops, 3, SkTileMode::kMirror, 0, nullptr); + { + RenderWith([=](SkCanvas*, SkPaint& p) { p.setShader(shader); }, + [=](DisplayListBuilder& b) { b.setShader(shader); }, + cv_renderer, dl_renderer, "LinearGradient GYB"); + } + ASSERT_TRUE(shader->unique()) << "Shader Cleanup"; + } + } + + static void RenderWithStrokes(CvRenderer& cv_renderer, + DlRenderer& dl_renderer) { + RenderWith( + [=](SkCanvas*, SkPaint& p) { p.setStyle(SkPaint::kFill_Style); }, + [=](DisplayListBuilder& b) { b.setDrawStyle(SkPaint::kFill_Style); }, + cv_renderer, dl_renderer, "Fill"); + RenderWith( + [=](SkCanvas*, SkPaint& p) { p.setStyle(SkPaint::kStroke_Style); }, + [=](DisplayListBuilder& b) { b.setDrawStyle(SkPaint::kStroke_Style); }, + cv_renderer, dl_renderer, "Stroke + defaults"); + + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kFill_Style); + p.setStrokeWidth(10.0); + }, + [=](DisplayListBuilder& b) { + b.setDrawStyle(SkPaint::kFill_Style); + b.setStrokeWidth(10.0); + }, + cv_renderer, dl_renderer, "Fill + unnecessary StrokeWidth 10"); + + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(10.0); + }, + [=](DisplayListBuilder& b) { + b.setDrawStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(10.0); + }, + cv_renderer, dl_renderer, "Stroke Width 10"); + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + }, + [=](DisplayListBuilder& b) { + b.setDrawStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + }, + cv_renderer, dl_renderer, "Stroke Width 5"); + + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeCap(SkPaint::kButt_Cap); + }, + [=](DisplayListBuilder& b) { + b.setDrawStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setCaps(SkPaint::kButt_Cap); + }, + cv_renderer, dl_renderer, "Stroke Width 5, Butt Cap"); + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeCap(SkPaint::kRound_Cap); + }, + [=](DisplayListBuilder& b) { + b.setDrawStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setCaps(SkPaint::kRound_Cap); + }, + cv_renderer, dl_renderer, "Stroke Width 5, Round Cap"); + + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeJoin(SkPaint::kBevel_Join); + }, + [=](DisplayListBuilder& b) { + b.setDrawStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setJoins(SkPaint::kBevel_Join); + }, + cv_renderer, dl_renderer, "Stroke Width 5, Bevel Join"); + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeJoin(SkPaint::kRound_Join); + }, + [=](DisplayListBuilder& b) { + b.setDrawStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setJoins(SkPaint::kRound_Join); + }, + cv_renderer, dl_renderer, "Stroke Width 5, Round Join"); + + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeMiter(100.0); + p.setStrokeJoin(SkPaint::kMiter_Join); + }, + [=](DisplayListBuilder& b) { + b.setDrawStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setMiterLimit(100.0); + b.setJoins(SkPaint::kMiter_Join); + }, + cv_renderer, dl_renderer, "Stroke Width 5, Miter 100"); + + RenderWith( + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeMiter(0.0); + p.setStrokeJoin(SkPaint::kMiter_Join); + }, + [=](DisplayListBuilder& b) { + b.setDrawStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setMiterLimit(0.0); + b.setJoins(SkPaint::kMiter_Join); + }, + cv_renderer, dl_renderer, "Stroke Width 5, Miter 0"); + } + + static void RenderWithTransforms(CvRenderer& cv_renderer, + DlRenderer& dl_renderer) { + RenderWith([=](SkCanvas* c, SkPaint&) { c->translate(5, 10); }, // + [=](DisplayListBuilder& b) { b.translate(5, 10); }, // + cv_renderer, dl_renderer, "Translate 5, 10"); + RenderWith([=](SkCanvas* c, SkPaint&) { c->scale(0.95, 0.95); }, // + [=](DisplayListBuilder& b) { b.scale(0.95, 0.95); }, // + cv_renderer, dl_renderer, "Scale 95%"); + RenderWith([=](SkCanvas* c, SkPaint&) { c->rotate(5); }, // + [=](DisplayListBuilder& b) { b.rotate(5); }, // + cv_renderer, dl_renderer, "Rotate 5 degrees"); + RenderWith([=](SkCanvas* c, SkPaint&) { c->skew(0.05, 0.05); }, // + [=](DisplayListBuilder& b) { b.skew(0.05, 0.05); }, // + cv_renderer, dl_renderer, "Skew 5%"); + { + SkMatrix tx = SkMatrix::MakeAll(1.1, 0.1, 1.05, 0.05, 1, 1, 0, 0, 1); + RenderWith([=](SkCanvas* c, SkPaint&) { c->concat(tx); }, // + [=](DisplayListBuilder& b) { + b.transform2x3(tx[0], tx[1], tx[2], // + tx[3], tx[4], tx[5]); + }, // + cv_renderer, dl_renderer, "Transform 2x3"); + } + { + SkMatrix tx = SkMatrix::MakeAll(1.1, 0.1, 1.05, 0.05, 1, 1, 0, 0, 1.01); + RenderWith([=](SkCanvas* c, SkPaint&) { c->concat(tx); }, // + [=](DisplayListBuilder& b) { + b.transform3x3(tx[0], tx[1], tx[2], // + tx[3], tx[4], tx[5], // + tx[6], tx[7], tx[8]); + }, // + cv_renderer, dl_renderer, "Transform 3x3"); + } + } + + static void RenderWithClips(CvRenderer& cv_renderer, + DlRenderer& dl_renderer) { + SkRect r_clip = RenderBounds.makeInset(15.5, 15.5); + RenderWith( + [=](SkCanvas* c, SkPaint&) { + c->clipRect(r_clip, SkClipOp::kIntersect, false); + }, + [=](DisplayListBuilder& b) { + b.clipRect(r_clip, false, SkClipOp::kIntersect); + }, + cv_renderer, dl_renderer, "Hard ClipRect inset by 15.5"); + RenderWith( + [=](SkCanvas* c, SkPaint&) { + c->clipRect(r_clip, SkClipOp::kIntersect, true); + }, + [=](DisplayListBuilder& b) { + b.clipRect(r_clip, true, SkClipOp::kIntersect); + }, + cv_renderer, dl_renderer, "AA ClipRect inset by 15.5"); + RenderWith( + [=](SkCanvas* c, SkPaint&) { + c->clipRect(r_clip, SkClipOp::kDifference, false); + }, + [=](DisplayListBuilder& b) { + b.clipRect(r_clip, false, SkClipOp::kDifference); + }, + cv_renderer, dl_renderer, "Hard ClipRect Diff, inset by 15.5"); + SkRRect rr_clip = SkRRect::MakeRectXY(r_clip, 1.8, 2.7); + RenderWith( + [=](SkCanvas* c, SkPaint&) { + c->clipRRect(rr_clip, SkClipOp::kIntersect, false); + }, + [=](DisplayListBuilder& b) { + b.clipRRect(rr_clip, false, SkClipOp::kIntersect); + }, + cv_renderer, dl_renderer, "Hard ClipRRect inset by 15.5"); + RenderWith( + [=](SkCanvas* c, SkPaint&) { + c->clipRRect(rr_clip, SkClipOp::kIntersect, true); + }, + [=](DisplayListBuilder& b) { + b.clipRRect(rr_clip, true, SkClipOp::kIntersect); + }, + cv_renderer, dl_renderer, "AA ClipRRect inset by 15.5"); + RenderWith( + [=](SkCanvas* c, SkPaint&) { + c->clipRRect(rr_clip, SkClipOp::kDifference, false); + }, + [=](DisplayListBuilder& b) { + b.clipRRect(rr_clip, false, SkClipOp::kDifference); + }, + cv_renderer, dl_renderer, "Hard ClipRRect Diff, inset by 15.5"); + SkPath path_clip = SkPath(); + path_clip.setFillType(SkPathFillType::kEvenOdd); + path_clip.addRect(r_clip); + path_clip.addCircle(RenderCenterX, RenderCenterY, 1.0); + RenderWith( + [=](SkCanvas* c, SkPaint&) { + c->clipPath(path_clip, SkClipOp::kIntersect, false); + }, + [=](DisplayListBuilder& b) { + b.clipPath(path_clip, false, SkClipOp::kIntersect); + }, + cv_renderer, dl_renderer, "Hard ClipPath inset by 15.5"); + RenderWith( + [=](SkCanvas* c, SkPaint&) { + c->clipPath(path_clip, SkClipOp::kIntersect, true); + }, + [=](DisplayListBuilder& b) { + b.clipPath(path_clip, true, SkClipOp::kIntersect); + }, + cv_renderer, dl_renderer, "AA ClipPath inset by 15.5"); + RenderWith( + [=](SkCanvas* c, SkPaint&) { + c->clipPath(path_clip, SkClipOp::kDifference, false); + }, + [=](DisplayListBuilder& b) { + b.clipPath(path_clip, false, SkClipOp::kDifference); + }, + cv_renderer, dl_renderer, "Hard ClipPath Diff, inset by 15.5"); + } + + static SkRect getSkBounds(CvRenderer& cv_setup, CvRenderer& cv_render) { + SkPictureRecorder recorder; + SkRTreeFactory rtree_factory; + SkCanvas* cv = recorder.beginRecording(TestBounds, &rtree_factory); + SkPaint p; + cv_setup(cv, p); + cv_render(cv, p); + return recorder.finishRecordingAsPicture()->cullRect(); + } + + static void RenderWith(CvRenderer& cv_setup, + DlRenderer& dl_setup, + CvRenderer& cv_render, + DlRenderer& dl_render, + const std::string info, + const SkColor* bg = nullptr) { + // surface1 is direct rendering via SkCanvas to SkSurface + // DisplayList mechanisms are not involved in this operation + sk_sp ref_surface = makeSurface(bg); + SkPaint paint1; + cv_setup(ref_surface->getCanvas(), paint1); + cv_render(ref_surface->getCanvas(), paint1); + SkRect ref_bounds = getSkBounds(cv_setup, cv_render); + SkPixmap ref_pixels; + ASSERT_TRUE(ref_surface->peekPixels(&ref_pixels)) << info; + ASSERT_EQ(ref_pixels.width(), TestWidth) << info; + ASSERT_EQ(ref_pixels.height(), TestHeight) << info; + ASSERT_EQ(ref_pixels.info().bytesPerPixel(), 4) << info; + checkPixels(&ref_pixels, ref_bounds, info, bg); + + { + // This sequence plays the provided equivalently constructed + // DisplayList onto the SkCanvas of the surface + // DisplayList => direct rendering + sk_sp test_surface = makeSurface(bg); + DisplayListBuilder builder(TestBounds); + dl_setup(builder); + dl_render(builder); + sk_sp display_list = builder.Build(); + SkRect dl_bounds = display_list->bounds(); +#ifdef DISPLAY_LIST_BOUNDS_ACCURACY_CHECKING + if (dl_bounds != ref_bounds) { + FML_LOG(ERROR) << "For " << info; + FML_LOG(ERROR) << "ref: " << ref_bounds.fLeft << ", " << ref_bounds.fTop + << " => " << ref_bounds.fRight << ", " + << ref_bounds.fBottom; + FML_LOG(ERROR) << "dl: " << dl_bounds.fLeft << ", " << dl_bounds.fTop + << " => " << dl_bounds.fRight << ", " + << dl_bounds.fBottom; + if (!dl_bounds.contains(ref_bounds)) { + FML_LOG(ERROR) << "DisplayList bounds are too small!"; + } + } +#endif // DISPLAY_LIST_BOUNDS_ACCURACY_CHECKING + // This sometimes triggers, but when it triggers and I examine + // the ref_bounds, they are always unnecessarily large and + // since the pixel OOB tests in the compare method do not + // trigger, we will trust the DL bounds. + // EXPECT_TRUE(dl_bounds.contains(ref_bounds)) << info; + display_list->RenderTo(test_surface->getCanvas()); + compareToReference(test_surface.get(), &ref_pixels, info + " (DL render)", + &dl_bounds, bg); + } + + // This test cannot work if the rendering is using shadows until + // we can access the Skia ShadowRec via public headers. + if (!UsingShadows) { + // This sequence renders SkCanvas calls to a DisplayList and then + // plays them back on SkCanvas to SkSurface + // SkCanvas calls => DisplayList => rendering + sk_sp test_surface = makeSurface(bg); + DisplayListCanvasRecorder dl_recorder(TestBounds); + SkPaint test_paint; + cv_setup(&dl_recorder, test_paint); + cv_render(&dl_recorder, test_paint); + dl_recorder.builder()->Build()->RenderTo(test_surface->getCanvas()); + compareToReference(test_surface.get(), &ref_pixels, + info + " (Sk->DL render)", nullptr, nullptr); + } + } + + static void checkPixels(SkPixmap* ref_pixels, + SkRect ref_bounds, + const std::string info, + const SkColor* bg) { + SkPMColor untouched = (bg) ? SkPreMultiplyColor(*bg) : 0; + int pixels_touched = 0; + int pixels_oob = 0; + for (int y = 0; y < TestHeight; y++) { + const uint32_t* ref_row = ref_pixels->addr32(0, y); + for (int x = 0; x < TestWidth; x++) { + if (ref_row[x] != untouched) { + pixels_touched++; + if (!ref_bounds.intersects(SkRect::MakeXYWH(x, y, 1, 1))) { + pixels_oob++; + } + } + } + } + ASSERT_EQ(pixels_oob, 0) << info; + ASSERT_GT(pixels_touched, 0) << info; + } + + static void compareToReference(SkSurface* test_surface, + SkPixmap* reference, + const std::string info, + SkRect* bounds, + const SkColor* bg) { + SkPMColor untouched = (bg) ? SkPreMultiplyColor(*bg) : 0; + SkPixmap test_pixels; + ASSERT_TRUE(test_surface->peekPixels(&test_pixels)) << info; + ASSERT_EQ(test_pixels.width(), TestWidth) << info; + ASSERT_EQ(test_pixels.height(), TestHeight) << info; + ASSERT_EQ(test_pixels.info().bytesPerPixel(), 4) << info; + + int pixels_different = 0; + int pixels_oob = 0; + int minX = TestWidth; + int minY = TestWidth; + int maxX = 0; + int maxY = 0; + for (int y = 0; y < TestHeight; y++) { + const uint32_t* ref_row = reference->addr32(0, y); + const uint32_t* test_row = test_pixels.addr32(0, y); + for (int x = 0; x < TestWidth; x++) { + if (bounds && test_row[x] != untouched) { + if (minX > x) + minX = x; + if (minY > y) + minY = y; + if (maxX < x) + maxX = x; + if (maxY < y) + maxY = y; + if (!bounds->intersects(SkRect::MakeXYWH(x, y, 1, 1))) { + pixels_oob++; + } + } + if (test_row[x] != ref_row[x]) { + pixels_different++; + } + } + } +#ifdef DISPLAY_LIST_BOUNDS_ACCURACY_CHECKING + if (bounds && *bounds != SkRect::MakeLTRB(minX, minY, maxX + 1, maxY + 1)) { + FML_LOG(ERROR) << "inaccurate bounds for " << info; + FML_LOG(ERROR) << "dl: " << bounds->fLeft << ", " << bounds->fTop + << " => " << bounds->fRight << ", " << bounds->fBottom; + FML_LOG(ERROR) << "pixels: " << minX << ", " << minY << " => " + << (maxX + 1) << ", " << (maxY + 1); + } +#endif // DISPLAY_LIST_BOUNDS_ACCURACY_CHECKING + ASSERT_EQ(pixels_oob, 0) << info; + ASSERT_EQ(pixels_different, 0) << info; + } + + static sk_sp makeSurface(const SkColor* bg) { + sk_sp surface = + SkSurface::MakeRasterN32Premul(TestWidth, TestHeight); + if (bg) { + surface->getCanvas()->drawColor(*bg); + } + return surface; + } + + static const sk_sp testImage; + static const sk_sp makeTestImage() { + sk_sp surface = + SkSurface::MakeRasterN32Premul(RenderWidth, RenderHeight); + SkCanvas* canvas = surface->getCanvas(); + SkPaint p0, p1; + p0.setStyle(SkPaint::kFill_Style); + p0.setColor(SK_ColorGREEN); + p1.setStyle(SkPaint::kFill_Style); + p1.setColor(SK_ColorBLUE); + // Some pixels need some transparency for DstIn testing + p1.setAlpha(128); + int cbdim = 5; + for (int y = 0; y < RenderHeight; y += cbdim) { + for (int x = 0; x < RenderWidth; x += cbdim) { + SkPaint& cellp = ((x + y) & 1) == 0 ? p0 : p1; + canvas->drawRect(SkRect::MakeXYWH(x, y, cbdim, cbdim), cellp); + } + } + return surface->makeImageSnapshot(); + } + + static sk_sp MakeTextBlob(std::string string, + SkScalar height = RenderHeight) { + SkFont font(SkTypeface::MakeDefault(), height); + return SkTextBlob::MakeFromText(string.c_str(), string.size(), font, + SkTextEncoding::kUTF8); + } +}; + +bool CanvasCompareTester::UsingShadows = false; +const sk_sp CanvasCompareTester::testImage = + CanvasCompareTester::makeTestImage(); + +TEST(DisplayListCanvas, DrawPaint) { + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawPaint(paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPaint(); + }); +} + +TEST(DisplayListCanvas, DrawColor) { + CanvasCompareTester::RenderNoAttributes( // + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawColor(SK_ColorMAGENTA); + }, + [=](DisplayListBuilder& builder) { // + builder.drawColor(SK_ColorMAGENTA, SkBlendMode::kSrcOver); + }); +} + +TEST(DisplayListCanvas, DrawLine) { + SkRect rect = RenderBounds; + SkPoint p1 = SkPoint::Make(rect.fLeft, rect.fTop); + SkPoint p2 = SkPoint::Make(rect.fRight, rect.fBottom); + + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawLine(p1, p2, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawLine(p1, p2); + }); +} + +TEST(DisplayListCanvas, DrawRect) { + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawRect(RenderBounds, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawRect(RenderBounds); + }); +} + +TEST(DisplayListCanvas, DrawOval) { + SkRect rect = RenderBounds.makeInset(0, 10); + + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawOval(rect, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawOval(rect); + }); +} + +TEST(DisplayListCanvas, DrawCircle) { + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawCircle(TestCenter, RenderRadius, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawCircle(TestCenter, RenderRadius); + }); +} + +TEST(DisplayListCanvas, DrawRRect) { + SkRRect rrect = + SkRRect::MakeRectXY(RenderBounds, RenderCornerRadius, RenderCornerRadius); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawRRect(rrect, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawRRect(rrect); + }); +} + +TEST(DisplayListCanvas, DrawDRRect) { + SkRRect outer = + SkRRect::MakeRectXY(RenderBounds, RenderCornerRadius, RenderCornerRadius); + SkRect innerBounds = RenderBounds.makeInset(30.0, 30.0); + SkRRect inner = + SkRRect::MakeRectXY(innerBounds, RenderCornerRadius, RenderCornerRadius); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawDRRect(outer, inner, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawDRRect(outer, inner); + }); +} + +TEST(DisplayListCanvas, DrawPath) { + SkPath path; + path.moveTo(RenderCenterX, RenderTop); + path.lineTo(RenderRight, RenderBottom); + path.lineTo(RenderLeft, RenderCenterY); + path.lineTo(RenderRight, RenderCenterY); + path.lineTo(RenderLeft, RenderBottom); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawPath(path, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPath(path); + }); +} + +TEST(DisplayListCanvas, DrawArc) { + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawArc(RenderBounds, 30, 270, false, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawArc(RenderBounds, 30, 270, false); + }); +} + +TEST(DisplayListCanvas, DrawArcCenter) { + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawArc(RenderBounds, 30, 270, true, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawArc(RenderBounds, 30, 270, true); + }); +} + +TEST(DisplayListCanvas, DrawPointsAsPoints) { + const SkScalar x0 = RenderLeft; + const SkScalar x1 = (RenderLeft + RenderCenterX) * 0.5; + const SkScalar x2 = RenderCenterX; + const SkScalar x3 = (RenderRight + RenderCenterX) * 0.5; + const SkScalar x4 = RenderRight; + + const SkScalar y0 = RenderTop; + const SkScalar y1 = (RenderTop + RenderCenterY) * 0.5; + const SkScalar y2 = RenderCenterY; + const SkScalar y3 = (RenderBottom + RenderCenterY) * 0.5; + const SkScalar y4 = RenderBottom; + + // clang-format off + const SkPoint points[] = { + {x0, y0}, {x1, y0}, {x2, y0}, {x3, y0}, {x4, y0}, + {x0, y1}, {x1, y1}, {x2, y1}, {x3, y1}, {x4, y1}, + {x0, y2}, {x1, y2}, {x2, y2}, {x3, y2}, {x4, y2}, + {x0, y3}, {x1, y3}, {x2, y3}, {x3, y3}, {x4, y3}, + {x0, y4}, {x1, y4}, {x2, y4}, {x3, y4}, {x4, y4}, + }; + // clang-format on + + const int count = sizeof(points) / sizeof(points[0]); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawPoints(SkCanvas::kPoints_PointMode, count, points, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPoints(SkCanvas::kPoints_PointMode, count, points); + }); +} + +TEST(DisplayListCanvas, DrawPointsAsLines) { + const SkScalar x0 = RenderLeft; + const SkScalar x1 = (RenderLeft + RenderCenterX) * 0.5; + const SkScalar x2 = RenderCenterX; + const SkScalar x3 = (RenderRight + RenderCenterX) * 0.5; + const SkScalar x4 = RenderRight; + + const SkScalar y0 = RenderTop; + const SkScalar y1 = (RenderTop + RenderCenterY) * 0.5; + const SkScalar y2 = RenderCenterY; + const SkScalar y3 = (RenderBottom + RenderCenterY) * 0.5; + const SkScalar y4 = RenderBottom; + + // clang-format off + const SkPoint points[] = { + // Diagonals + {x0, y0}, {x4, y4}, {x4, y0}, {x0, y4}, + // Inner box + {x1, y1}, {x3, y1}, + {x3, y1}, {x3, y3}, + {x3, y3}, {x1, y3}, + {x1, y3}, {x1, y1}, + // Middle crosshair + {x2, y1}, {x2, y3}, + {x1, y2}, {x3, y3}, + }; + // clang-format on + + const int count = sizeof(points) / sizeof(points[0]); + ASSERT_TRUE((count & 1) == 0); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawPoints(SkCanvas::kLines_PointMode, count, points, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPoints(SkCanvas::kLines_PointMode, count, points); + }); +} + +TEST(DisplayListCanvas, DrawPointsAsPolygon) { + const SkPoint points[] = { + SkPoint::Make(RenderLeft, RenderTop), + SkPoint::Make(RenderRight, RenderBottom), + SkPoint::Make(RenderRight, RenderTop), + SkPoint::Make(RenderLeft, RenderBottom), + SkPoint::Make(RenderLeft, RenderTop), + }; + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawPoints(SkCanvas::kPolygon_PointMode, 4, points, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPoints(SkCanvas::kPolygon_PointMode, 4, points); + }); +} + +TEST(DisplayListCanvas, DrawVerticesWithColors) { + const SkPoint pts[3] = { + SkPoint::Make(RenderCenterX, RenderTop), + SkPoint::Make(RenderLeft, RenderBottom), + SkPoint::Make(RenderRight, RenderBottom), + }; + const SkColor colors[3] = {SK_ColorRED, SK_ColorBLUE, SK_ColorGREEN}; + const sk_sp vertices = SkVertices::MakeCopy( + SkVertices::kTriangles_VertexMode, 3, pts, nullptr, colors); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawVertices(vertices.get(), SkBlendMode::kSrcOver, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawVertices(vertices, SkBlendMode::kSrcOver); + }); + ASSERT_TRUE(vertices->unique()); +} + +TEST(DisplayListCanvas, DrawVerticesWithImage) { + const SkPoint pts[3] = { + SkPoint::Make(RenderCenterX, RenderTop), + SkPoint::Make(RenderLeft, RenderBottom), + SkPoint::Make(RenderRight, RenderBottom), + }; + const SkPoint tex[3] = { + SkPoint::Make(RenderWidth / 2.0, 0), + SkPoint::Make(0, RenderHeight), + SkPoint::Make(RenderWidth, RenderHeight), + }; + const sk_sp vertices = SkVertices::MakeCopy( + SkVertices::kTriangles_VertexMode, 3, pts, tex, nullptr); + const sk_sp shader = CanvasCompareTester::testImage->makeShader( + SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + paint.setShader(shader); + canvas->drawVertices(vertices.get(), SkBlendMode::kSrcOver, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.setShader(shader); + builder.drawVertices(vertices, SkBlendMode::kSrcOver); + }); + ASSERT_TRUE(vertices->unique()); + ASSERT_TRUE(shader->unique()); +} + +TEST(DisplayListCanvas, DrawImageNearest) { + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawImage(CanvasCompareTester::testImage, RenderLeft, RenderTop, + DisplayList::NearestSampling, &paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImage(CanvasCompareTester::testImage, + SkPoint::Make(RenderLeft, RenderTop), + DisplayList::NearestSampling); + }); +} + +TEST(DisplayListCanvas, DrawImageLinear) { + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawImage(CanvasCompareTester::testImage, RenderLeft, RenderTop, + DisplayList::LinearSampling, &paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImage(CanvasCompareTester::testImage, + SkPoint::Make(RenderLeft, RenderTop), + DisplayList::LinearSampling); + }); +} + +TEST(DisplayListCanvas, DrawImageRectNearest) { + SkRect src = SkRect::MakeIWH(RenderWidth, RenderHeight).makeInset(5, 5); + SkRect dst = RenderBounds.makeInset(15.5, 10.5); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawImageRect(CanvasCompareTester::testImage, src, dst, + DisplayList::NearestSampling, &paint, + SkCanvas::kFast_SrcRectConstraint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImageRect(CanvasCompareTester::testImage, src, dst, + DisplayList::NearestSampling); + }); +} + +TEST(DisplayListCanvas, DrawImageRectLinear) { + SkRect src = SkRect::MakeIWH(RenderWidth, RenderHeight).makeInset(5, 5); + SkRect dst = RenderBounds.makeInset(15.5, 10.5); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawImageRect(CanvasCompareTester::testImage, src, dst, + DisplayList::LinearSampling, &paint, + SkCanvas::kFast_SrcRectConstraint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImageRect(CanvasCompareTester::testImage, src, dst, + DisplayList::LinearSampling); + }); +} + +TEST(DisplayListCanvas, DrawImageNineNearest) { + SkIRect src = SkIRect::MakeWH(RenderWidth, RenderHeight).makeInset(5, 5); + SkRect dst = RenderBounds.makeInset(15.5, 10.5); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawImageNine(CanvasCompareTester::testImage.get(), src, dst, + SkFilterMode::kNearest, &paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImageNine(CanvasCompareTester::testImage, src, dst, + SkFilterMode::kNearest); + }); +} + +TEST(DisplayListCanvas, DrawImageNineLinear) { + SkIRect src = SkIRect::MakeWH(RenderWidth, RenderHeight).makeInset(5, 5); + SkRect dst = RenderBounds.makeInset(15.5, 10.5); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawImageNine(CanvasCompareTester::testImage.get(), src, dst, + SkFilterMode::kLinear, &paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImageNine(CanvasCompareTester::testImage, src, dst, + SkFilterMode::kLinear); + }); +} + +TEST(DisplayListCanvas, DrawImageLatticeNearest) { + const SkRect dst = RenderBounds.makeInset(15.5, 10.5); + const int divX[] = { + (RenderLeft + RenderCenterX) / 2, + RenderCenterX, + (RenderRight + RenderCenterX) / 2, + }; + const int divY[] = { + (RenderTop + RenderCenterY) / 2, + RenderCenterY, + (RenderBottom + RenderCenterY) / 2, + }; + SkCanvas::Lattice lattice = { + divX, divY, nullptr, 3, 3, nullptr, nullptr, + }; + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawImageLattice(CanvasCompareTester::testImage.get(), lattice, + dst, SkFilterMode::kNearest, &paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImageLattice(CanvasCompareTester::testImage, lattice, // + dst, SkFilterMode::kNearest, true); + }); +} + +TEST(DisplayListCanvas, DrawImageLatticeLinear) { + const SkRect dst = RenderBounds.makeInset(15.5, 10.5); + const int divX[] = { + (RenderLeft + RenderCenterX) / 2, + RenderCenterX, + (RenderRight + RenderCenterX) / 2, + }; + const int divY[] = { + (RenderTop + RenderCenterY) / 2, + RenderCenterY, + (RenderBottom + RenderCenterY) / 2, + }; + SkCanvas::Lattice lattice = { + divX, divY, nullptr, 3, 3, nullptr, nullptr, + }; + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawImageLattice(CanvasCompareTester::testImage.get(), lattice, + dst, SkFilterMode::kLinear, &paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImageLattice(CanvasCompareTester::testImage, lattice, // + dst, SkFilterMode::kLinear, true); + }); +} + +TEST(DisplayListCanvas, DrawAtlasNearest) { + const SkRSXform xform[] = { + {0.5, 0, RenderLeft, RenderRight}, + {0, 0.5, RenderCenterX, RenderCenterY}, + }; + const SkRect tex[] = { + {0, 0, RenderWidth * 0.5, RenderHeight * 0.5}, + {RenderWidth * 0.5, RenderHeight * 0.5, RenderWidth, RenderHeight}, + }; + const SkColor colors[] = { + SK_ColorBLUE, + SK_ColorGREEN, + }; + const sk_sp image = CanvasCompareTester::testImage; + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { + canvas->drawAtlas(image.get(), xform, tex, colors, 2, + SkBlendMode::kSrcOver, DisplayList::NearestSampling, + nullptr, &paint); + }, + [=](DisplayListBuilder& builder) { + builder.drawAtlas(image, xform, tex, colors, 2, // + SkBlendMode::kSrcOver, DisplayList::NearestSampling, + nullptr); + }); +} + +TEST(DisplayListCanvas, DrawAtlasLinear) { + const SkRSXform xform[] = { + {0.5, 0, RenderLeft, RenderRight}, + {0, 0.5, RenderCenterX, RenderCenterY}, + }; + const SkRect tex[] = { + {0, 0, RenderWidth * 0.5, RenderHeight * 0.5}, + {RenderWidth * 0.5, RenderHeight * 0.5, RenderWidth, RenderHeight}, + }; + const SkColor colors[] = { + SK_ColorBLUE, + SK_ColorGREEN, + }; + const sk_sp image = CanvasCompareTester::testImage; + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { + canvas->drawAtlas(image.get(), xform, tex, colors, 2, // + SkBlendMode::kSrcOver, DisplayList::LinearSampling, + nullptr, &paint); + }, + [=](DisplayListBuilder& builder) { + builder.drawAtlas(image, xform, tex, colors, 2, // + SkBlendMode::kSrcOver, DisplayList::LinearSampling, + nullptr); + }); +} + +TEST(DisplayListCanvas, DrawPicture) { + SkPictureRecorder recorder; + SkCanvas* cv = recorder.beginRecording(RenderBounds); + SkPaint p; + p.setStyle(SkPaint::kFill_Style); + p.setColor(SK_ColorBLUE); + cv->drawOval(RenderBounds, p); + sk_sp picture = recorder.finishRecordingAsPicture(); + CanvasCompareTester::RenderNoAttributes( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawPicture(picture, nullptr, nullptr); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPicture(picture, nullptr, false); + }); +} + +TEST(DisplayListCanvas, DrawPictureWithMatrix) { + SkPictureRecorder recorder; + SkCanvas* cv = recorder.beginRecording(RenderBounds); + SkPaint p; + p.setStyle(SkPaint::kFill_Style); + p.setColor(SK_ColorBLUE); + cv->drawOval(RenderBounds, p); + sk_sp picture = recorder.finishRecordingAsPicture(); + SkMatrix matrix = SkMatrix::Scale(0.95, 0.95); + CanvasCompareTester::RenderNoAttributes( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawPicture(picture, &matrix, nullptr); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPicture(picture, &matrix, false); + }); +} + +TEST(DisplayListCanvas, DrawPictureWithPaint) { + SkPictureRecorder recorder; + SkCanvas* cv = recorder.beginRecording(RenderBounds); + SkPaint p; + p.setStyle(SkPaint::kFill_Style); + p.setColor(SK_ColorBLUE); + cv->drawOval(RenderBounds, p); + sk_sp picture = recorder.finishRecordingAsPicture(); + CanvasCompareTester::RenderAll( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawPicture(picture, nullptr, &paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPicture(picture, nullptr, true); + }); +} + +TEST(DisplayListCanvas, DrawDisplayList) { + DisplayListBuilder builder; + builder.setDrawStyle(SkPaint::kFill_Style); + builder.setColor(SK_ColorBLUE); + builder.drawOval(RenderBounds); + sk_sp display_list = builder.Build(); + CanvasCompareTester::RenderNoAttributes( + [=](SkCanvas* canvas, SkPaint& paint) { // + display_list->RenderTo(canvas); + }, + [=](DisplayListBuilder& builder) { // + builder.drawDisplayList(display_list); + }); +} + +TEST(DisplayListCanvas, DrawTextBlob) { + // TODO(https://github.com/flutter/flutter/issues/82202): Remove once the + // performance overlay can use Fuchsia's font manager instead of the empty + // default. +#if defined(OS_FUCHSIA) + GTEST_SKIP() << "Rendering comparisons require a valid default font manager"; +#endif // OS_FUCHSIA + sk_sp blob = CanvasCompareTester::MakeTextBlob("Test Blob"); + CanvasCompareTester::RenderNoAttributes( + [=](SkCanvas* canvas, SkPaint& paint) { // + canvas->drawTextBlob(blob, RenderLeft, RenderBottom, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawTextBlob(blob, RenderLeft, RenderBottom); + }); +} + +TEST(DisplayListCanvas, DrawShadow) { + CanvasCompareTester::UsingShadows = true; + SkPath path; + path.moveTo(RenderCenterX, RenderTop); + path.lineTo(RenderRight, RenderBottom); + path.lineTo(RenderLeft, RenderCenterY); + path.lineTo(RenderRight, RenderCenterY); + path.lineTo(RenderLeft, RenderBottom); + path.close(); + const SkColor color = SK_ColorDKGRAY; + const SkScalar elevation = 10; + + CanvasCompareTester::RenderNoAttributes( + [=](SkCanvas* canvas, SkPaint& paint) { // + PhysicalShapeLayer::DrawShadow(canvas, path, color, elevation, false, + 1.0); + }, + [=](DisplayListBuilder& builder) { // + builder.drawShadow(path, color, elevation, false); + }); + CanvasCompareTester::UsingShadows = false; +} + +TEST(DisplayListCanvas, DrawOccludingShadow) { + CanvasCompareTester::UsingShadows = true; + SkPath path; + path.moveTo(RenderCenterX, RenderTop); + path.lineTo(RenderRight, RenderBottom); + path.lineTo(RenderLeft, RenderCenterY); + path.lineTo(RenderRight, RenderCenterY); + path.lineTo(RenderLeft, RenderBottom); + path.close(); + const SkColor color = SK_ColorDKGRAY; + const SkScalar elevation = 10; + + CanvasCompareTester::RenderNoAttributes( + [=](SkCanvas* canvas, SkPaint& paint) { // + PhysicalShapeLayer::DrawShadow(canvas, path, color, elevation, true, + 1.0); + }, + [=](DisplayListBuilder& builder) { // + builder.drawShadow(path, color, elevation, true); + }); + CanvasCompareTester::UsingShadows = false; +} + +} // namespace testing +} // namespace flutter diff --git a/flow/display_list_unittests.cc b/flow/display_list_unittests.cc new file mode 100644 index 0000000000000..eae1815eb047f --- /dev/null +++ b/flow/display_list_unittests.cc @@ -0,0 +1,830 @@ +// Copyright 2013 The Flutter 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 "flutter/flow/display_list_canvas.h" + +#include "third_party/skia/include/core/SkColor.h" +#include "third_party/skia/include/core/SkImageInfo.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkPicture.h" +#include "third_party/skia/include/core/SkPictureRecorder.h" +#include "third_party/skia/include/core/SkRRect.h" +#include "third_party/skia/include/core/SkRSXform.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/core/SkTextBlob.h" +#include "third_party/skia/include/core/SkVertices.h" +#include "third_party/skia/include/effects/SkGradientShader.h" +#include "third_party/skia/include/effects/SkImageFilters.h" + +#include + +#include "gtest/gtest.h" + +namespace flutter { +namespace testing { + +constexpr SkPoint end_points[] = { + {0, 0}, + {100, 100}, +}; +constexpr SkColor colors[] = { + SK_ColorGREEN, + SK_ColorYELLOW, + SK_ColorBLUE, +}; +constexpr float stops[] = { + 0.0, + 0.5, + 1.0, +}; +constexpr float rotate_color_matrix[20] = { + 0, 1, 0, 0, 0, // + 0, 0, 1, 0, 0, // + 1, 0, 0, 0, 0, // + 0, 0, 0, 1, 0, // +}; + +constexpr SkPoint TestPoints[] = { + {10, 10}, + {20, 20}, + {10, 20}, + {20, 10}, +}; +#define TestPointCount sizeof(TestPoints) / (sizeof(TestPoints[0])) + +static const sk_sp TestShader1 = + SkGradientShader::MakeLinear(end_points, + colors, + stops, + 3, + SkTileMode::kMirror, + 0, + nullptr); +// TestShader2 is identical to TestShader1 and points out that we cannot +// perform a deep compare over our various sk_sp objects because the +// DisplayLists constructed with the two do not compare == below. +static const sk_sp TestShader2 = + SkGradientShader::MakeLinear(end_points, + colors, + stops, + 3, + SkTileMode::kMirror, + 0, + nullptr); +static const sk_sp TestShader3 = + SkGradientShader::MakeLinear(end_points, + colors, + stops, + 3, + SkTileMode::kDecal, + 0, + nullptr); +static const sk_sp TestImageFilter = + SkImageFilters::Blur(5.0, 5.0, SkTileMode::kDecal, nullptr, nullptr); +static const sk_sp TestColorFilter = + SkColorFilters::Matrix(rotate_color_matrix); +static const sk_sp TestMaskFilter = + SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5.0); +constexpr SkRect TestBounds = SkRect::MakeLTRB(10, 10, 50, 60); +static const SkRRect TestRRect = SkRRect::MakeRectXY(TestBounds, 5, 5); +static const SkRRect TestRRectRect = SkRRect::MakeRect(TestBounds); +static const SkRRect TestInnerRRect = + SkRRect::MakeRectXY(TestBounds.makeInset(5, 5), 2, 2); +static const SkPath TestPathRect = SkPath::Rect(TestBounds); +static const SkPath TestPathOval = SkPath::Oval(TestBounds); +static const SkPath TestPath1 = + SkPath::Polygon({{0, 0}, {10, 10}, {10, 0}, {0, 10}}, true); +static const SkPath TestPath2 = + SkPath::Polygon({{0, 0}, {10, 10}, {0, 10}, {10, 0}}, true); +static const SkPath TestPath3 = + SkPath::Polygon({{0, 0}, {10, 10}, {10, 0}, {0, 10}}, false); +static const SkMatrix TestMatrix1 = SkMatrix::Scale(2, 2); +static const SkMatrix TestMatrix2 = SkMatrix::RotateDeg(45); + +static sk_sp MakeTestImage(int w, int h, int checker_size) { + sk_sp surface = SkSurface::MakeRasterN32Premul(w, h); + SkCanvas* canvas = surface->getCanvas(); + SkPaint p0, p1; + p0.setStyle(SkPaint::kFill_Style); + p0.setColor(SK_ColorGREEN); + p1.setStyle(SkPaint::kFill_Style); + p1.setColor(SK_ColorBLUE); + p1.setAlpha(128); + for (int y = 0; y < w; y += checker_size) { + for (int x = 0; x < h; x += checker_size) { + SkPaint& cellp = ((x + y) & 1) == 0 ? p0 : p1; + canvas->drawRect(SkRect::MakeXYWH(x, y, checker_size, checker_size), + cellp); + } + } + return surface->makeImageSnapshot(); +} +static sk_sp TestImage1 = MakeTestImage(40, 40, 5); +static sk_sp TestImage2 = MakeTestImage(50, 50, 5); + +static sk_sp TestVertices1 = + SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, + 3, + TestPoints, + nullptr, + colors); +static sk_sp TestVertices2 = + SkVertices::MakeCopy(SkVertices::kTriangleFan_VertexMode, + 3, + TestPoints, + nullptr, + colors); + +static constexpr int TestDivs1[] = {10, 20, 30}; +static constexpr int TestDivs2[] = {15, 20, 25}; +static constexpr int TestDivs3[] = {15, 25}; +static constexpr SkCanvas::Lattice::RectType TestRTypes[] = { + SkCanvas::Lattice::RectType::kDefault, + SkCanvas::Lattice::RectType::kTransparent, + SkCanvas::Lattice::RectType::kFixedColor, + SkCanvas::Lattice::RectType::kDefault, + SkCanvas::Lattice::RectType::kTransparent, + SkCanvas::Lattice::RectType::kFixedColor, + SkCanvas::Lattice::RectType::kDefault, + SkCanvas::Lattice::RectType::kTransparent, + SkCanvas::Lattice::RectType::kFixedColor, +}; +static constexpr SkColor TestLatticeColors[] = { + SK_ColorBLUE, SK_ColorGREEN, SK_ColorYELLOW, + SK_ColorBLUE, SK_ColorGREEN, SK_ColorYELLOW, + SK_ColorBLUE, SK_ColorGREEN, SK_ColorYELLOW, +}; +static constexpr SkIRect TestLatticeSrcRect = {1, 1, 39, 39}; + +static sk_sp MakeTestPicture(int w, int h, SkColor color) { + SkPictureRecorder recorder; + SkCanvas* cv = recorder.beginRecording(TestBounds); + SkPaint paint; + paint.setColor(color); + paint.setStyle(SkPaint::kFill_Style); + cv->drawRect(SkRect::MakeWH(w, h), paint); + return recorder.finishRecordingAsPicture(); +} +static sk_sp TestPicture1 = MakeTestPicture(20, 20, SK_ColorGREEN); +static sk_sp TestPicture2 = MakeTestPicture(25, 25, SK_ColorBLUE); + +static sk_sp MakeTestDisplayList(int w, int h, SkColor color) { + DisplayListBuilder builder; + builder.setColor(color); + builder.drawRect(SkRect::MakeWH(w, h)); + return builder.Build(); +} +static sk_sp TestDisplayList1 = + MakeTestDisplayList(20, 20, SK_ColorGREEN); +static sk_sp TestDisplayList2 = + MakeTestDisplayList(25, 25, SK_ColorBLUE); + +static sk_sp MakeTextBlob(std::string string) { + return SkTextBlob::MakeFromText(string.c_str(), string.size(), SkFont(), + SkTextEncoding::kUTF8); +} +static sk_sp TestBlob1 = MakeTextBlob("TestBlob1"); +static sk_sp TestBlob2 = MakeTextBlob("TestBlob2"); + +// --------------- +// Test Suite data +// --------------- + +typedef const std::function DlInvoker; + +struct DisplayListInvocation { + int op_count; + size_t byte_count; + + // in some cases, running the sequence through an SkCanvas will result + // in fewer ops/bytes. Attribute invocations are recorded in an SkPaint + // and not forwarded on, and SkCanvas culls unused save/restore/transforms. + int sk_op_count; + size_t sk_byte_count; + + DlInvoker invoker; + + bool sk_version_matches() { + return (op_count == sk_op_count && byte_count == sk_byte_count); + } + + sk_sp Build() { + DisplayListBuilder builder; + invoker(builder); + return builder.Build(); + } +}; + +struct DisplayListInvocationGroup { + std::string op_name; + std::vector variants; +}; + +std::vector allGroups = { + { "SetAA", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setAA(false);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setAA(true);}}, + } + }, + { "SetDither", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setDither(false);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setDither(true);}}, + } + }, + { "SetInvertColors", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setInvertColors(false);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setInvertColors(true);}}, + } + }, + { "SetStrokeCap", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setCaps(SkPaint::kButt_Cap);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setCaps(SkPaint::kRound_Cap);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setCaps(SkPaint::kSquare_Cap);}}, + } + }, + { "SetStrokeJoin", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setJoins(SkPaint::kBevel_Join);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setJoins(SkPaint::kRound_Join);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setJoins(SkPaint::kMiter_Join);}}, + } + }, + { "SetDrawStyle", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setDrawStyle(SkPaint::kFill_Style);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setDrawStyle(SkPaint::kStroke_Style);}}, + } + }, + { "SetStrokeWidth", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeWidth(0.0);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeWidth(5.0);}}, + } + }, + { "SetMiterLimit", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setMiterLimit(0.0);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setMiterLimit(5.0);}}, + } + }, + { "SetColor", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setColor(SK_ColorGREEN);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setColor(SK_ColorBLUE);}}, + } + }, + { "SetBlendMode", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setBlendMode(SkBlendMode::kSrcIn);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setBlendMode(SkBlendMode::kDstIn);}}, + } + }, + { "SetFilterQuality", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setFilterQuality(kNone_SkFilterQuality);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setFilterQuality(kLow_SkFilterQuality);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setFilterQuality(kMedium_SkFilterQuality);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setFilterQuality(kHigh_SkFilterQuality);}}, + } + }, + { "SetShader", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setShader(nullptr);}}, + {1, 16, 0, 0, [](DisplayListBuilder& b) {b.setShader(TestShader1);}}, + {1, 16, 0, 0, [](DisplayListBuilder& b) {b.setShader(TestShader2);}}, + {1, 16, 0, 0, [](DisplayListBuilder& b) {b.setShader(TestShader3);}}, + } + }, + { "SetImageFilter", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(nullptr);}}, + {1, 16, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(TestImageFilter);}}, + } + }, + { "SetColorFilter", { + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setColorFilter(nullptr);}}, + {1, 16, 0, 0, [](DisplayListBuilder& b) {b.setColorFilter(TestColorFilter);}}, + } + }, + { "SetMaskFilter", { + {1, 16, 0, 0, [](DisplayListBuilder& b) {b.setMaskFilter(nullptr);}}, + {1, 16, 0, 0, [](DisplayListBuilder& b) {b.setMaskFilter(TestMaskFilter);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setMaskBlurFilter(kNormal_SkBlurStyle, 3.0);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setMaskBlurFilter(kNormal_SkBlurStyle, 5.0);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setMaskBlurFilter(kSolid_SkBlurStyle, 3.0);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setMaskBlurFilter(kInner_SkBlurStyle, 3.0);}}, + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.setMaskBlurFilter(kOuter_SkBlurStyle, 3.0);}}, + } + }, + { "Save(Layer)+Restore", { + // cv.save/restore are ignored if there are no draw calls between them + {2, 16, 0, 0, [](DisplayListBuilder& b) {b.save(); b.restore();}}, + {2, 16, 2, 16, [](DisplayListBuilder& b) {b.saveLayer(nullptr, false); b.restore(); }}, + {2, 16, 2, 16, [](DisplayListBuilder& b) {b.saveLayer(nullptr, true); b.restore(); }}, + {2, 32, 2, 32, [](DisplayListBuilder& b) {b.saveLayer(&TestBounds, false); b.restore(); }}, + {2, 32, 2, 32, [](DisplayListBuilder& b) {b.saveLayer(&TestBounds, true); b.restore(); }}, + } + }, + { "Translate", { + // cv.translate(0, 0) is ignored + {1, 16, 0, 0, [](DisplayListBuilder& b) {b.translate(0, 0);}}, + {1, 16, 1, 16, [](DisplayListBuilder& b) {b.translate(10, 10);}}, + {1, 16, 1, 16, [](DisplayListBuilder& b) {b.translate(10, 15);}}, + {1, 16, 1, 16, [](DisplayListBuilder& b) {b.translate(15, 10);}}, + } + }, + { "Scale", { + // cv.scale(1, 1) is ignored + {1, 16, 0, 0, [](DisplayListBuilder& b) {b.scale(1, 1);}}, + {1, 16, 1, 16, [](DisplayListBuilder& b) {b.scale(2, 2);}}, + {1, 16, 1, 16, [](DisplayListBuilder& b) {b.scale(2, 3);}}, + {1, 16, 1, 16, [](DisplayListBuilder& b) {b.scale(3, 2);}}, + } + }, + { "Rotate", { + // cv.rotate(0) is ignored, otherwise expressed as concat(rotmatrix) + {1, 8, 0, 0, [](DisplayListBuilder& b) {b.rotate(0);}}, + {1, 8, 1, 32, [](DisplayListBuilder& b) {b.rotate(30);}}, + {1, 8, 1, 32, [](DisplayListBuilder& b) {b.rotate(45);}}, + } + }, + { "Skew", { + // cv.skew(0, 0) is ignored, otherwise expressed as concat(skewmatrix) + {1, 16, 0, 0, [](DisplayListBuilder& b) {b.skew(0, 0);}}, + {1, 16, 1, 32, [](DisplayListBuilder& b) {b.skew(0.1, 0.1);}}, + {1, 16, 1, 32, [](DisplayListBuilder& b) {b.skew(0.1, 0.2);}}, + {1, 16, 1, 32, [](DisplayListBuilder& b) {b.skew(0.2, 0.1);}}, + } + }, + { "Transform2x3", { + // cv.transform(identity) is ignored + {1, 32, 0, 0, [](DisplayListBuilder& b) {b.transform2x3(1, 0, 0, 0, 1, 0);}}, + {1, 32, 1, 32, [](DisplayListBuilder& b) {b.transform2x3(0, 1, 12, 1, 0, 33);}}, + } + }, + { "Transform3x3", { + // cv.transform(identity) is ignored + {1, 40, 0, 0, [](DisplayListBuilder& b) {b.transform3x3(1, 0, 0, 0, 1, 0, 0, 0, 1);}}, + {1, 40, 1, 40, [](DisplayListBuilder& b) {b.transform3x3(0, 1, 12, 1, 0, 33, 0, 0, 12);}}, + } + }, + { "ClipRect", { + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipRect(TestBounds, true, SkClipOp::kIntersect);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipRect(TestBounds.makeOffset(1, 1), + true, SkClipOp::kIntersect);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipRect(TestBounds, false, SkClipOp::kIntersect);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipRect(TestBounds, true, SkClipOp::kDifference);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipRect(TestBounds, false, SkClipOp::kDifference);}}, + } + }, + { "ClipRRect", { + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.clipRRect(TestRRect, true, SkClipOp::kIntersect);}}, + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.clipRRect(TestRRect.makeOffset(1, 1), + true, SkClipOp::kIntersect);}}, + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.clipRRect(TestRRect, false, SkClipOp::kIntersect);}}, + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.clipRRect(TestRRect, true, SkClipOp::kDifference);}}, + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.clipRRect(TestRRect, false, SkClipOp::kDifference);}}, + } + }, + { "ClipPath", { + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipPath(TestPath1, true, SkClipOp::kIntersect);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipPath(TestPath2, true, SkClipOp::kIntersect);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipPath(TestPath3, true, SkClipOp::kIntersect);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipPath(TestPath1, false, SkClipOp::kIntersect);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipPath(TestPath1, true, SkClipOp::kDifference);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipPath(TestPath1, false, SkClipOp::kDifference);}}, + // clipPath(rect) becomes clipRect + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.clipPath(TestPathRect, true, SkClipOp::kIntersect);}}, + // clipPath(oval) becomes clipRRect + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.clipPath(TestPathOval, true, SkClipOp::kIntersect);}}, + } + }, + { "DrawPaint", { + {1, 8, 1, 8, [](DisplayListBuilder& b) {b.drawPaint();}}, + } + }, + { "DrawColor", { + // cv.drawColor becomes cv.drawPaint(paint) + {1, 16, 3, 24, [](DisplayListBuilder& b) {b.drawColor(SK_ColorBLUE, SkBlendMode::kSrcIn);}}, + {1, 16, 3, 24, [](DisplayListBuilder& b) {b.drawColor(SK_ColorBLUE, SkBlendMode::kDstIn);}}, + {1, 16, 3, 24, [](DisplayListBuilder& b) {b.drawColor(SK_ColorCYAN, SkBlendMode::kSrcIn);}}, + } + }, + { "DrawLine", { + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawLine({0, 0}, {10, 10});}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawLine({0, 1}, {10, 10});}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawLine({0, 0}, {20, 10});}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawLine({0, 0}, {10, 20});}}, + } + }, + { "DrawRect", { + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawRect({0, 0, 10, 10});}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawRect({0, 1, 10, 10});}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawRect({0, 0, 20, 10});}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawRect({0, 0, 10, 20});}}, + } + }, + { "DrawOval", { + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawOval({0, 0, 10, 10});}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawOval({0, 1, 10, 10});}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawOval({0, 0, 20, 10});}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawOval({0, 0, 10, 20});}}, + } + }, + { "DrawCircle", { + // cv.drawCircle becomes cv.drawOval + {1, 16, 1, 24, [](DisplayListBuilder& b) {b.drawCircle({0, 0}, 10);}}, + {1, 16, 1, 24, [](DisplayListBuilder& b) {b.drawCircle({0, 5}, 10);}}, + {1, 16, 1, 24, [](DisplayListBuilder& b) {b.drawCircle({0, 0}, 20);}}, + } + }, + { "DrawRRect", { + {1, 56, 1, 56, [](DisplayListBuilder& b) {b.drawRRect(TestRRect);}}, + {1, 56, 1, 56, [](DisplayListBuilder& b) {b.drawRRect(TestRRect.makeOffset(5, 5));}}, + } + }, + { "DrawDRRect", { + {1, 112, 1, 112, [](DisplayListBuilder& b) {b.drawDRRect(TestRRect, TestInnerRRect);}}, + {1, 112, 1, 112, [](DisplayListBuilder& b) {b.drawDRRect(TestRRect.makeOffset(5, 5), + TestInnerRRect.makeOffset(4, 4));}}, + } + }, + { "DrawPath", { + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawPath(TestPath1);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawPath(TestPath2);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawPath(TestPath3);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawPath(TestPathRect);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawPath(TestPathOval);}}, + } + }, + { "DrawArc", { + {1, 32, 1, 32, [](DisplayListBuilder& b) {b.drawArc(TestBounds, 45, 270, false);}}, + {1, 32, 1, 32, [](DisplayListBuilder& b) {b.drawArc(TestBounds.makeOffset(1, 1), + 45, 270, false);}}, + {1, 32, 1, 32, [](DisplayListBuilder& b) {b.drawArc(TestBounds, 30, 270, false);}}, + {1, 32, 1, 32, [](DisplayListBuilder& b) {b.drawArc(TestBounds, 45, 260, false);}}, + {1, 32, 1, 32, [](DisplayListBuilder& b) {b.drawArc(TestBounds, 45, 270, true);}}, + } + }, + { "DrawPoints", { + {1, 8 + TestPointCount * 8, 1, 8 + TestPointCount * 8, + [](DisplayListBuilder& b) {b.drawPoints(SkCanvas::kPoints_PointMode, + TestPointCount, + TestPoints);}}, + {1, 8 + (TestPointCount - 1) * 8, 1, 8 + (TestPointCount - 1) * 8, + [](DisplayListBuilder& b) {b.drawPoints(SkCanvas::kPoints_PointMode, + TestPointCount - 1, + TestPoints);}}, + {1, 8 + TestPointCount * 8, 1, 8 + TestPointCount * 8, + [](DisplayListBuilder& b) {b.drawPoints(SkCanvas::kLines_PointMode, + TestPointCount, + TestPoints);}}, + {1, 8 + TestPointCount * 8, 1, 8 + TestPointCount * 8, + [](DisplayListBuilder& b) {b.drawPoints(SkCanvas::kPolygon_PointMode, + TestPointCount, + TestPoints);}}, + } + }, + { "DrawVertices", { + {1, 16, 1, 16, [](DisplayListBuilder& b) {b.drawVertices(TestVertices1, SkBlendMode::kSrcIn);}}, + {1, 16, 1, 16, [](DisplayListBuilder& b) {b.drawVertices(TestVertices1, SkBlendMode::kDstIn);}}, + {1, 16, 1, 16, [](DisplayListBuilder& b) {b.drawVertices(TestVertices2, SkBlendMode::kSrcIn);}}, + } + }, + { "DrawImage", { + {1, 40, 1, 40, [](DisplayListBuilder& b) {b.drawImage(TestImage1, {10, 10}, DisplayList::NearestSampling);}}, + {1, 40, 1, 40, [](DisplayListBuilder& b) {b.drawImage(TestImage1, {20, 10}, DisplayList::NearestSampling);}}, + {1, 40, 1, 40, [](DisplayListBuilder& b) {b.drawImage(TestImage1, {10, 20}, DisplayList::NearestSampling);}}, + {1, 40, 1, 40, [](DisplayListBuilder& b) {b.drawImage(TestImage1, {10, 10}, DisplayList::LinearSampling);}}, + {1, 40, 1, 40, [](DisplayListBuilder& b) {b.drawImage(TestImage2, {10, 10}, DisplayList::NearestSampling);}}, + } + }, + { "DrawImageRect", { + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80}, + DisplayList::NearestSampling);}}, + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80}, + DisplayList::NearestSampling, + SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint);}}, + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.drawImageRect(TestImage1, {10, 10, 25, 20}, {10, 10, 80, 80}, + DisplayList::NearestSampling);}}, + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 85, 80}, + DisplayList::NearestSampling);}}, + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80}, + DisplayList::LinearSampling);}}, + {1, 64, 1, 64, [](DisplayListBuilder& b) {b.drawImageRect(TestImage2, {10, 10, 15, 15}, {10, 10, 80, 80}, + DisplayList::NearestSampling);}}, + } + }, + { "DrawImageNine", { + // SkVanvas::drawImageNine is immediately converted to drawImageLattice + {1, 48, 1, 80, [](DisplayListBuilder& b) {b.drawImageNine(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80}, + SkFilterMode::kNearest);}}, + {1, 48, 1, 80, [](DisplayListBuilder& b) {b.drawImageNine(TestImage1, {10, 10, 25, 20}, {10, 10, 80, 80}, + SkFilterMode::kNearest);}}, + {1, 48, 1, 80, [](DisplayListBuilder& b) {b.drawImageNine(TestImage1, {10, 10, 20, 20}, {10, 10, 85, 80}, + SkFilterMode::kNearest);}}, + {1, 48, 1, 80, [](DisplayListBuilder& b) {b.drawImageNine(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80}, + SkFilterMode::kLinear);}}, + {1, 48, 1, 80, [](DisplayListBuilder& b) {b.drawImageNine(TestImage2, {10, 10, 15, 15}, {10, 10, 80, 80}, + SkFilterMode::kNearest);}}, + } + }, + { "DrawImageLattice", { + // Lattice: + // const int* fXDivs; //!< x-axis values dividing bitmap + // const int* fYDivs; //!< y-axis values dividing bitmap + // const RectType* fRectTypes; //!< array of fill types + // int fXCount; //!< number of x-coordinates + // int fYCount; //!< number of y-coordinates + // const SkIRect* fBounds; //!< source bounds to draw from + // const SkColor* fColors; //!< array of colors + // size = 64 + fXCount * 4 + fYCount * 4 + // if fColors and fRectTypes are not null, add (fXCount + 1) * (fYCount + 1) * 5 + {1, 88, 1, 88, [](DisplayListBuilder& b) {b.drawImageLattice(TestImage1, + {TestDivs1, TestDivs1, nullptr, 3, 3, nullptr, nullptr}, + {10, 10, 40, 40}, SkFilterMode::kNearest, false);}}, + {1, 88, 1, 88, [](DisplayListBuilder& b) {b.drawImageLattice(TestImage1, + {TestDivs1, TestDivs1, nullptr, 3, 3, nullptr, nullptr}, + {10, 10, 40, 45}, SkFilterMode::kNearest, false);}}, + {1, 88, 1, 88, [](DisplayListBuilder& b) {b.drawImageLattice(TestImage1, + {TestDivs2, TestDivs1, nullptr, 3, 3, nullptr, nullptr}, + {10, 10, 40, 40}, SkFilterMode::kNearest, false);}}, + // One less yDiv does not change the allocation due to 8-byte alignment + {1, 88, 1, 88, [](DisplayListBuilder& b) {b.drawImageLattice(TestImage1, + {TestDivs1, TestDivs1, nullptr, 3, 2, nullptr, nullptr}, + {10, 10, 40, 40}, SkFilterMode::kNearest, false);}}, + {1, 88, 1, 88, [](DisplayListBuilder& b) {b.drawImageLattice(TestImage1, + {TestDivs1, TestDivs1, nullptr, 3, 3, nullptr, nullptr}, + {10, 10, 40, 40}, SkFilterMode::kLinear, false);}}, + {2, 96, 2, 96, [](DisplayListBuilder& b) {b.setColor(SK_ColorMAGENTA); + b.drawImageLattice(TestImage1, + {TestDivs1, TestDivs1, nullptr, 3, 3, nullptr, nullptr}, + {10, 10, 40, 40}, SkFilterMode::kNearest, true);}}, + {1, 88, 1, 88, [](DisplayListBuilder& b) {b.drawImageLattice(TestImage2, + {TestDivs1, TestDivs1, nullptr, 3, 3, nullptr, nullptr}, + {10, 10, 40, 40}, SkFilterMode::kNearest, false);}}, + // Supplying fBounds does not change size because the Op record always includes it + {1, 88, 1, 88, [](DisplayListBuilder& b) {b.drawImageLattice(TestImage1, + {TestDivs1, TestDivs1, nullptr, 3, 3, &TestLatticeSrcRect, nullptr}, + {10, 10, 40, 40}, SkFilterMode::kNearest, false);}}, + {1, 128, 1, 128, [](DisplayListBuilder& b) {b.drawImageLattice(TestImage1, + {TestDivs3, TestDivs3, TestRTypes, 2, 2, nullptr, TestLatticeColors}, + {10, 10, 40, 40}, SkFilterMode::kNearest, false);}}, + } + }, + { "DrawAtlas", { + {1, 40 + 32 + 32, 1, 40 + 32 + 32, [](DisplayListBuilder& b) { + static SkRSXform xforms[] = { {1, 0, 0, 0}, {0, 1, 0, 0} }; + static SkRect texs[] = { { 10, 10, 20, 20 }, {20, 20, 30, 30} }; + b.drawAtlas(TestImage1, xforms, texs, nullptr, 2, SkBlendMode::kSrcIn, + DisplayList::NearestSampling, nullptr);}}, + {1, 40 + 32 + 32, 1, 40 + 32 + 32, [](DisplayListBuilder& b) { + static SkRSXform xforms[] = { {0, 1, 0, 0}, {0, 1, 0, 0} }; + static SkRect texs[] = { { 10, 10, 20, 20 }, {20, 20, 30, 30} }; + b.drawAtlas(TestImage1, xforms, texs, nullptr, 2, SkBlendMode::kSrcIn, + DisplayList::NearestSampling, nullptr);}}, + {1, 40 + 32 + 32, 1, 40 + 32 + 32, [](DisplayListBuilder& b) { + static SkRSXform xforms[] = { {1, 0, 0, 0}, {0, 1, 0, 0} }; + static SkRect texs[] = { { 10, 10, 20, 20 }, {20, 25, 30, 30} }; + b.drawAtlas(TestImage1, xforms, texs, nullptr, 2, SkBlendMode::kSrcIn, + DisplayList::NearestSampling, nullptr);}}, + {1, 40 + 32 + 32, 1, 40 + 32 + 32, [](DisplayListBuilder& b) { + static SkRSXform xforms[] = { {1, 0, 0, 0}, {0, 1, 0, 0} }; + static SkRect texs[] = { { 10, 10, 20, 20 }, {20, 20, 30, 30} }; + b.drawAtlas(TestImage1, xforms, texs, nullptr, 2, SkBlendMode::kSrcIn, + DisplayList::LinearSampling, nullptr);}}, + {1, 40 + 32 + 32, 1, 40 + 32 + 32, [](DisplayListBuilder& b) { + static SkRSXform xforms[] = { {1, 0, 0, 0}, {0, 1, 0, 0} }; + static SkRect texs[] = { { 10, 10, 20, 20 }, {20, 20, 30, 30} }; + b.drawAtlas(TestImage1, xforms, texs, nullptr, 2, SkBlendMode::kDstIn, + DisplayList::NearestSampling, nullptr);}}, + {1, 56 + 32 + 32, 1, 56 + 32 + 32, [](DisplayListBuilder& b) { + static SkRSXform xforms[] = { {1, 0, 0, 0}, {0, 1, 0, 0} }; + static SkRect texs[] = { { 10, 10, 20, 20 }, {20, 20, 30, 30} }; + static SkRect cullRect = { 0, 0, 200, 200 }; + b.drawAtlas(TestImage2, xforms, texs, nullptr, 2, SkBlendMode::kSrcIn, + DisplayList::NearestSampling, &cullRect);}}, + {1, 40 + 32 + 32 + 8, 1, 40 + 32 + 32 + 8, [](DisplayListBuilder& b) { + static SkRSXform xforms[] = { {1, 0, 0, 0}, {0, 1, 0, 0} }; + static SkRect texs[] = { { 10, 10, 20, 20 }, {20, 20, 30, 30} }; + static SkColor colors[] = { SK_ColorBLUE, SK_ColorGREEN }; + b.drawAtlas(TestImage1, xforms, texs, colors, 2, SkBlendMode::kSrcIn, + DisplayList::NearestSampling, nullptr);}}, + {1, 56 + 32 + 32 + 8, 1, 56 + 32 + 32 + 8, [](DisplayListBuilder& b) { + static SkRSXform xforms[] = { {1, 0, 0, 0}, {0, 1, 0, 0} }; + static SkRect texs[] = { { 10, 10, 20, 20 }, {20, 20, 30, 30} }; + static SkColor colors[] = { SK_ColorBLUE, SK_ColorGREEN }; + static SkRect cullRect = { 0, 0, 200, 200 }; + b.drawAtlas(TestImage1, xforms, texs, colors, 2, SkBlendMode::kSrcIn, + DisplayList::NearestSampling, &cullRect);}}, + } + }, + { "DrawPicture", { + // cv.drawPicture cannot be compared as SkCanvas may inline it + {1, 16, -1, 16, [](DisplayListBuilder& b) {b.drawPicture(TestPicture1, nullptr, false);}}, + {1, 16, -1, 16, [](DisplayListBuilder& b) {b.drawPicture(TestPicture2, nullptr, false);}}, + {1, 16, -1, 16, [](DisplayListBuilder& b) {b.drawPicture(TestPicture1, nullptr, true);}}, + {1, 56, -1, 56, [](DisplayListBuilder& b) {b.drawPicture(TestPicture1, &TestMatrix1, false);}}, + {1, 56, -1, 56, [](DisplayListBuilder& b) {b.drawPicture(TestPicture1, &TestMatrix2, false);}}, + {1, 56, -1, 56, [](DisplayListBuilder& b) {b.drawPicture(TestPicture1, &TestMatrix1, true);}}, + } + }, + { "DrawDisplayList", { + // cv.drawDL does not exist + {1, 16, -1, 16, [](DisplayListBuilder& b) {b.drawDisplayList(TestDisplayList1);}}, + {1, 16, -1, 16, [](DisplayListBuilder& b) {b.drawDisplayList(TestDisplayList2);}}, + } + }, + { "DrawTextBlob", { + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawTextBlob(TestBlob1, 10, 10);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawTextBlob(TestBlob1, 20, 10);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawTextBlob(TestBlob1, 10, 20);}}, + {1, 24, 1, 24, [](DisplayListBuilder& b) {b.drawTextBlob(TestBlob2, 10, 10);}}, + } + }, + // The -1 op counts below are to indicate to the framework not to test + // SkCanvas conversion of these ops as it converts the operation into a + // format that is not exposed publicly and so we cannot recapture the + // operation. + // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125 + { "DrawShadow", { + // cv shadows are turned into an opaque ShadowRec which is not exposed + {1, 32, -1, 32, [](DisplayListBuilder& b) {b.drawShadow(TestPath1, SK_ColorGREEN, 1.0, false);}}, + {1, 32, -1, 32, [](DisplayListBuilder& b) {b.drawShadow(TestPath2, SK_ColorGREEN, 1.0, false);}}, + {1, 32, -1, 32, [](DisplayListBuilder& b) {b.drawShadow(TestPath1, SK_ColorBLUE, 1.0, false);}}, + {1, 32, -1, 32, [](DisplayListBuilder& b) {b.drawShadow(TestPath1, SK_ColorGREEN, 2.0, false);}}, + {1, 32, -1, 32, [](DisplayListBuilder& b) {b.drawShadow(TestPath1, SK_ColorGREEN, 1.0, true);}}, + } + }, +}; + +TEST(DisplayList, SingleOpSizes) { + for (auto& group : allGroups) { + for (size_t i = 0; i < group.variants.size(); i++) { + auto& invocation = group.variants[i]; + sk_sp dl = invocation.Build(); + auto desc = group.op_name + "(variant " + std::to_string(i + 1) + ")"; + ASSERT_EQ(dl->op_count(), invocation.op_count) << desc; + EXPECT_EQ(dl->bytes(), invocation.byte_count) << desc; + } + } +} + +TEST(DisplayList, SingleOpDisplayListsNotEqualEmpty) { + sk_sp empty = DisplayListBuilder().Build(); + for (auto& group : allGroups) { + for (size_t i = 0; i < group.variants.size(); i++) { + sk_sp dl = group.variants[i].Build(); + auto desc = + group.op_name + "(variant " + std::to_string(i + 1) + " != empty)"; + ASSERT_FALSE(dl->Equals(*empty)) << desc; + ASSERT_FALSE(empty->Equals(*dl)) << desc; + } + } +} + +TEST(DisplayList, SingleOpDisplayListsRecapturedAreEqual) { + for (auto& group : allGroups) { + for (size_t i = 0; i < group.variants.size(); i++) { + sk_sp dl = group.variants[i].Build(); + // Verify recapturing the replay of the display list is Equals() + // when dispatching directly from the DL to another builder + DisplayListBuilder builder; + dl->Dispatch(builder); + sk_sp copy = builder.Build(); + auto desc = + group.op_name + "(variant " + std::to_string(i + 1) + " == copy)"; + ASSERT_EQ(copy->op_count(), dl->op_count()) << desc; + ASSERT_EQ(copy->bytes(), dl->bytes()) << desc; + ASSERT_EQ(copy->bounds(), dl->bounds()) << desc; + ASSERT_TRUE(copy->Equals(*dl)) << desc; + ASSERT_TRUE(dl->Equals(*copy)) << desc; + } + } +} + +TEST(DisplayList, SingleOpDisplayListsRecapturedViaSkCanvasAreEqual) { + for (auto& group : allGroups) { + for (size_t i = 0; i < group.variants.size(); i++) { + if (group.variants[i].sk_op_count < 0) { + // A negative sk_op_count means "do not test this op". + // Used mainly for these cases: + // - we cannot encode a DrawShadowRec (Skia private header) + // - SkCanvas cannot receive a DisplayList + // - SkCanvas may or may not inline an SkPicture + continue; + } + // Verify a DisplayList (re)built by "rendering" it to an + // [SkCanvas->DisplayList] recorder recaptures an equivalent + // sequence. + // Note that sometimes the rendering ops can be optimized out by + // SkCanvas so the transfer is not always 1:1. We control for + // this by having separate op counts and sizes for the sk results + // and changing our expectation of Equals() results accordingly. + sk_sp dl = group.variants[i].Build(); + + DisplayListCanvasRecorder recorder(dl->bounds()); + dl->RenderTo(&recorder); + sk_sp sk_copy = recorder.Build(); + auto desc = group.op_name + "[variant " + std::to_string(i + 1) + "]"; + EXPECT_EQ(sk_copy->op_count(), group.variants[i].sk_op_count) << desc; + EXPECT_EQ(sk_copy->bytes(), group.variants[i].sk_byte_count) << desc; + if (group.variants[i].sk_version_matches()) { + EXPECT_EQ(sk_copy->bounds(), dl->bounds()) << desc; + EXPECT_TRUE(dl->Equals(*sk_copy)) << desc << " == sk_copy"; + EXPECT_TRUE(sk_copy->Equals(*dl)) << "sk_copy == " << desc; + } else { + // No assertion on bounds + // they could be equal, hard to tell + EXPECT_FALSE(dl->Equals(*sk_copy)) << desc << " != sk_copy"; + EXPECT_FALSE(sk_copy->Equals(*dl)) << "sk_copy != " << desc; + } + } + } +} + +TEST(DisplayList, SingleOpDisplayListsCompareToEachOther) { + for (auto& group : allGroups) { + std::vector> listsA; + std::vector> listsB; + for (size_t i = 0; i < group.variants.size(); i++) { + listsA.push_back(group.variants[i].Build()); + listsB.push_back(group.variants[i].Build()); + } + + for (size_t i = 0; i < listsA.size(); i++) { + sk_sp listA = listsA[i]; + for (size_t j = 0; j < listsB.size(); j++) { + sk_sp listB = listsB[j]; + auto desc = group.op_name + "(variant " + std::to_string(i + 1) + + " ==? variant " + std::to_string(j + 1) + ")"; + if (i == j) { + ASSERT_EQ(listA->op_count(), listB->op_count()) << desc; + ASSERT_EQ(listA->bytes(), listB->bytes()) << desc; + ASSERT_EQ(listA->bounds(), listB->bounds()) << desc; + ASSERT_TRUE(listA->Equals(*listB)) << desc; + ASSERT_TRUE(listB->Equals(*listA)) << desc; + } else { + // No assertion on op/byte counts or bounds + // they may or may not be equal between variants + ASSERT_FALSE(listA->Equals(*listB)) << desc; + ASSERT_FALSE(listB->Equals(*listA)) << desc; + } + } + } + } +} + +static sk_sp Build(size_t g_index, size_t v_index) { + DisplayListBuilder builder; + int op_count = 0; + size_t byte_count = 0; + for (size_t i = 0; i < allGroups.size(); i++) { + DisplayListInvocationGroup& group = allGroups[i]; + size_t j = (i == g_index ? v_index : 0); + if (j >= group.variants.size()) + continue; + DisplayListInvocation& invocation = group.variants[j]; + op_count += invocation.op_count; + byte_count += invocation.byte_count; + invocation.invoker(builder); + } + sk_sp dl = builder.Build(); + std::string name; + if (g_index >= allGroups.size()) { + name = "Default"; + } else { + name = allGroups[g_index].op_name; + if (v_index < 0) { + name += " skipped"; + } else { + name += " variant " + std::to_string(v_index + 1); + } + } + EXPECT_EQ(dl->op_count(), op_count) << name; + EXPECT_EQ(dl->bytes(), byte_count) << name; + return dl; +} + +TEST(DisplayList, DisplayListsWithVaryingOpComparisons) { + sk_sp default_dl = Build(allGroups.size(), 0); + ASSERT_TRUE(default_dl->Equals(*default_dl)) << "Default == itself"; + for (size_t gi = 0; gi < allGroups.size(); gi++) { + DisplayListInvocationGroup& group = allGroups[gi]; + sk_sp missing_dl = Build(gi, group.variants.size()); + auto desc = "[Group " + std::to_string(gi + 1) + " omitted]"; + ASSERT_TRUE(missing_dl->Equals(*missing_dl)) << desc << " == itself"; + ASSERT_FALSE(missing_dl->Equals(*default_dl)) << desc << " != Default"; + ASSERT_FALSE(default_dl->Equals(*missing_dl)) << "Default != " << desc; + for (size_t vi = 0; vi < group.variants.size(); vi++) { + auto desc = "[Group " + std::to_string(gi + 1) + " variant " + + std::to_string(vi + 1) + "]"; + sk_sp variant_dl = Build(gi, vi); + ASSERT_TRUE(variant_dl->Equals(*variant_dl)) << desc << " == itself"; + if (vi == 0) { + ASSERT_TRUE(variant_dl->Equals(*default_dl)) << desc << " == Default"; + ASSERT_TRUE(default_dl->Equals(*variant_dl)) << "Default == " << desc; + } else { + ASSERT_FALSE(variant_dl->Equals(*default_dl)) << desc << " != Default"; + ASSERT_FALSE(default_dl->Equals(*variant_dl)) << "Default != " << desc; + } + ASSERT_FALSE(variant_dl->Equals(*missing_dl)) << desc << " != omitted"; + ASSERT_FALSE(missing_dl->Equals(*variant_dl)) << "omitted != " << desc; + } + } +} + +} // namespace testing +} // namespace flutter diff --git a/flow/display_list_utils.cc b/flow/display_list_utils.cc new file mode 100644 index 0000000000000..474435b94c992 --- /dev/null +++ b/flow/display_list_utils.cc @@ -0,0 +1,428 @@ +// Copyright 2013 The Flutter 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 +#include + +#include "flutter/flow/display_list_utils.h" +#include "flutter/flow/layers/physical_shape_layer.h" +#include "flutter/fml/logging.h" + +#include "third_party/skia/include/core/SkMaskFilter.h" +#include "third_party/skia/include/core/SkPath.h" +#include "third_party/skia/include/core/SkRSXform.h" +#include "third_party/skia/include/core/SkTextBlob.h" +#include "third_party/skia/include/utils/SkShadowUtils.h" + +namespace flutter { + +// clang-format off +constexpr float invert_color_matrix[20] = { + -1.0, 0, 0, 1.0, 0, + 0, -1.0, 0, 1.0, 0, + 0, 0, -1.0, 1.0, 0, + 1.0, 1.0, 1.0, 1.0, 0 +}; +// clang-format on + +void SkPaintDispatchHelper::setAA(bool aa) { + paint_.setAntiAlias(aa); +} +void SkPaintDispatchHelper::setDither(bool dither) { + paint_.setDither(dither); +} +void SkPaintDispatchHelper::setInvertColors(bool invert) { + invert_colors_ = invert; + paint_.setColorFilter(makeColorFilter()); +} +void SkPaintDispatchHelper::setCaps(SkPaint::Cap cap) { + paint_.setStrokeCap(cap); +} +void SkPaintDispatchHelper::setJoins(SkPaint::Join join) { + paint_.setStrokeJoin(join); +} +void SkPaintDispatchHelper::setDrawStyle(SkPaint::Style style) { + paint_.setStyle(style); +} +void SkPaintDispatchHelper::setStrokeWidth(SkScalar width) { + paint_.setStrokeWidth(width); +} +void SkPaintDispatchHelper::setMiterLimit(SkScalar limit) { + paint_.setStrokeMiter(limit); +} +void SkPaintDispatchHelper::setColor(SkColor color) { + paint_.setColor(color); +} +void SkPaintDispatchHelper::setBlendMode(SkBlendMode mode) { + paint_.setBlendMode(mode); +} +void SkPaintDispatchHelper::setFilterQuality(SkFilterQuality quality) { + paint_.setFilterQuality(quality); +} +void SkPaintDispatchHelper::setShader(sk_sp shader) { + paint_.setShader(shader); +} +void SkPaintDispatchHelper::setImageFilter(sk_sp filter) { + paint_.setImageFilter(filter); +} +void SkPaintDispatchHelper::setColorFilter(sk_sp filter) { + color_filter_ = filter; + paint_.setColorFilter(makeColorFilter()); +} +void SkPaintDispatchHelper::setMaskFilter(sk_sp filter) { + paint_.setMaskFilter(filter); +} +void SkPaintDispatchHelper::setMaskBlurFilter(SkBlurStyle style, + SkScalar sigma) { + paint_.setMaskFilter(SkMaskFilter::MakeBlur(style, sigma)); +} + +sk_sp SkPaintDispatchHelper::makeColorFilter() { + if (!invert_colors_) { + return color_filter_; + } + sk_sp invert_filter = + SkColorFilters::Matrix(invert_color_matrix); + if (color_filter_) { + invert_filter = invert_filter->makeComposed(color_filter_); + } + return invert_filter; +} + +void SkMatrixDispatchHelper::translate(SkScalar tx, SkScalar ty) { + matrix_.preTranslate(tx, ty); +} +void SkMatrixDispatchHelper::scale(SkScalar sx, SkScalar sy) { + matrix_.preScale(sx, sy); +} +void SkMatrixDispatchHelper::rotate(SkScalar degrees) { + matrix_.preRotate(degrees); +} +void SkMatrixDispatchHelper::skew(SkScalar sx, SkScalar sy) { + matrix_.preSkew(sx, sy); +} +void SkMatrixDispatchHelper::transform2x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt) { + matrix_.preConcat(SkMatrix::MakeAll(mxx, mxy, mxt, myx, myy, myt, 0, 0, 1)); +} +void SkMatrixDispatchHelper::transform3x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt, + SkScalar px, + SkScalar py, + SkScalar pt) { + matrix_.preConcat( + SkMatrix::MakeAll(mxx, mxy, mxt, myx, myy, myt, px, py, pt)); +} +void SkMatrixDispatchHelper::save() { + saved_.push_back(matrix_); +} +void SkMatrixDispatchHelper::restore() { + matrix_ = saved_.back(); + saved_.pop_back(); +} +void SkMatrixDispatchHelper::reset() { + matrix_.reset(); +} + +void ClipBoundsDispatchHelper::clipRect(const SkRect& rect, + bool isAA, + SkClipOp clip_op) { + if (clip_op == SkClipOp::kIntersect) { + intersect(rect); + } +} +void ClipBoundsDispatchHelper::clipRRect(const SkRRect& rrect, + bool isAA, + SkClipOp clip_op) { + if (clip_op == SkClipOp::kIntersect) { + intersect(rrect.getBounds()); + } +} +void ClipBoundsDispatchHelper::clipPath(const SkPath& path, + bool isAA, + SkClipOp clip_op) { + if (clip_op == SkClipOp::kIntersect) { + intersect(path.getBounds()); + } +} +void ClipBoundsDispatchHelper::intersect(const SkRect& rect) { + SkRect devClipBounds = matrix().mapRect(rect); + if (!bounds_.intersect(devClipBounds)) { + bounds_.setEmpty(); + } +} +void ClipBoundsDispatchHelper::save() { + saved_.push_back(bounds_); +} +void ClipBoundsDispatchHelper::restore() { + bounds_ = saved_.back(); + saved_.pop_back(); +} + +void DisplayListBoundsCalculator::saveLayer(const SkRect* bounds, + bool with_paint) { + SkMatrixDispatchHelper::save(); + ClipBoundsDispatchHelper::save(); + SaveInfo info = + with_paint ? SaveLayerWithPaintInfo(this, accumulator_, matrix(), paint()) + : SaveLayerInfo(accumulator_, matrix()); + saved_infos_.push_back(info); + accumulator_ = info.save(); + SkMatrixDispatchHelper::reset(); +} +void DisplayListBoundsCalculator::save() { + SkMatrixDispatchHelper::save(); + ClipBoundsDispatchHelper::save(); + SaveInfo info = SaveInfo(accumulator_); + saved_infos_.push_back(info); + accumulator_ = info.save(); +} +void DisplayListBoundsCalculator::restore() { + if (!saved_infos_.empty()) { + SkMatrixDispatchHelper::restore(); + ClipBoundsDispatchHelper::restore(); + SaveInfo info = saved_infos_.back(); + saved_infos_.pop_back(); + accumulator_ = info.restore(); + } +} + +void DisplayListBoundsCalculator::drawPaint() { + if (!bounds_cull_.isEmpty()) { + root_accumulator_.accumulate(bounds_cull_); + } +} +void DisplayListBoundsCalculator::drawColor(SkColor color, SkBlendMode mode) { + if (!bounds_cull_.isEmpty()) { + root_accumulator_.accumulate(bounds_cull_); + } +} +void DisplayListBoundsCalculator::drawLine(const SkPoint& p0, + const SkPoint& p1) { + SkRect bounds = SkRect::MakeLTRB(p0.fX, p0.fY, p1.fX, p1.fY).makeSorted(); + accumulateRect(bounds, true); +} +void DisplayListBoundsCalculator::drawRect(const SkRect& rect) { + accumulateRect(rect); +} +void DisplayListBoundsCalculator::drawOval(const SkRect& bounds) { + accumulateRect(bounds); +} +void DisplayListBoundsCalculator::drawCircle(const SkPoint& center, + SkScalar radius) { + accumulateRect(SkRect::MakeLTRB(center.fX - radius, center.fY - radius, + center.fX + radius, center.fY + radius)); +} +void DisplayListBoundsCalculator::drawRRect(const SkRRect& rrect) { + accumulateRect(rrect.getBounds()); +} +void DisplayListBoundsCalculator::drawDRRect(const SkRRect& outer, + const SkRRect& inner) { + accumulateRect(outer.getBounds()); +} +void DisplayListBoundsCalculator::drawPath(const SkPath& path) { + accumulateRect(path.getBounds()); +} +void DisplayListBoundsCalculator::drawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter) { + // This could be tighter if we compute where the start and end + // angles are and then also consider the quadrants swept and + // the center if specified. + accumulateRect(bounds); +} +void DisplayListBoundsCalculator::drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint pts[]) { + if (count > 0) { + BoundsAccumulator ptBounds; + for (size_t i = 0; i < count; i++) { + ptBounds.accumulate(pts[i]); + } + accumulateRect(ptBounds.getBounds(), true); + } +} +void DisplayListBoundsCalculator::drawVertices(const sk_sp vertices, + SkBlendMode mode) { + accumulateRect(vertices->bounds()); +} +void DisplayListBoundsCalculator::drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling) { + SkRect bounds = SkRect::Make(image->bounds()); + bounds.offset(point); + accumulateRect(bounds); +} +void DisplayListBoundsCalculator::drawImageRect( + const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + SkCanvas::SrcRectConstraint constraint) { + accumulateRect(dst); +} +void DisplayListBoundsCalculator::drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter) { + accumulateRect(dst); +} +void DisplayListBoundsCalculator::drawImageLattice( + const sk_sp image, + const SkCanvas::Lattice& lattice, + const SkRect& dst, + SkFilterMode filter, + bool with_paint) { + accumulateRect(dst); +} +void DisplayListBoundsCalculator::drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + SkBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cullRect) { + SkPoint quad[4]; + BoundsAccumulator atlasBounds; + for (int i = 0; i < count; i++) { + const SkRect& src = tex[i]; + xform[i].toQuad(src.width(), src.height(), quad); + for (int j = 0; j < 4; j++) { + atlasBounds.accumulate(quad[j]); + } + } + if (atlasBounds.isNotEmpty()) { + accumulateRect(atlasBounds.getBounds()); + } +} +void DisplayListBoundsCalculator::drawPicture(const sk_sp picture, + const SkMatrix* pic_matrix, + bool with_save_layer) { + // TODO(flar) cull rect really cannot be trusted in general, but it will + // work for SkPictures generated from our own PictureRecorder or any + // picture captured with an SkRTreeFactory or accurate bounds estimate. + SkRect bounds = picture->cullRect(); + if (pic_matrix) { + pic_matrix->mapRect(&bounds); + } + if (with_save_layer) { + accumulateRect(bounds); + } else { + matrix().mapRect(&bounds); + accumulator_->accumulate(bounds); + } +} +void DisplayListBoundsCalculator::drawDisplayList( + const sk_sp display_list) { + accumulateRect(display_list->bounds()); +} +void DisplayListBoundsCalculator::drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) { + accumulateRect(blob->bounds().makeOffset(x, y)); +} +void DisplayListBoundsCalculator::drawShadow(const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool occludes) { + // Constants from physical_shape_layer.cc + const SkScalar kLightHeight = 600; + const SkScalar kLightRadius = 800; + + SkShadowFlags flags = occludes + ? SkShadowFlags::kTransparentOccluder_ShadowFlag + : SkShadowFlags::kNone_ShadowFlag; + const SkRect& bounds = path.getBounds(); + SkScalar shadow_x = (bounds.left() + bounds.right()) / 2; + SkScalar shadow_y = bounds.top() - 600.0f; + SkRect shadow_bounds; + SkShadowUtils::GetLocalBounds( + matrix(), path, SkPoint3::Make(0, 0, elevation), + SkPoint3::Make(shadow_x, shadow_y, kLightHeight), kLightRadius, flags, + &shadow_bounds); + accumulateRect(shadow_bounds); +} + +void DisplayListBoundsCalculator::accumulateRect(const SkRect& rect, + bool forceStroke) { + SkRect dstRect = rect; + const SkPaint& p = paint(); + if (forceStroke) { + if (p.getStyle() == SkPaint::kFill_Style) { + setDrawStyle(SkPaint::kStroke_Style); + } else { + forceStroke = false; + } + } + if (p.canComputeFastBounds()) { + dstRect = p.computeFastBounds(rect, &dstRect); + matrix().mapRect(&dstRect); + accumulator_->accumulate(dstRect); + } else { + root_accumulator_.accumulate(bounds_cull_); + } + if (forceStroke) { + setDrawStyle(SkPaint::kFill_Style); + } +} + +DisplayListBoundsCalculator::SaveInfo::SaveInfo(BoundsAccumulator* accumulator) + : saved_accumulator_(accumulator) {} +BoundsAccumulator* DisplayListBoundsCalculator::SaveInfo::save() { + // No need to swap out the accumulator for a normal save + return saved_accumulator_; +} +BoundsAccumulator* DisplayListBoundsCalculator::SaveInfo::restore() { + return saved_accumulator_; +} + +DisplayListBoundsCalculator::SaveLayerInfo::SaveLayerInfo( + BoundsAccumulator* accumulator, + const SkMatrix& matrix) + : SaveInfo(accumulator), matrix_(matrix) {} +BoundsAccumulator* DisplayListBoundsCalculator::SaveLayerInfo::save() { + // Use the local layerAccumulator until restore is called and + // then transform (and adjust with paint if necessary) on restore() + return &layer_accumulator_; +} +BoundsAccumulator* DisplayListBoundsCalculator::SaveLayerInfo::restore() { + SkRect layer_bounds = layer_accumulator_.getBounds(); + matrix_.mapRect(&layer_bounds); + saved_accumulator_->accumulate(layer_bounds); + return saved_accumulator_; +} + +DisplayListBoundsCalculator::SaveLayerWithPaintInfo::SaveLayerWithPaintInfo( + DisplayListBoundsCalculator* calculator, + BoundsAccumulator* accumulator, + const SkMatrix& saveMatrix, + const SkPaint& savePaint) + : SaveLayerInfo(accumulator, saveMatrix), + calculator_(calculator), + paint_(savePaint) {} + +BoundsAccumulator* +DisplayListBoundsCalculator::SaveLayerWithPaintInfo::restore() { + SkRect layer_bounds = layer_accumulator_.getBounds(); + if (paint_.canComputeFastBounds()) { + layer_bounds = paint_.computeFastBounds(layer_bounds, &layer_bounds); + matrix_.mapRect(&layer_bounds); + saved_accumulator_->accumulate(layer_bounds); + } else { + calculator_->root_accumulator_.accumulate(calculator_->bounds_cull_); + } + return saved_accumulator_; +} + +} // namespace flutter diff --git a/flow/display_list_utils.h b/flow/display_list_utils.h new file mode 100644 index 0000000000000..68b9dc56b0120 --- /dev/null +++ b/flow/display_list_utils.h @@ -0,0 +1,372 @@ +// Copyright 2013 The Flutter 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 FLUTTER_FLOW_DISPLAY_LIST_UTILS_H_ +#define FLUTTER_FLOW_DISPLAY_LIST_UTILS_H_ + +#include "flutter/flow/display_list.h" + +#include "third_party/skia/include/core/SkMaskFilter.h" + +// This file contains various utility classes to ease implementing +// a Flutter DisplayList Dispatcher, including: +// +// IngoreAttributeDispatchHelper: +// IngoreClipDispatchHelper: +// IngoreTransformDispatchHelper +// Empty overrides of all of the associated methods of Dispatcher +// for dispatchers that only track some of the rendering operations +// +// SkPaintAttributeDispatchHelper: +// Tracks the attribute methods and maintains their state in an +// SkPaint object. +// SkMatrixTransformDispatchHelper: +// Tracks the transform methods and maintains their state in a +// (save/restore stack of) SkMatrix object. +// ClipBoundsDispatchHelper: +// Tracks the clip methods and maintains a culling box in a +// (save/restore stack of) SkRect culling rectangle. +// +// DisplayListBoundsCalculator: +// A class that can traverse an entire display list and compute +// a conservative estimate of the bounds of all of the rendering +// operations. + +namespace flutter { + +// A utility class that will ignore all Dispatcher methods relating +// to the setting of attributes. +class IngoreAttributeDispatchHelper : public virtual Dispatcher { + public: + void setAA(bool aa) override {} + void setDither(bool dither) override {} + void setInvertColors(bool invert) override {} + void setCaps(SkPaint::Cap cap) override {} + void setJoins(SkPaint::Join join) override {} + void setDrawStyle(SkPaint::Style style) override {} + void setStrokeWidth(SkScalar width) override {} + void setMiterLimit(SkScalar limit) override {} + void setColor(SkColor color) override {} + void setBlendMode(SkBlendMode mode) override {} + void setFilterQuality(SkFilterQuality quality) override {} + void setShader(sk_sp shader) override {} + void setImageFilter(sk_sp filter) override {} + void setColorFilter(sk_sp filter) override {} + void setMaskFilter(sk_sp filter) override {} + void setMaskBlurFilter(SkBlurStyle style, SkScalar sigma) override {} +}; + +// A utility class that will ignore all Dispatcher methods relating +// to setting a clip. +class IngoreClipDispatchHelper : public virtual Dispatcher { + void clipRect(const SkRect& rect, bool isAA, SkClipOp clip_op) override {} + void clipRRect(const SkRRect& rrect, bool isAA, SkClipOp clip_op) override {} + void clipPath(const SkPath& path, bool isAA, SkClipOp clip_op) override {} +}; + +// A utility class that will ignore all Dispatcher methods relating +// to modifying the transform. +class IngoreTransformDispatchHelper : public virtual Dispatcher { + public: + void translate(SkScalar tx, SkScalar ty) override {} + void scale(SkScalar sx, SkScalar sy) override {} + void rotate(SkScalar degrees) override {} + void skew(SkScalar sx, SkScalar sy) override {} + void transform2x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt) override {} + void transform3x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt, + SkScalar px, + SkScalar py, + SkScalar pt) override {} +}; + +// A utility class that will monitor the Dispatcher methods relating +// to the rendering attributes and accumulate them into an SkPaint +// which can be accessed at any time via paint(). +class SkPaintDispatchHelper : public virtual Dispatcher { + public: + void setAA(bool aa) override; + void setDither(bool dither) override; + void setInvertColors(bool invert) override; + void setCaps(SkPaint::Cap cap) override; + void setJoins(SkPaint::Join join) override; + void setDrawStyle(SkPaint::Style style) override; + void setStrokeWidth(SkScalar width) override; + void setMiterLimit(SkScalar limit) override; + void setColor(SkColor color) override; + void setBlendMode(SkBlendMode mode) override; + void setFilterQuality(SkFilterQuality quality) override; + void setShader(sk_sp shader) override; + void setImageFilter(sk_sp filter) override; + void setColorFilter(sk_sp filter) override; + void setMaskFilter(sk_sp filter) override; + void setMaskBlurFilter(SkBlurStyle style, SkScalar sigma) override; + + const SkPaint& paint() { return paint_; } + + private: + SkPaint paint_; + bool invert_colors_ = false; + sk_sp color_filter_; + + sk_sp makeColorFilter(); +}; + +class SkMatrixSource { + public: + virtual const SkMatrix& matrix() const = 0; +}; + +// A utility class that will monitor the Dispatcher methods relating +// to the transform and accumulate them into an SkMatrix which can +// be accessed at any time via getMatrix(). +// +// This class also implements an appropriate stack of transforms via +// its save() and restore() methods so those methods will need to be +// forwarded if overridden in more than one super class. +class SkMatrixDispatchHelper : public virtual Dispatcher, + public virtual SkMatrixSource { + public: + void translate(SkScalar tx, SkScalar ty) override; + void scale(SkScalar sx, SkScalar sy) override; + void rotate(SkScalar degrees) override; + void skew(SkScalar sx, SkScalar sy) override; + void transform2x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt) override; + void transform3x3(SkScalar mxx, + SkScalar mxy, + SkScalar mxt, + SkScalar myx, + SkScalar myy, + SkScalar myt, + SkScalar px, + SkScalar py, + SkScalar pt) override; + + void save() override; + void restore() override; + + const SkMatrix& matrix() const override { return matrix_; } + + protected: + void reset(); + + private: + SkMatrix matrix_; + std::vector saved_; +}; + +// A utility class that will monitor the Dispatcher methods relating +// to the clip and accumulate a conservative bounds into an SkRect +// which can be accessed at any time via getCullingBounds(). +// +// The subclass must implement a single virtual method matrix() +// which will happen automatically if the subclass also inherits +// from SkMatrixTransformDispatchHelper. +// +// This class also implements an appropriate stack of transforms via +// its save() and restore() methods so those methods will need to be +// forwarded if overridden in more than one super class. +class ClipBoundsDispatchHelper : public virtual Dispatcher, + private virtual SkMatrixSource { + public: + void clipRect(const SkRect& rect, bool isAA, SkClipOp clip_op) override; + void clipRRect(const SkRRect& rrect, bool isAA, SkClipOp clip_op) override; + void clipPath(const SkPath& path, bool isAA, SkClipOp clip_op) override; + + void save() override; + void restore() override; + + const SkRect& getCullingBounds() const { return bounds_; } + + private: + SkRect bounds_; + std::vector saved_; + + void intersect(const SkRect& clipBounds); +}; + +class BoundsAccumulator { + public: + void accumulate(const SkPoint& p) { accumulate(p.fX, p.fY); } + void accumulate(SkScalar x, SkScalar y) { + if (min_x_ > x) + min_x_ = x; + if (min_y_ > y) + min_y_ = y; + if (max_x_ < x) + max_x_ = x; + if (max_y_ < y) + max_y_ = y; + } + void accumulate(const SkRect& r) { + if (r.fLeft <= r.fRight && r.fTop <= r.fBottom) { + accumulate(r.fLeft, r.fTop); + accumulate(r.fRight, r.fBottom); + } + } + + bool isEmpty() const { return min_x_ >= max_x_ || min_y_ >= max_y_; } + bool isNotEmpty() const { return min_x_ < max_x_ && min_y_ < max_y_; } + + SkRect getBounds() const { + return (max_x_ > min_x_ && max_y_ > min_y_) + ? SkRect::MakeLTRB(min_x_, min_y_, max_x_, max_y_) + : SkRect::MakeEmpty(); + } + + private: + SkScalar min_x_ = std::numeric_limits::infinity(); + SkScalar min_y_ = std::numeric_limits::infinity(); + SkScalar max_x_ = -std::numeric_limits::infinity(); + SkScalar max_y_ = -std::numeric_limits::infinity(); +}; + +// This class implements all rendering methods and computes a liberal +// bounds of the rendering operations. +class DisplayListBoundsCalculator final + : public virtual Dispatcher, + public virtual SkPaintDispatchHelper, + public virtual SkMatrixDispatchHelper, + public virtual ClipBoundsDispatchHelper { + public: + // Construct a Calculator to determine the bounds of a list of + // DisplayList dispatcher method calls. Since 2 of the method calls + // have no intrinsic size because they render to the entire available, + // the |cullRect| provides a bounds for them to include. + DisplayListBoundsCalculator(const SkRect& cull_rect = SkRect::MakeEmpty()) + : accumulator_(&root_accumulator_), bounds_cull_(cull_rect) {} + + void saveLayer(const SkRect* bounds, bool with_paint) override; + void save() override; + void restore() override; + + void drawPaint() override; + void drawColor(SkColor color, SkBlendMode mode) override; + void drawLine(const SkPoint& p0, const SkPoint& p1) override; + void drawRect(const SkRect& rect) override; + void drawOval(const SkRect& bounds) override; + void drawCircle(const SkPoint& center, SkScalar radius) override; + void drawRRect(const SkRRect& rrect) override; + void drawDRRect(const SkRRect& outer, const SkRRect& inner) override; + void drawPath(const SkPath& path) override; + void drawArc(const SkRect& bounds, + SkScalar start, + SkScalar sweep, + bool useCenter) override; + void drawPoints(SkCanvas::PointMode mode, + uint32_t count, + const SkPoint pts[]) override; + void drawVertices(const sk_sp vertices, + SkBlendMode mode) override; + void drawImage(const sk_sp image, + const SkPoint point, + const SkSamplingOptions& sampling) override; + void drawImageRect(const sk_sp image, + const SkRect& src, + const SkRect& dst, + const SkSamplingOptions& sampling, + SkCanvas::SrcRectConstraint constraint) override; + void drawImageNine(const sk_sp image, + const SkIRect& center, + const SkRect& dst, + SkFilterMode filter) override; + void drawImageLattice(const sk_sp image, + const SkCanvas::Lattice& lattice, + const SkRect& dst, + SkFilterMode filter, + bool with_paint) override; + void drawAtlas(const sk_sp atlas, + const SkRSXform xform[], + const SkRect tex[], + const SkColor colors[], + int count, + SkBlendMode mode, + const SkSamplingOptions& sampling, + const SkRect* cullRect) override; + void drawPicture(const sk_sp picture, + const SkMatrix* matrix, + bool with_save_layer) override; + void drawDisplayList(const sk_sp display_list) override; + void drawTextBlob(const sk_sp blob, + SkScalar x, + SkScalar y) override; + void drawShadow(const SkPath& path, + const SkColor color, + const SkScalar elevation, + bool occludes) override; + + SkRect getBounds() { return accumulator_->getBounds(); } + + private: + // current accumulator based on saveLayer history + BoundsAccumulator* accumulator_; + + // Only used for drawColor and drawPaint and paint objects that + // cannot support fast bounds. + SkRect bounds_cull_; + BoundsAccumulator root_accumulator_; + + class SaveInfo { + public: + SaveInfo(BoundsAccumulator* accumulator); + virtual ~SaveInfo() = default; + + virtual BoundsAccumulator* save(); + virtual BoundsAccumulator* restore(); + + protected: + BoundsAccumulator* saved_accumulator_; + }; + + class SaveLayerInfo : public SaveInfo { + public: + SaveLayerInfo(BoundsAccumulator* accumulator, const SkMatrix& matrix); + virtual ~SaveLayerInfo() = default; + + BoundsAccumulator* save() override; + BoundsAccumulator* restore() override; + + protected: + BoundsAccumulator layer_accumulator_; + const SkMatrix matrix_; + }; + + class SaveLayerWithPaintInfo : public SaveLayerInfo { + public: + SaveLayerWithPaintInfo(DisplayListBoundsCalculator* calculator, + BoundsAccumulator* accumulator, + const SkMatrix& save_matrix, + const SkPaint& save_paint); + virtual ~SaveLayerWithPaintInfo() = default; + + BoundsAccumulator* restore() override; + + protected: + DisplayListBoundsCalculator* calculator_; + + SkPaint paint_; + }; + + std::vector saved_infos_; + + void accumulateRect(const SkRect& rect, bool force_stroke = false); +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_DISPLAY_LIST_UTILS_H_ diff --git a/flow/layers/display_list_layer.cc b/flow/layers/display_list_layer.cc new file mode 100644 index 0000000000000..a40cc314c82ec --- /dev/null +++ b/flow/layers/display_list_layer.cc @@ -0,0 +1,129 @@ +// Copyright 2013 The Flutter 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 "flutter/flow/layers/display_list_layer.h" + +#include "flutter/flow/display_list_canvas.h" + +namespace flutter { + +DisplayListLayer::DisplayListLayer(const SkPoint& offset, + sk_sp display_list, + bool is_complex, + bool will_change) + : offset_(offset), + display_list_(display_list), + is_complex_(is_complex), + will_change_(will_change) {} + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +bool DisplayListLayer::IsReplacing(DiffContext* context, + const Layer* layer) const { + // Only return true for identical display lists; This way + // ContainerLayer::DiffChildren can detect when a display list layer + // got inserted between other display list layers + auto old_layer = layer->as_display_list_layer(); + return old_layer != nullptr && offset_ == old_layer->offset_ && + Compare(context->statistics(), this, old_layer); +} + +void DisplayListLayer::Diff(DiffContext* context, const Layer* old_layer) { + DiffContext::AutoSubtreeRestore subtree(context); + if (!context->IsSubtreeDirty()) { +#ifndef NDEBUG + FML_DCHECK(old_layer); + auto prev = old_layer->as_display_list_layer(); + DiffContext::Statistics dummy_statistics; + // IsReplacing has already determined that the display list is same + FML_DCHECK(prev->offset_ == offset_ && + Compare(dummy_statistics, this, prev)); +#endif + } + context->PushTransform(SkMatrix::Translate(offset_.x(), offset_.y())); + context->AddLayerBounds(display_list_->bounds()); + context->SetLayerPaintRegion(this, context->CurrentSubtreeRegion()); +} + +bool DisplayListLayer::Compare(DiffContext::Statistics& statistics, + const DisplayListLayer* l1, + const DisplayListLayer* l2) { + const auto& dl1 = l1->display_list_; + const auto& dl2 = l2->display_list_; + if (dl1.get() == dl2.get()) { + statistics.AddSameInstancePicture(); + return true; + } + const auto op_cnt_1 = dl1->op_count(); + const auto op_cnt_2 = dl2->op_count(); + const auto op_bytes_1 = dl1->bytes(); + const auto op_bytes_2 = dl2->bytes(); + if (op_cnt_1 != op_cnt_2 || op_bytes_1 != op_bytes_2 || + dl1->bounds() != dl2->bounds()) { + statistics.AddNewPicture(); + return false; + } + + if (op_bytes_1 > kMaxBytesToCompare) { + statistics.AddPictureTooComplexToCompare(); + return false; + } + + statistics.AddDeepComparePicture(); + + auto res = dl1->Equals(*dl2); + if (res) { + statistics.AddDifferentInstanceButEqualPicture(); + } else { + statistics.AddNewPicture(); + } + return res; +} + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + +void DisplayListLayer::Preroll(PrerollContext* context, + const SkMatrix& matrix) { + TRACE_EVENT0("flutter", "DisplayListLayer::Preroll"); + + DisplayList* disp_list = display_list(); + + if (auto* cache = context->raster_cache) { + TRACE_EVENT0("flutter", "DisplayListLayer::RasterCache (Preroll)"); + + SkMatrix ctm = matrix; + ctm.preTranslate(offset_.x(), offset_.y()); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + ctm = RasterCache::GetIntegralTransCTM(ctm); +#endif + cache->Prepare(context->gr_context, disp_list, ctm, + context->dst_color_space, is_complex_, will_change_); + } + + SkRect bounds = disp_list->bounds().makeOffset(offset_.x(), offset_.y()); + set_paint_bounds(bounds); +} + +void DisplayListLayer::Paint(PaintContext& context) const { + TRACE_EVENT0("flutter", "DisplayListLayer::Paint"); + FML_DCHECK(display_list_.get()); + FML_DCHECK(needs_painting(context)); + + SkAutoCanvasRestore save(context.leaf_nodes_canvas, true); + context.leaf_nodes_canvas->translate(offset_.x(), offset_.y()); +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + context.leaf_nodes_canvas->setMatrix(RasterCache::GetIntegralTransCTM( + context.leaf_nodes_canvas->getTotalMatrix())); +#endif + + if (context.raster_cache && + context.raster_cache->Draw(*display_list(), *context.leaf_nodes_canvas)) { + TRACE_EVENT_INSTANT0("flutter", "raster cache hit"); + return; + } + + display_list()->RenderTo(context.leaf_nodes_canvas); +} + +} // namespace flutter diff --git a/flow/layers/display_list_layer.h b/flow/layers/display_list_layer.h new file mode 100644 index 0000000000000..4760d2d200654 --- /dev/null +++ b/flow/layers/display_list_layer.h @@ -0,0 +1,61 @@ +// Copyright 2013 The Flutter 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 FLUTTER_FLOW_LAYERS_DISPLAY_LIST_LAYER_H_ +#define FLUTTER_FLOW_LAYERS_DISPLAY_LIST_LAYER_H_ + +#include "flutter/flow/display_list.h" +#include "flutter/flow/layers/layer.h" + +namespace flutter { + +class DisplayListLayer : public Layer { + public: + static constexpr size_t kMaxBytesToCompare = 10000; + + DisplayListLayer(const SkPoint& offset, + sk_sp display_list, + bool is_complex, + bool will_change); + + DisplayList* display_list() const { return display_list_.get(); } + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + bool IsReplacing(DiffContext* context, const Layer* layer) const override; + + void Diff(DiffContext* context, const Layer* old_layer) override; + + const DisplayListLayer* as_display_list_layer() const override { + return this; + } + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + + void Preroll(PrerollContext* frame, const SkMatrix& matrix) override; + + void Paint(PaintContext& context) const override; + + private: + SkPoint offset_; + sk_sp display_list_; + bool is_complex_ = false; + bool will_change_ = false; + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + + sk_sp SerializedPicture() const; + mutable sk_sp cached_serialized_picture_; + static bool Compare(DiffContext::Statistics& statistics, + const DisplayListLayer* l1, + const DisplayListLayer* l2); + +#endif // FLUTTER_ENABLE_DIFF_CONTEXT + + FML_DISALLOW_COPY_AND_ASSIGN(DisplayListLayer); +}; + +} // namespace flutter + +#endif // FLUTTER_FLOW_LAYERS_DISPLAY_LIST_LAYER_H_ diff --git a/flow/layers/display_list_layer_unittests.cc b/flow/layers/display_list_layer_unittests.cc new file mode 100644 index 0000000000000..ad1842b8c5a73 --- /dev/null +++ b/flow/layers/display_list_layer_unittests.cc @@ -0,0 +1,159 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#define FML_USED_ON_EMBEDDER + +#include "flutter/flow/layers/display_list_layer.h" + +#include "flutter/flow/testing/diff_context_test.h" +#include "flutter/fml/macros.h" +#include "flutter/testing/mock_canvas.h" + +#ifndef SUPPORT_FRACTIONAL_TRANSLATION +#include "flutter/flow/raster_cache.h" +#endif + +namespace flutter { +namespace testing { + +using DisplayListLayerTest = LayerTest; + +#ifndef NDEBUG +TEST_F(DisplayListLayerTest, PaintBeforePrerollInvalidDisplayListDies) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + auto layer = std::make_shared( + layer_offset, sk_ref_sp(nullptr), false, false); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "display_list_\\.get\\(\\)"); +} + +TEST_F(DisplayListLayerTest, PaintBeforePrerollDies) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + auto layer = std::make_shared( + layer_offset, sk_make_sp(), false, false); + + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(context\\)"); +} + +TEST_F(DisplayListLayerTest, PaintingEmptyLayerDies) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + auto layer = std::make_shared( + layer_offset, sk_make_sp(), false, false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), SkRect::MakeEmpty()); + EXPECT_FALSE(layer->needs_painting(paint_context())); + + EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), + "needs_painting\\(context\\)"); +} + +TEST_F(DisplayListLayerTest, InvalidDisplayListDies) { + const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); + auto layer = std::make_shared( + layer_offset, sk_ref_sp(nullptr), false, false); + + // Crashes reading a nullptr. + EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), ""); +} +#endif + +TEST_F(DisplayListLayerTest, SimpleDisplayList) { + const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f); + const SkMatrix layer_offset_matrix = + SkMatrix::Translate(layer_offset.fX, layer_offset.fY); + const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f); + DisplayListBuilder builder; + builder.drawRect(picture_bounds); + auto display_list = builder.Build(); + auto layer = std::make_shared(layer_offset, display_list, + false, false); + + layer->Preroll(preroll_context(), SkMatrix()); + EXPECT_EQ(layer->paint_bounds(), + picture_bounds.makeOffset(layer_offset.fX, layer_offset.fY)); + EXPECT_EQ(layer->display_list(), display_list.get()); + EXPECT_TRUE(layer->needs_painting(paint_context())); + + layer->Paint(paint_context()); + auto expected_draw_calls = std::vector( + {MockCanvas::DrawCall{0, MockCanvas::SaveData{1}}, + MockCanvas::DrawCall{ + 1, MockCanvas::ConcatMatrixData{SkM44(layer_offset_matrix)}}, +#ifndef SUPPORT_FRACTIONAL_TRANSLATION + MockCanvas::DrawCall{ + 1, MockCanvas::SetMatrixData{SkM44( + RasterCache::GetIntegralTransCTM(layer_offset_matrix))}}, +#endif + MockCanvas::DrawCall{ + 1, MockCanvas::DrawRectData{picture_bounds, SkPaint()}}, + MockCanvas::DrawCall{1, MockCanvas::RestoreData{0}}}); + EXPECT_EQ(mock_canvas().draw_calls(), expected_draw_calls); +} + +#ifdef FLUTTER_ENABLE_DIFF_CONTEXT + +using DisplayListLayerDiffTest = DiffContextTest; + +TEST_F(DisplayListLayerDiffTest, SimpleDisplayList) { + auto display_list = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1); + + MockLayerTree tree1; + tree1.root()->Add(CreateDisplayListLayer(display_list)); + + auto damage = DiffLayerTree(tree1, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 60, 60)); + + MockLayerTree tree2; + tree2.root()->Add(CreateDisplayListLayer(display_list)); + + damage = DiffLayerTree(tree2, tree1); + EXPECT_TRUE(damage.frame_damage.isEmpty()); + + MockLayerTree tree3; + damage = DiffLayerTree(tree3, tree2); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 60, 60)); +} + +TEST_F(DisplayListLayerDiffTest, DisplayListCompare) { + MockLayerTree tree1; + auto display_list1 = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1); + tree1.root()->Add(CreateDisplayListLayer(display_list1)); + + auto damage = DiffLayerTree(tree1, MockLayerTree()); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 60, 60)); + + MockLayerTree tree2; + auto display_list2 = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1); + tree2.root()->Add(CreateDisplayListLayer(display_list2)); + + damage = DiffLayerTree(tree2, tree1); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeEmpty()); + + MockLayerTree tree3; + auto display_list3 = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1); + // add offset + tree3.root()->Add( + CreateDisplayListLayer(display_list3, SkPoint::Make(10, 10))); + + damage = DiffLayerTree(tree3, tree2); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 70, 70)); + + MockLayerTree tree4; + // different color + auto display_list4 = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 2); + tree4.root()->Add( + CreateDisplayListLayer(display_list4, SkPoint::Make(10, 10))); + + damage = DiffLayerTree(tree4, tree3); + EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(20, 20, 70, 70)); +} + +#endif + +} // namespace testing +} // namespace flutter diff --git a/flow/layers/layer.h b/flow/layers/layer.h index ec7c00d6247fb..9ad88e6b7843e 100644 --- a/flow/layers/layer.h +++ b/flow/layers/layer.h @@ -64,6 +64,7 @@ struct PrerollContext { }; class PictureLayer; +class DisplayListLayer; class PerformanceOverlayLayer; class TextureLayer; @@ -239,6 +240,9 @@ class Layer { #ifdef FLUTTER_ENABLE_DIFF_CONTEXT virtual const PictureLayer* as_picture_layer() const { return nullptr; } + virtual const DisplayListLayer* as_display_list_layer() const { + return nullptr; + } virtual const TextureLayer* as_texture_layer() const { return nullptr; } virtual const PerformanceOverlayLayer* as_performance_overlay_layer() const { return nullptr; diff --git a/flow/layers/picture_layer_unittests.cc b/flow/layers/picture_layer_unittests.cc index cdd6e6b75fc2b..dba13d117ca2b 100644 --- a/flow/layers/picture_layer_unittests.cc +++ b/flow/layers/picture_layer_unittests.cc @@ -57,7 +57,6 @@ TEST_F(PictureLayerTest, PaintingEmptyLayerDies) { EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), "needs_painting\\(context\\)"); } -#endif TEST_F(PictureLayerTest, InvalidPictureDies) { const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f); @@ -67,6 +66,7 @@ TEST_F(PictureLayerTest, InvalidPictureDies) { // Crashes reading a nullptr. EXPECT_DEATH_IF_SUPPORTED(layer->Preroll(preroll_context(), SkMatrix()), ""); } +#endif TEST_F(PictureLayerTest, SimplePicture) { const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f); diff --git a/flow/raster_cache.cc b/flow/raster_cache.cc index e748520006724..d1671a1c1ab13 100644 --- a/flow/raster_cache.cc +++ b/flow/raster_cache.cc @@ -56,6 +56,28 @@ static bool CanRasterizePicture(SkPicture* picture) { if (!cull_rect.isFinite()) { // Cannot attempt to rasterize into an infinitely large surface. + FML_LOG(INFO) << "Attempted to raster cache non-finite picture"; + return false; + } + + return true; +} + +static bool CanRasterizeDisplayList(DisplayList* display_list) { + if (display_list == nullptr) { + return false; + } + + const SkRect cull_rect = display_list->bounds(); + + if (cull_rect.isEmpty()) { + // No point in ever rasterizing an empty display list. + return false; + } + + if (!cull_rect.isFinite()) { + // Cannot attempt to rasterize into an infinitely large surface. + FML_LOG(INFO) << "Attempted to raster cache non-finite display list"; return false; } @@ -88,6 +110,32 @@ static bool IsPictureWorthRasterizing(SkPicture* picture, return picture->approximateOpCount() > 5; } +static bool IsDisplayListWorthRasterizing(DisplayList* display_list, + bool will_change, + bool is_complex) { + if (will_change) { + // If the display list is going to change in the future, there is no point + // in doing to extra work to rasterize. + return false; + } + + if (!CanRasterizeDisplayList(display_list)) { + // No point in deciding whether the display list is worth rasterizing if it + // cannot be rasterized at all. + return false; + } + + if (is_complex) { + // The caller seems to have extra information about the display list and + // thinks the display list is always worth rasterizing. + return true; + } + + // TODO(abarth): We should find a better heuristic here that lets us avoid + // wasting memory on trivial layers that are easy to re-rasterize every frame. + return display_list->op_count() > 5; +} + /// @note Procedure doesn't copy all closures. static std::unique_ptr Rasterize( GrDirectContext* context, @@ -136,6 +184,17 @@ std::unique_ptr RasterCache::RasterizePicture( [=](SkCanvas* canvas) { canvas->drawPicture(picture); }); } +std::unique_ptr RasterCache::RasterizeDisplayList( + DisplayList* display_list, + GrDirectContext* context, + const SkMatrix& ctm, + SkColorSpace* dst_color_space, + bool checkerboard) const { + return Rasterize(context, ctm, dst_color_space, checkerboard, + display_list->bounds(), + [=](SkCanvas* canvas) { display_list->RenderTo(canvas); }); +} + void RasterCache::Prepare(PrerollContext* context, Layer* layer, const SkMatrix& ctm) { @@ -223,6 +282,52 @@ bool RasterCache::Prepare(GrDirectContext* context, return true; } +bool RasterCache::Prepare(GrDirectContext* context, + DisplayList* display_list, + const SkMatrix& transformation_matrix, + SkColorSpace* dst_color_space, + bool is_complex, + bool will_change) { + // Disabling caching when access_threshold is zero is historic behavior. + if (access_threshold_ == 0) { + return false; + } + if (picture_cached_this_frame_ >= picture_cache_limit_per_frame_) { + return false; + } + if (!IsDisplayListWorthRasterizing(display_list, will_change, is_complex)) { + // We only deal with display lists that are worthy of rasterization. + return false; + } + + // Decompose the matrix (once) for all subsequent operations. We want to make + // sure to avoid volumetric distortions while accounting for scaling. + const MatrixDecomposition matrix(transformation_matrix); + + if (!matrix.IsValid()) { + // The matrix was singular. No point in going further. + return false; + } + + DisplayListRasterCacheKey cache_key(display_list->unique_id(), + transformation_matrix); + + // Creates an entry, if not present prior. + Entry& entry = display_list_cache_[cache_key]; + if (entry.access_count < access_threshold_) { + // Frame threshold has not yet been reached. + return false; + } + + if (!entry.image) { + entry.image = + RasterizeDisplayList(display_list, context, transformation_matrix, + dst_color_space, checkerboard_images_); + picture_cached_this_frame_++; + } + return true; +} + bool RasterCache::Draw(const SkPicture& picture, SkCanvas& canvas) const { PictureRasterCacheKey cache_key(picture.uniqueID(), canvas.getTotalMatrix()); auto it = picture_cache_.find(cache_key); @@ -242,6 +347,27 @@ bool RasterCache::Draw(const SkPicture& picture, SkCanvas& canvas) const { return false; } +bool RasterCache::Draw(const DisplayList& display_list, + SkCanvas& canvas) const { + DisplayListRasterCacheKey cache_key(display_list.unique_id(), + canvas.getTotalMatrix()); + auto it = display_list_cache_.find(cache_key); + if (it == display_list_cache_.end()) { + return false; + } + + Entry& entry = it->second; + entry.access_count++; + entry.used_this_frame = true; + + if (entry.image) { + entry.image->draw(canvas, nullptr); + return true; + } + + return false; +} + bool RasterCache::Draw(const Layer* layer, SkCanvas& canvas, SkPaint* paint) const { @@ -265,6 +391,7 @@ bool RasterCache::Draw(const Layer* layer, void RasterCache::SweepAfterFrame() { SweepOneCacheAfterFrame(picture_cache_); + SweepOneCacheAfterFrame(display_list_cache_); SweepOneCacheAfterFrame(layer_cache_); picture_cached_this_frame_ = 0; TraceStatsToTimeline(); @@ -272,11 +399,13 @@ void RasterCache::SweepAfterFrame() { void RasterCache::Clear() { picture_cache_.clear(); + display_list_cache_.clear(); layer_cache_.clear(); } size_t RasterCache::GetCachedEntriesCount() const { - return layer_cache_.size() + picture_cache_.size(); + return layer_cache_.size() + picture_cache_.size() + + display_list_cache_.size(); } size_t RasterCache::GetLayerCachedEntriesCount() const { @@ -287,6 +416,10 @@ size_t RasterCache::GetPictureCachedEntriesCount() const { return picture_cache_.size(); } +size_t RasterCache::GetDisplayListCachedEntriesCount() const { + return display_list_cache_.size(); +} + void RasterCache::SetCheckboardCacheImages(bool checkerboard) { if (checkerboard_images_ == checkerboard) { return; @@ -305,7 +438,10 @@ void RasterCache::TraceStatsToTimeline() const { "LayerCount", layer_cache_.size(), "LayerMBytes", EstimateLayerCacheByteSize() / kMegaByteSizeInBytes, "PictureCount", picture_cache_.size(), "PictureMBytes", - EstimatePictureCacheByteSize() / kMegaByteSizeInBytes); + EstimatePictureCacheByteSize() / kMegaByteSizeInBytes, + "DisplayListCount", display_list_cache_.size(), + "DisplayListMBytes", + EstimateDisplayListCacheByteSize() / kMegaByteSizeInBytes); #endif // !FLUTTER_RELEASE } @@ -330,4 +466,14 @@ size_t RasterCache::EstimatePictureCacheByteSize() const { return picture_cache_bytes; } +size_t RasterCache::EstimateDisplayListCacheByteSize() const { + size_t display_list_cache_bytes = 0; + for (const auto& item : display_list_cache_) { + if (item.second.image) { + display_list_cache_bytes += item.second.image->image_bytes(); + } + } + return display_list_cache_bytes; +} + } // namespace flutter diff --git a/flow/raster_cache.h b/flow/raster_cache.h index e2b317cb55d11..a440ade7919d9 100644 --- a/flow/raster_cache.h +++ b/flow/raster_cache.h @@ -8,6 +8,7 @@ #include #include +#include "flutter/flow/display_list.h" #include "flutter/flow/raster_cache_key.h" #include "flutter/fml/macros.h" #include "flutter/fml/memory/weak_ptr.h" @@ -74,6 +75,12 @@ class RasterCache { const SkMatrix& ctm, SkColorSpace* dst_color_space, bool checkerboard) const; + virtual std::unique_ptr RasterizeDisplayList( + DisplayList* display_list, + GrDirectContext* context, + const SkMatrix& ctm, + SkColorSpace* dst_color_space, + bool checkerboard) const; /** * @brief Rasterize an engine Layer and produce a RasterCacheResult @@ -138,6 +145,12 @@ class RasterCache { SkColorSpace* dst_color_space, bool is_complex, bool will_change); + bool Prepare(GrDirectContext* context, + DisplayList* display_list, + const SkMatrix& transformation_matrix, + SkColorSpace* dst_color_space, + bool is_complex, + bool will_change); void Prepare(PrerollContext* context, Layer* layer, const SkMatrix& ctm); @@ -146,6 +159,11 @@ class RasterCache { // Return true if it's found and drawn. bool Draw(const SkPicture& picture, SkCanvas& canvas) const; + // Find the raster cache for the display list and draw it to the canvas. + // + // Return true if it's found and drawn. + bool Draw(const DisplayList& display_list, SkCanvas& canvas) const; + // Find the raster cache for the layer and draw it to the canvas. // // Additional paint can be given to change how the raster cache is drawn @@ -168,6 +186,8 @@ class RasterCache { size_t GetPictureCachedEntriesCount() const; + size_t GetDisplayListCachedEntriesCount() const; + /** * @brief Estimate how much memory is used by picture raster cache entries in * bytes. @@ -178,6 +198,16 @@ class RasterCache { */ size_t EstimatePictureCacheByteSize() const; + /** + * @brief Estimate how much memory is used by display list raster cache + * entries in bytes. + * + * Only SkImage's memory usage is counted as other objects are often much + * smaller compared to SkImage. SkImageInfo::computeMinByteSize is used to + * estimate the SkImage memory usage. + */ + size_t EstimateDisplayListCacheByteSize() const; + /** * @brief Estimate how much memory is used by layer raster cache entries in * bytes. @@ -216,6 +246,7 @@ class RasterCache { const size_t picture_cache_limit_per_frame_; size_t picture_cached_this_frame_ = 0; mutable PictureRasterCacheKey::Map picture_cache_; + mutable DisplayListRasterCacheKey::Map display_list_cache_; mutable LayerRasterCacheKey::Map layer_cache_; bool checkerboard_images_; diff --git a/flow/raster_cache_key.h b/flow/raster_cache_key.h index cc4c5fe2b0c7d..21d6bfe3dfdcb 100644 --- a/flow/raster_cache_key.h +++ b/flow/raster_cache_key.h @@ -52,6 +52,9 @@ class RasterCacheKey { // The ID is the uint32_t picture uniqueID using PictureRasterCacheKey = RasterCacheKey; +// The ID is the uint32_t DisplayList uniqueID +using DisplayListRasterCacheKey = RasterCacheKey; + class Layer; // The ID is the uint64_t layer unique_id diff --git a/flow/testing/diff_context_test.cc b/flow/testing/diff_context_test.cc index 1321c617ef12a..74f26f780c659 100644 --- a/flow/testing/diff_context_test.cc +++ b/flow/testing/diff_context_test.cc @@ -42,6 +42,20 @@ std::shared_ptr DiffContextTest::CreatePictureLayer( offset, SkiaGPUObject(picture, unref_queue()), false, false); } +sk_sp DiffContextTest::CreateDisplayList(const SkRect& bounds, + SkColor color) { + DisplayListBuilder builder; + builder.setColor(color); + builder.drawRect(bounds); + return builder.Build(); +} + +std::shared_ptr DiffContextTest::CreateDisplayListLayer( + sk_sp display_list, + const SkPoint& offset) { + return std::make_shared(offset, display_list, false, false); +} + std::shared_ptr DiffContextTest::CreateContainerLayer( std::initializer_list> layers) { auto res = std::make_shared(); diff --git a/flow/testing/diff_context_test.h b/flow/testing/diff_context_test.h index 5c5b574b735ac..75341146d98e6 100644 --- a/flow/testing/diff_context_test.h +++ b/flow/testing/diff_context_test.h @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/layers/display_list_layer.h" #include "flutter/flow/layers/picture_layer.h" #include "flutter/flow/testing/skia_gpu_object_layer_test.h" #include "third_party/skia/include/core/SkPicture.h" @@ -48,6 +49,14 @@ class DiffContextTest : public ThreadTest { sk_sp picture, const SkPoint& offset = SkPoint::Make(0, 0)); + // Create display list consisting of filled rect with given color; Being able + // to specify different color is useful to test deep comparison of pictures + sk_sp CreateDisplayList(const SkRect& bounds, uint32_t color); + + std::shared_ptr CreateDisplayListLayer( + sk_sp display_list, + const SkPoint& offset = SkPoint::Make(0, 0)); + std::shared_ptr CreateContainerLayer( std::initializer_list> layers); diff --git a/lib/ui/compositing/scene_builder.cc b/lib/ui/compositing/scene_builder.cc index c3e5377287697..e7c2b3ea151cc 100644 --- a/lib/ui/compositing/scene_builder.cc +++ b/lib/ui/compositing/scene_builder.cc @@ -10,6 +10,7 @@ #include "flutter/flow/layers/clip_rrect_layer.h" #include "flutter/flow/layers/color_filter_layer.h" #include "flutter/flow/layers/container_layer.h" +#include "flutter/flow/layers/display_list_layer.h" #include "flutter/flow/layers/image_filter_layer.h" #include "flutter/flow/layers/layer.h" #include "flutter/flow/layers/layer_tree.h" @@ -265,10 +266,17 @@ void SceneBuilder::addPicture(double dx, double dy, Picture* picture, int hints) { - auto layer = std::make_unique( - SkPoint::Make(dx, dy), UIDartState::CreateGPUObject(picture->picture()), - !!(hints & 1), !!(hints & 2)); - AddLayer(std::move(layer)); + if (picture->picture()) { + auto layer = std::make_unique( + SkPoint::Make(dx, dy), UIDartState::CreateGPUObject(picture->picture()), + !!(hints & 1), !!(hints & 2)); + AddLayer(std::move(layer)); + } else { + auto layer = std::make_unique( + SkPoint::Make(dx, dy), picture->display_list(), !!(hints & 1), + !!(hints & 2)); + AddLayer(std::move(layer)); + } } void SceneBuilder::addTexture(double dx, diff --git a/lib/ui/painting/canvas.cc b/lib/ui/painting/canvas.cc index 728540c2d4dd4..fee3127f06dbe 100644 --- a/lib/ui/painting/canvas.cc +++ b/lib/ui/painting/canvas.cc @@ -85,6 +85,7 @@ fml::RefPtr Canvas::Create(PictureRecorder* recorder, fml::RefPtr canvas = fml::MakeRefCounted( recorder->BeginRecording(SkRect::MakeLTRB(left, top, right, bottom))); recorder->set_canvas(canvas); + canvas->display_list_recorder_ = recorder->display_list_recorder(); return canvas; } @@ -383,8 +384,19 @@ void Canvas::drawImageNine(const CanvasImage* image, center.round(&icenter); SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom); auto filter = ImageFilter::FilterModeFromIndex(bitmapSamplingIndex); - canvas_->drawImageNine(image->image().get(), icenter, dst, filter, - paint.paint()); + if (display_list_recorder_) { + // SkCanvas turns a simple 2-rect DrawImageNine operation into a + // drawImageLattice operation which has arrays to allocate and + // pass along. For simplicity, we will bypass the canvas and ask + // the recorder to record our paint attributes and record a much + // simpler DrawImageNineOp record directly. + display_list_recorder_->RecordPaintAttributes( + paint.paint(), DisplayListCanvasRecorder::DrawType::kImageOpType); + builder()->drawImageNine(image->image(), icenter, dst, filter); + } else { + canvas_->drawImageNine(image->image().get(), icenter, dst, filter, + paint.paint()); + } } void Canvas::drawPicture(Picture* picture) { @@ -396,7 +408,17 @@ void Canvas::drawPicture(Picture* picture) { ToDart("Canvas.drawPicture called with non-genuine Picture.")); return; } - canvas_->drawPicture(picture->picture().get()); + if (picture->picture()) { + canvas_->drawPicture(picture->picture().get()); + } else if (picture->display_list()) { + if (display_list_recorder_) { + builder()->drawDisplayList(picture->display_list()); + } else { + picture->display_list()->RenderTo(canvas_); + } + } else { + FML_DCHECK(false); + } } void Canvas::drawPoints(const Paint& paint, @@ -477,13 +499,24 @@ void Canvas::drawShadow(const CanvasPath* path, ToDart("Canvas.drawShader called with non-genuine Path.")); return; } - SkScalar dpr = UIDartState::Current() - ->platform_configuration() - ->get_window(0) - ->viewport_metrics() - .device_pixel_ratio; - flutter::PhysicalShapeLayer::DrawShadow(canvas_, path->path(), color, - elevation, transparentOccluder, dpr); + if (display_list_recorder_) { + // The DrawShadow mechanism results in non-public operations to be + // performed on the canvas involving an SkDrawShadowRec. Since we + // cannot include the header that defines that structure, we cannot + // record an operation that it injects into an SkCanvas. To prevent + // that situation we bypass the canvas interface and inject the + // shadow parameters directly into the underlying DisplayList. + // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125 + builder()->drawShadow(path->path(), color, elevation, transparentOccluder); + } else { + SkScalar dpr = UIDartState::Current() + ->platform_configuration() + ->get_window(0) + ->viewport_metrics() + .device_pixel_ratio; + flutter::PhysicalShapeLayer::DrawShadow( + canvas_, path->path(), color, elevation, transparentOccluder, dpr); + } } void Canvas::Invalidate() { diff --git a/lib/ui/painting/canvas.h b/lib/ui/painting/canvas.h index cf067d2b3f95a..ca328d310dbb6 100644 --- a/lib/ui/painting/canvas.h +++ b/lib/ui/painting/canvas.h @@ -180,6 +180,17 @@ class Canvas : public RefCountedDartWrappable { // which does not transfer ownership. For this reason, we hold a raw // pointer and manually set to null in Clear. SkCanvas* canvas_; + + // A copy of the recorder used by the SkCanvas->DisplayList adapter for cases + // where we cannot record the SkCanvas method call through the various OnOp() + // virtual methods or where we can be more efficient by talking directly in + // the DisplayList operation lexicon. The recorder has a method for recording + // paint attributes from an SkPaint and an operation type as well as access + // to the raw DisplayListBuilder for emitting custom rendering operations. + sk_sp display_list_recorder_; + sk_sp builder() { + return display_list_recorder_->builder(); + } }; } // namespace flutter diff --git a/lib/ui/painting/image_dispose_unittests.cc b/lib/ui/painting/image_dispose_unittests.cc index 245d0a58ee631..3a673328932ea 100644 --- a/lib/ui/painting/image_dispose_unittests.cc +++ b/lib/ui/painting/image_dispose_unittests.cc @@ -6,8 +6,10 @@ #include "flutter/common/task_runners.h" #include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/lib/ui/painting/canvas.h" #include "flutter/lib/ui/painting/image.h" #include "flutter/lib/ui/painting/picture.h" +#include "flutter/lib/ui/painting/picture_recorder.h" #include "flutter/runtime/dart_vm.h" #include "flutter/shell/common/shell_test.h" #include "flutter/shell/common/thread_host.h" @@ -31,6 +33,7 @@ class ImageDisposeTest : public ShellTest { fml::AutoResetWaitableEvent message_latch_; sk_sp current_picture_; + sk_sp current_display_list_; sk_sp current_image_; }; @@ -45,9 +48,14 @@ TEST_F(ImageDisposeTest, ImageReleasedAfterFrameAndDisposePictureAndLayer) { CanvasImage* image = GetNativePeer(native_image_handle); Picture* picture = GetNativePeer(Dart_GetNativeArgument(args, 1)); ASSERT_FALSE(image->image()->unique()); - ASSERT_FALSE(picture->picture()->unique()); + if (picture->display_list()) { + ASSERT_FALSE(picture->display_list()->unique()); + current_display_list_ = picture->display_list(); + } else { + ASSERT_FALSE(picture->picture()->unique()); + current_picture_ = picture->picture(); + } current_image_ = image->image(); - current_picture_ = picture->picture(); }; auto native_finish = [&](Dart_NativeArguments args) { @@ -84,7 +92,7 @@ TEST_F(ImageDisposeTest, ImageReleasedAfterFrameAndDisposePictureAndLayer) { message_latch_.Wait(); - ASSERT_TRUE(current_picture_); + ASSERT_TRUE(current_display_list_ || current_picture_); ASSERT_TRUE(current_image_); // Force a drain the SkiaUnrefQueue. The engine does this normally as frames @@ -96,8 +104,13 @@ TEST_F(ImageDisposeTest, ImageReleasedAfterFrameAndDisposePictureAndLayer) { }); message_latch_.Wait(); - EXPECT_TRUE(current_picture_->unique()); - current_picture_.reset(); + if (current_display_list_) { + EXPECT_TRUE(current_display_list_->unique()); + current_display_list_.reset(); + } else { + EXPECT_TRUE(current_picture_->unique()); + current_picture_.reset(); + } EXPECT_TRUE(current_image_->unique()); current_image_.reset(); diff --git a/lib/ui/painting/picture.cc b/lib/ui/painting/picture.cc index 5225d993ba4fb..2789d2eec6720 100644 --- a/lib/ui/painting/picture.cc +++ b/lib/ui/painting/picture.cc @@ -37,29 +37,50 @@ fml::RefPtr Picture::Create( return canvas_picture; } +fml::RefPtr Picture::Create(Dart_Handle dart_handle, + sk_sp display_list) { + auto canvas_picture = fml::MakeRefCounted(std::move(display_list)); + + canvas_picture->AssociateWithDartWrapper(dart_handle); + return canvas_picture; +} + Picture::Picture(flutter::SkiaGPUObject picture) : picture_(std::move(picture)) {} +Picture::Picture(sk_sp display_list) + : display_list_(std::move(display_list)) {} + Picture::~Picture() = default; Dart_Handle Picture::toImage(uint32_t width, uint32_t height, Dart_Handle raw_image_callback) { - if (!picture_.get()) { - return tonic::ToDart("Picture is null"); + if (display_list_) { + return RasterizeToImage( + [display_list = display_list_.get()](SkCanvas* canvas) { + display_list->RenderTo(canvas); + }, + width, height, raw_image_callback); + } else { + if (!picture_.get()) { + return tonic::ToDart("Picture is null"); + } + return RasterizeToImage(picture_.get(), width, height, raw_image_callback); } - - return RasterizeToImage(picture_.get(), width, height, raw_image_callback); } void Picture::dispose() { picture_.reset(); + display_list_.reset(); ClearDartWrapper(); } size_t Picture::GetAllocationSize() const { if (auto picture = picture_.get()) { return picture->approximateBytesUsed() + sizeof(Picture); + } else if (auto display_list = display_list_.get()) { + return display_list_->bytes() + sizeof(Picture); } else { return sizeof(Picture); } @@ -69,6 +90,18 @@ Dart_Handle Picture::RasterizeToImage(sk_sp picture, uint32_t width, uint32_t height, Dart_Handle raw_image_callback) { + return RasterizeToImage( + [sk_picture = picture.get()](SkCanvas* canvas) { + canvas->drawPicture(sk_picture); + }, + width, height, raw_image_callback); +} + +Dart_Handle Picture::RasterizeToImage( + std::function draw_callback, + uint32_t width, + uint32_t height, + Dart_Handle raw_image_callback) { if (Dart_IsNull(raw_image_callback) || !Dart_IsClosure(raw_image_callback)) { return tonic::ToDart("Image callback was invalid"); } @@ -121,10 +154,10 @@ Dart_Handle Picture::RasterizeToImage(sk_sp picture, // Kick things off on the raster rask runner. fml::TaskRunner::RunNowOrPostTask( - raster_task_runner, - [ui_task_runner, snapshot_delegate, picture, picture_bounds, ui_task] { - sk_sp raster_image = - snapshot_delegate->MakeRasterSnapshot(picture, picture_bounds); + raster_task_runner, [ui_task_runner, snapshot_delegate, draw_callback, + picture_bounds, ui_task] { + sk_sp raster_image = snapshot_delegate->MakeRasterSnapshot( + draw_callback, picture_bounds); fml::TaskRunner::RunNowOrPostTask( ui_task_runner, diff --git a/lib/ui/painting/picture.h b/lib/ui/painting/picture.h index e0158d400ff5e..432d9d32fb1b4 100644 --- a/lib/ui/painting/picture.h +++ b/lib/ui/painting/picture.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_LIB_UI_PAINTING_PICTURE_H_ #define FLUTTER_LIB_UI_PAINTING_PICTURE_H_ +#include "flutter/flow/display_list.h" #include "flutter/flow/skia_gpu_object.h" #include "flutter/lib/ui/dart_wrapper.h" #include "flutter/lib/ui/painting/image.h" @@ -26,8 +27,11 @@ class Picture : public RefCountedDartWrappable { ~Picture() override; static fml::RefPtr Create(Dart_Handle dart_handle, flutter::SkiaGPUObject picture); + static fml::RefPtr Create(Dart_Handle dart_handle, + sk_sp display_list); sk_sp picture() const { return picture_.get(); } + sk_sp display_list() const { return display_list_; } Dart_Handle toImage(uint32_t width, uint32_t height, @@ -44,10 +48,18 @@ class Picture : public RefCountedDartWrappable { uint32_t height, Dart_Handle raw_image_callback); + static Dart_Handle RasterizeToImage( + std::function draw_callback, + uint32_t width, + uint32_t height, + Dart_Handle raw_image_callback); + private: Picture(flutter::SkiaGPUObject picture); + Picture(sk_sp display_list); flutter::SkiaGPUObject picture_; + sk_sp display_list_; }; } // namespace flutter diff --git a/lib/ui/painting/picture_recorder.cc b/lib/ui/painting/picture_recorder.cc index 5084f30d7812f..ed6f88cfeda78 100644 --- a/lib/ui/painting/picture_recorder.cc +++ b/lib/ui/painting/picture_recorder.cc @@ -39,7 +39,13 @@ PictureRecorder::PictureRecorder() {} PictureRecorder::~PictureRecorder() {} SkCanvas* PictureRecorder::BeginRecording(SkRect bounds) { - return picture_recorder_.beginRecording(bounds, &rtree_factory_); + bool enable_display_list = UIDartState::Current()->enable_display_list(); + if (enable_display_list) { + display_list_recorder_ = sk_make_sp(bounds); + return display_list_recorder_.get(); + } else { + return picture_recorder_.beginRecording(bounds, &rtree_factory_); + } } fml::RefPtr PictureRecorder::endRecording(Dart_Handle dart_picture) { @@ -47,9 +53,16 @@ fml::RefPtr PictureRecorder::endRecording(Dart_Handle dart_picture) { return nullptr; } - fml::RefPtr picture = Picture::Create( - dart_picture, UIDartState::CreateGPUObject( - picture_recorder_.finishRecordingAsPicture())); + fml::RefPtr picture; + + if (display_list_recorder_) { + picture = Picture::Create(dart_picture, display_list_recorder_->Build()); + display_list_recorder_ = nullptr; + } else { + picture = Picture::Create( + dart_picture, UIDartState::CreateGPUObject( + picture_recorder_.finishRecordingAsPicture())); + } canvas_->Invalidate(); canvas_ = nullptr; diff --git a/lib/ui/painting/picture_recorder.h b/lib/ui/painting/picture_recorder.h index 59d0d782c57ae..7d2303284d32b 100644 --- a/lib/ui/painting/picture_recorder.h +++ b/lib/ui/painting/picture_recorder.h @@ -5,6 +5,7 @@ #ifndef FLUTTER_LIB_UI_PAINTING_PICTURE_RECORDER_H_ #define FLUTTER_LIB_UI_PAINTING_PICTURE_RECORDER_H_ +#include "flutter/flow/display_list_canvas.h" #include "flutter/lib/ui/dart_wrapper.h" #include "third_party/skia/include/core/SkPictureRecorder.h" @@ -28,6 +29,10 @@ class PictureRecorder : public RefCountedDartWrappable { SkCanvas* BeginRecording(SkRect bounds); fml::RefPtr endRecording(Dart_Handle dart_picture); + sk_sp display_list_recorder() { + return display_list_recorder_; + } + void set_canvas(fml::RefPtr canvas) { canvas_ = std::move(canvas); } static void RegisterNatives(tonic::DartLibraryNatives* natives); @@ -37,6 +42,9 @@ class PictureRecorder : public RefCountedDartWrappable { SkRTreeFactory rtree_factory_; SkPictureRecorder picture_recorder_; + + sk_sp display_list_recorder_; + fml::RefPtr canvas_; }; diff --git a/lib/ui/snapshot_delegate.h b/lib/ui/snapshot_delegate.h index ad9b8ef1f3612..a1f7c618b0653 100644 --- a/lib/ui/snapshot_delegate.h +++ b/lib/ui/snapshot_delegate.h @@ -12,6 +12,10 @@ namespace flutter { class SnapshotDelegate { public: + virtual sk_sp MakeRasterSnapshot( + std::function draw_callback, + SkISize picture_size) = 0; + virtual sk_sp MakeRasterSnapshot(sk_sp picture, SkISize picture_size) = 0; diff --git a/lib/ui/ui_dart_state.cc b/lib/ui/ui_dart_state.cc index 71d02c7b73b9f..06411c4d3ce98 100644 --- a/lib/ui/ui_dart_state.cc +++ b/lib/ui/ui_dart_state.cc @@ -56,6 +56,7 @@ UIDartState::UIDartState( std::shared_ptr isolate_name_server, bool is_root_isolate, bool enable_skparagraph, + bool enable_display_list, const UIDartState::Context& context) : add_callback_(std::move(add_callback)), remove_callback_(std::move(remove_callback)), @@ -65,6 +66,7 @@ UIDartState::UIDartState( log_message_callback_(log_message_callback), isolate_name_server_(std::move(isolate_name_server)), enable_skparagraph_(enable_skparagraph), + enable_display_list_(enable_display_list), context_(std::move(context)) { AddOrRemoveTaskObserver(true /* add */); } @@ -238,4 +240,8 @@ bool UIDartState::enable_skparagraph() const { return enable_skparagraph_; } +bool UIDartState::enable_display_list() const { + return enable_display_list_; +} + } // namespace flutter diff --git a/lib/ui/ui_dart_state.h b/lib/ui/ui_dart_state.h index fb2baadb1aa2f..f79d584ad0090 100644 --- a/lib/ui/ui_dart_state.h +++ b/lib/ui/ui_dart_state.h @@ -143,6 +143,8 @@ class UIDartState : public tonic::DartState { bool enable_skparagraph() const; + bool enable_display_list() const; + template static flutter::SkiaGPUObject CreateGPUObject(sk_sp object) { if (!object) { @@ -163,6 +165,7 @@ class UIDartState : public tonic::DartState { std::shared_ptr isolate_name_server, bool is_root_isolate_, bool enable_skparagraph, + bool enable_display_list, const UIDartState::Context& context); ~UIDartState() override; @@ -189,6 +192,7 @@ class UIDartState : public tonic::DartState { LogMessageCallback log_message_callback_; const std::shared_ptr isolate_name_server_; const bool enable_skparagraph_; + const bool enable_display_list_; UIDartState::Context context_; void AddOrRemoveTaskObserver(bool add); diff --git a/runtime/dart_isolate.cc b/runtime/dart_isolate.cc index 52aa2ffb4d3ab..916ce6e72ad1c 100644 --- a/runtime/dart_isolate.cc +++ b/runtime/dart_isolate.cc @@ -311,6 +311,7 @@ DartIsolate::DartIsolate(const Settings& settings, DartVMRef::GetIsolateNameServer(), is_root_isolate, settings.enable_skparagraph, + settings.enable_display_list, std::move(context)), may_insecurely_connect_to_all_domains_( settings.may_insecurely_connect_to_all_domains), diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc index 794095d308f2e..3c6ba5fbf2385 100644 --- a/shell/common/rasterizer.cc +++ b/shell/common/rasterizer.cc @@ -313,6 +313,12 @@ sk_sp Rasterizer::DoMakeRasterSnapshot( return result; } +sk_sp Rasterizer::MakeRasterSnapshot( + std::function draw_callback, + SkISize picture_size) { + return DoMakeRasterSnapshot(picture_size, draw_callback); +} + sk_sp Rasterizer::MakeRasterSnapshot(sk_sp picture, SkISize picture_size) { return DoMakeRasterSnapshot(picture_size, diff --git a/shell/common/rasterizer.h b/shell/common/rasterizer.h index b34e10b0b0d74..aa7334179e970 100644 --- a/shell/common/rasterizer.h +++ b/shell/common/rasterizer.h @@ -449,6 +449,11 @@ class Rasterizer final : public SnapshotDelegate { std::shared_ptr external_view_embedder_; bool shared_engine_block_thread_merging_ = false; + // |SnapshotDelegate| + sk_sp MakeRasterSnapshot( + std::function draw_callback, + SkISize picture_size) override; + // |SnapshotDelegate| sk_sp MakeRasterSnapshot(sk_sp picture, SkISize picture_size) override; diff --git a/shell/common/shell.cc b/shell/common/shell.cc index e423a1759689c..beea55e86736e 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1690,6 +1690,9 @@ bool Shell::OnServiceProtocolEstimateRasterCacheMemory( response->AddMember("pictureBytes", raster_cache.EstimatePictureCacheByteSize(), response->GetAllocator()); + response->AddMember("displayListBytes", + raster_cache.EstimateDisplayListCacheByteSize(), + response->GetAllocator()); return true; } diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 221d32f3eb223..48c27a1b77f3a 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -2110,7 +2110,7 @@ TEST_F(ShellTest, OnServiceProtocolEstimateRasterCacheMemoryWorks) { document.Accept(writer); std::string expected_json = "{\"type\":\"EstimateRasterCacheMemory\",\"layerBytes\":40000,\"picture" - "Bytes\":400}"; + "Bytes\":400,\"displayListBytes\":0}"; std::string actual_json = buffer.GetString(); ASSERT_EQ(actual_json, expected_json); diff --git a/shell/common/switches.cc b/shell/common/switches.cc index 249e9c5d8dca1..d49f99bda13be 100644 --- a/shell/common/switches.cc +++ b/shell/common/switches.cc @@ -66,6 +66,8 @@ static const std::string gAllowedDartFlags[] = { "--write-service-info", "--null_assertions", "--strict_null_safety_checks", + "--enable-display-list", + "--no-enable-display-list", }; // clang-format on @@ -404,6 +406,16 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { settings.dart_flags.push_back(flag); } } + if (std::find(settings.dart_flags.begin(), settings.dart_flags.end(), + "--enable-display-list") != settings.dart_flags.end()) { + FML_LOG(ERROR) << "Manually enabling display lists"; + settings.enable_display_list = true; + } else if (std::find(settings.dart_flags.begin(), settings.dart_flags.end(), + "--no-enable-display-list") != + settings.dart_flags.end()) { + FML_LOG(ERROR) << "Manually disabling display lists"; + settings.enable_display_list = false; + } #if !FLUTTER_RELEASE command_line.GetOptionValue(FlagForSwitch(Switch::LogTag), &settings.log_tag); diff --git a/testing/mock_canvas.cc b/testing/mock_canvas.cc index 518f52c89c035..9084563973dc6 100644 --- a/testing/mock_canvas.cc +++ b/testing/mock_canvas.cc @@ -105,6 +105,7 @@ void MockCanvas::onDrawPath(const SkPath& path, const SkPaint& paint) { void MockCanvas::onDrawShadowRec(const SkPath& path, const SkDrawShadowRec& rec) { + // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125 (void)rec; // Can't use b/c Skia keeps this type anonymous. draw_calls_.emplace_back(DrawCall{current_layer_, DrawShadowData{path}}); }