From 1a816e8c9994709af68bd349e8c0b3deaa36ff49 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Tue, 16 Nov 2021 14:18:04 -0800 Subject: [PATCH] Call DisplayList builder methods directly from dart canvas (#29714) --- flow/display_list.cc | 348 ++- flow/display_list.h | 476 +++- flow/display_list_canvas.cc | 155 +- flow/display_list_canvas.h | 81 +- flow/display_list_canvas_unittests.cc | 3449 +++++++++++++++---------- flow/display_list_unittests.cc | 329 ++- flow/display_list_utils.cc | 157 +- flow/display_list_utils.h | 72 +- lib/ui/painting/canvas.cc | 407 ++- lib/ui/painting/canvas.h | 6 +- lib/ui/painting/paint.cc | 182 +- lib/ui/painting/paint.h | 20 +- lib/ui/text/paragraph_builder.cc | 10 +- 13 files changed, 3665 insertions(+), 2027 deletions(-) diff --git a/flow/display_list.cc b/flow/display_list.cc index 3e3806dab66a..5f0845e14f64 100644 --- a/flow/display_list.cc +++ b/flow/display_list.cc @@ -7,10 +7,7 @@ #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" @@ -1085,65 +1082,85 @@ DisplayListBuilder::~DisplayListBuilder() { } } -void DisplayListBuilder::setAntiAlias(bool aa) { - Push(0, 0, aa); +void DisplayListBuilder::onSetAntiAlias(bool aa) { + Push(0, 0, current_anti_alias_ = aa); } -void DisplayListBuilder::setDither(bool dither) { - Push(0, 0, dither); +void DisplayListBuilder::onSetDither(bool dither) { + Push(0, 0, current_dither_ = dither); } -void DisplayListBuilder::setInvertColors(bool invert) { - Push(0, 0, invert); +void DisplayListBuilder::onSetInvertColors(bool invert) { + Push(0, 0, current_invert_colors_ = invert); } -void DisplayListBuilder::setStrokeCap(SkPaint::Cap cap) { - Push(0, 0, cap); +void DisplayListBuilder::onSetStrokeCap(SkPaint::Cap cap) { + Push(0, 0, current_stroke_cap_ = cap); } -void DisplayListBuilder::setStrokeJoin(SkPaint::Join join) { - Push(0, 0, join); +void DisplayListBuilder::onSetStrokeJoin(SkPaint::Join join) { + Push(0, 0, current_stroke_join_ = join); } -void DisplayListBuilder::setStyle(SkPaint::Style style) { - Push(0, 0, style); +void DisplayListBuilder::onSetStyle(SkPaint::Style style) { + Push(0, 0, current_style_ = style); } -void DisplayListBuilder::setStrokeWidth(SkScalar width) { - Push(0, 0, width); +void DisplayListBuilder::onSetStrokeWidth(SkScalar width) { + Push(0, 0, current_stroke_width_ = width); } -void DisplayListBuilder::setStrokeMiter(SkScalar limit) { - Push(0, 0, limit); +void DisplayListBuilder::onSetStrokeMiter(SkScalar limit) { + Push(0, 0, current_stroke_miter_ = limit); } -void DisplayListBuilder::setColor(SkColor color) { - Push(0, 0, color); +void DisplayListBuilder::onSetColor(SkColor color) { + Push(0, 0, current_color_ = color); } -void DisplayListBuilder::setBlendMode(SkBlendMode mode) { - Push(0, 0, mode); +void DisplayListBuilder::onSetBlendMode(SkBlendMode mode) { + current_blender_ = nullptr; + Push(0, 0, current_blend_mode_ = mode); } -void DisplayListBuilder::setBlender(sk_sp blender) { - blender // - ? Push(0, 0, std::move(blender)) - : Push(0, 0); +void DisplayListBuilder::onSetBlender(sk_sp blender) { + // setBlender(nullptr) should be redirected to setBlendMode(SrcOver) + // by the set method, if not then the following is inefficient but works + FML_DCHECK(blender); + SkPaint p; + p.setBlender(blender); + if (p.asBlendMode()) { + setBlendMode(p.asBlendMode().value()); + } else { + // |current_blender_| supersedes any value of |current_blend_mode_| + (current_blender_ = blender) // + ? Push(0, 0, std::move(blender)) + : Push(0, 0); + } } -void DisplayListBuilder::setShader(sk_sp shader) { - shader // +void DisplayListBuilder::onSetShader(sk_sp shader) { + (current_shader_ = shader) // ? Push(0, 0, std::move(shader)) : Push(0, 0); } -void DisplayListBuilder::setImageFilter(sk_sp filter) { - filter // +void DisplayListBuilder::onSetImageFilter(sk_sp filter) { + (current_image_filter_ = filter) // ? Push(0, 0, std::move(filter)) : Push(0, 0); } -void DisplayListBuilder::setColorFilter(sk_sp filter) { - filter // +void DisplayListBuilder::onSetColorFilter(sk_sp filter) { + (current_color_filter_ = filter) // ? Push(0, 0, std::move(filter)) : Push(0, 0); } -void DisplayListBuilder::setPathEffect(sk_sp effect) { - effect // +void DisplayListBuilder::onSetPathEffect(sk_sp effect) { + (current_path_effect_ = effect) // ? Push(0, 0, std::move(effect)) : Push(0, 0); } -void DisplayListBuilder::setMaskFilter(sk_sp filter) { - Push(0, 0, std::move(filter)); +void DisplayListBuilder::onSetMaskFilter(sk_sp filter) { + current_mask_sigma_ = kInvalidSigma; + (current_mask_filter_ = filter) // + ? Push(0, 0, std::move(filter)) + : Push(0, 0); } -void DisplayListBuilder::setMaskBlurFilter(SkBlurStyle style, SkScalar sigma) { +void DisplayListBuilder::onSetMaskBlurFilter(SkBlurStyle style, + SkScalar sigma) { + // Valid sigma is checked by setMaskBlurFilter + FML_DCHECK(mask_sigma_valid(sigma)); + current_mask_filter_ = nullptr; + current_mask_style_ = style; + current_mask_sigma_ = sigma; switch (style) { case kNormal_SkBlurStyle: Push(0, 0, sigma); @@ -1160,6 +1177,56 @@ void DisplayListBuilder::setMaskBlurFilter(SkBlurStyle style, SkScalar sigma) { } } +void DisplayListBuilder::setAttributesFromPaint( + const SkPaint& paint, + const DisplayListAttributeFlags flags) { + if (flags.applies_anti_alias()) { + setAntiAlias(paint.isAntiAlias()); + } + if (flags.applies_dither()) { + setDither(paint.isDither()); + } + if (flags.applies_alpha_or_color()) { + setColor(paint.getColor()); + } + if (flags.applies_blend()) { + skstd::optional mode_optional = paint.asBlendMode(); + if (mode_optional) { + setBlendMode(mode_optional.value()); + } else { + setBlender(sk_ref_sp(paint.getBlender())); + } + } + if (flags.applies_style()) { + setStyle(paint.getStyle()); + } + if (flags.is_stroked(paint.getStyle())) { + setStrokeWidth(paint.getStrokeWidth()); + setStrokeMiter(paint.getStrokeMiter()); + setStrokeCap(paint.getStrokeCap()); + setStrokeJoin(paint.getStrokeJoin()); + } + if (flags.applies_shader()) { + setShader(sk_ref_sp(paint.getShader())); + } + if (flags.applies_color_filter()) { + // invert colors is a Flutter::Paint thing, not an SkPaint thing + // we must clear it because it is a second potential color filter + // that is composed with the paint's color filter. + setInvertColors(false); + setColorFilter(sk_ref_sp(paint.getColorFilter())); + } + if (flags.applies_image_filter()) { + setImageFilter(sk_ref_sp(paint.getImageFilter())); + } + if (flags.applies_path_effect()) { + setPathEffect(sk_ref_sp(paint.getPathEffect())); + } + if (flags.applies_mask_filter()) { + setMaskFilter(sk_ref_sp(paint.getMaskFilter())); + } +} + void DisplayListBuilder::save() { save_level_++; Push(0, 1); @@ -1170,24 +1237,36 @@ void DisplayListBuilder::restore() { save_level_--; } } -void DisplayListBuilder::saveLayer(const SkRect* bounds, bool with_paint) { +void DisplayListBuilder::saveLayer(const SkRect* bounds, + bool restore_with_paint) { save_level_++; bounds // - ? Push(0, 1, *bounds, with_paint) - : Push(0, 1, with_paint); + ? Push(0, 1, *bounds, restore_with_paint) + : Push(0, 1, restore_with_paint); } void DisplayListBuilder::translate(SkScalar tx, SkScalar ty) { - Push(0, 1, tx, ty); + if (SkScalarIsFinite(tx) && SkScalarIsFinite(ty) && + (tx != 0.0 || ty != 0.0)) { + Push(0, 1, tx, ty); + } } void DisplayListBuilder::scale(SkScalar sx, SkScalar sy) { - Push(0, 1, sx, sy); + if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) && + (sx != 1.0 || sy != 1.0)) { + Push(0, 1, sx, sy); + } } void DisplayListBuilder::rotate(SkScalar degrees) { - Push(0, 1, degrees); + if (SkScalarMod(degrees, 360.0) != 0.0) { + Push(0, 1, degrees); + } } void DisplayListBuilder::skew(SkScalar sx, SkScalar sy) { - Push(0, 1, sx, sy); + if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) && + (sx != 0.0 || sy != 0.0)) { + Push(0, 1, sx, sy); + } } // clang-format off @@ -1196,7 +1275,10 @@ void DisplayListBuilder::skew(SkScalar sx, SkScalar sy) { void DisplayListBuilder::transform2DAffine( SkScalar mxx, SkScalar mxy, SkScalar mxt, SkScalar myx, SkScalar myy, SkScalar myt) { - if (!(mxx == 1 && mxy == 0 && mxt == 0 && + if (SkScalarsAreFinite(mxx, myx) && + SkScalarsAreFinite(mxy, myy) && + SkScalarsAreFinite(mxt, myt) && + !(mxx == 1 && mxy == 0 && mxt == 0 && myx == 0 && myy == 1 && myt == 0)) { Push(0, 1, mxx, mxy, mxt, @@ -1215,7 +1297,10 @@ void DisplayListBuilder::transformFullPerspective( mwx == 0 && mwy == 0 && mwz == 0 && mwt == 1) { transform2DAffine(mxx, mxy, mxt, myx, myy, myt); - } else { + } else if (SkScalarsAreFinite(mxx, mxy) && SkScalarsAreFinite(mxz, mxt) && + SkScalarsAreFinite(myx, myy) && SkScalarsAreFinite(myz, myt) && + SkScalarsAreFinite(mzx, mzy) && SkScalarsAreFinite(mzz, mzt) && + SkScalarsAreFinite(mwx, mwy) && SkScalarsAreFinite(mwz, mwt)) { Push(0, 1, mxx, mxy, mxz, mxt, myx, myy, myz, myt, @@ -1368,7 +1453,7 @@ void DisplayListBuilder::drawImageLattice(const sk_sp image, const SkCanvas::Lattice& lattice, const SkRect& dst, SkFilterMode filter, - bool with_paint) { + bool render_with_attributes) { int xDivCount = lattice.fXCount; int yDivCount = lattice.fYCount; FML_DCHECK((lattice.fRectTypes == nullptr) || (lattice.fColors != nullptr)); @@ -1379,9 +1464,9 @@ void DisplayListBuilder::drawImageLattice(const sk_sp image, (xDivCount + yDivCount) * sizeof(int) + cellCount * (sizeof(SkColor) + sizeof(SkCanvas::Lattice::RectType)); SkIRect src = lattice.fBounds ? *lattice.fBounds : image->bounds(); - void* pod = this->Push(bytes, 1, std::move(image), - xDivCount, yDivCount, cellCount, - src, dst, filter, with_paint); + void* pod = this->Push( + bytes, 1, std::move(image), xDivCount, yDivCount, cellCount, src, dst, + filter, render_with_attributes); CopyV(pod, lattice.fXDivs, xDivCount, lattice.fYDivs, yDivCount, lattice.fColors, cellCount, lattice.fRectTypes, cellCount); } @@ -1463,4 +1548,165 @@ void DisplayListBuilder::drawShadow(const SkPath& path, : Push(0, 1, path, color, elevation, dpr); } +// clang-format off +// Flags common to all primitives that apply colors +#define PAINT_FLAGS (kUsesDither_ | \ + kUsesColor_ | \ + kUsesAlpha_ | \ + kUsesBlend_ | \ + kUsesShader_ | \ + kUsesColorFilter_ | \ + kUsesImageFilter_) + +// Flags common to all primitives that stroke or fill +#define STROKE_OR_FILL_FLAGS (kIsDrawnGeometry_ | \ + kUsesAntiAlias_ | \ + kUsesMaskFilter_ | \ + kUsesPathEffect_) + +// Flags common to primitives that stroke geometry +#define STROKE_FLAGS (kIsStrokedGeometry_ | \ + kUsesAntiAlias_ | \ + kUsesMaskFilter_ | \ + kUsesPathEffect_) + +// Flags common to primitives that render an image with paint attributes +#define IMAGE_FLAGS_BASE (kIsNonGeometric_ | \ + kUsesAlpha_ | \ + kUsesDither_ | \ + kUsesBlend_ | \ + kUsesColorFilter_ | \ + kUsesImageFilter_) +// clang-format on + +const DisplayListAttributeFlags DisplayListOpFlags::kSaveLayerFlags = + DisplayListAttributeFlags(kIgnoresPaint_); + +const DisplayListAttributeFlags DisplayListOpFlags::kSaveLayerWithPaintFlags = + DisplayListAttributeFlags(kIsNonGeometric_ | // + kUsesAlpha_ | // + kUsesBlend_ | // + kUsesColorFilter_ | // + kUsesImageFilter_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawColorFlags = + DisplayListAttributeFlags(kFloodsSurface_ | kIgnoresPaint_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawPaintFlags = + DisplayListAttributeFlags(PAINT_FLAGS | kFloodsSurface_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawHVLineFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_FLAGS | kMayHaveCaps_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawLineFlags = + kDrawHVLineFlags.with(kMayHaveDiagonalCaps_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawRectFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_OR_FILL_FLAGS | + kMayHaveJoins_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawOvalFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_OR_FILL_FLAGS); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawCircleFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_OR_FILL_FLAGS); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawRRectFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_OR_FILL_FLAGS); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawDRRectFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_OR_FILL_FLAGS); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawPathFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_OR_FILL_FLAGS | + kMayHaveCaps_ | kMayHaveDiagonalCaps_ | + kMayHaveJoins_ | kMayHaveAcuteJoins_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawArcNoCenterFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_OR_FILL_FLAGS | + kMayHaveCaps_ | kMayHaveDiagonalCaps_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawArcWithCenterFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_OR_FILL_FLAGS | + kMayHaveJoins_ | kMayHaveAcuteJoins_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawPointsAsPointsFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_FLAGS | // + kMayHaveCaps_ | kButtCapIsSquare_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawPointsAsLinesFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_FLAGS | // + kMayHaveCaps_ | kMayHaveDiagonalCaps_); + +// Polygon mode just draws (count-1) separate lines, no joins +const DisplayListAttributeFlags DisplayListOpFlags::kDrawPointsAsPolygonFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_FLAGS | // + kMayHaveCaps_ | kMayHaveDiagonalCaps_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawVerticesFlags = + DisplayListAttributeFlags(kIsNonGeometric_ | // + kUsesDither_ | // + kUsesAlpha_ | // + kUsesShader_ | // + kUsesBlend_ | // + kUsesColorFilter_ | // + kUsesImageFilter_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawImageFlags = + DisplayListAttributeFlags(kIgnoresPaint_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawImageWithPaintFlags = + DisplayListAttributeFlags(IMAGE_FLAGS_BASE | // + kUsesAntiAlias_ | kUsesMaskFilter_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawImageRectFlags = + DisplayListAttributeFlags(kIgnoresPaint_); + +const DisplayListAttributeFlags + DisplayListOpFlags::kDrawImageRectWithPaintFlags = + DisplayListAttributeFlags(IMAGE_FLAGS_BASE | // + kUsesAntiAlias_ | kUsesMaskFilter_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawImageNineFlags = + DisplayListAttributeFlags(kIgnoresPaint_); + +const DisplayListAttributeFlags + DisplayListOpFlags::kDrawImageNineWithPaintFlags = + DisplayListAttributeFlags(IMAGE_FLAGS_BASE); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawImageLatticeFlags = + DisplayListAttributeFlags(kIgnoresPaint_); + +const DisplayListAttributeFlags + DisplayListOpFlags::kDrawImageLatticeWithPaintFlags = + DisplayListAttributeFlags(IMAGE_FLAGS_BASE); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawAtlasFlags = + DisplayListAttributeFlags(kIgnoresPaint_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawAtlasWithPaintFlags = + DisplayListAttributeFlags(IMAGE_FLAGS_BASE); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawPictureFlags = + DisplayListAttributeFlags(kIgnoresPaint_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawPictureWithPaintFlags = + kSaveLayerWithPaintFlags; + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawDisplayListFlags = + DisplayListAttributeFlags(kIgnoresPaint_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawTextBlobFlags = + DisplayListAttributeFlags(PAINT_FLAGS | STROKE_OR_FILL_FLAGS | + kMayHaveJoins_) + .without(kUsesAntiAlias_); + +const DisplayListAttributeFlags DisplayListOpFlags::kDrawShadowFlags = + DisplayListAttributeFlags(kIgnoresPaint_); + +#undef PAINT_FLAGS +#undef STROKE_OR_FILL_FLAGS +#undef STROKE_FLAGS +#undef IMAGE_FLAGS_BASE + } // namespace flutter diff --git a/flow/display_list.h b/flow/display_list.h index 327ad9df2ea2..171c8553a914 100644 --- a/flow/display_list.h +++ b/flow/display_list.h @@ -5,17 +5,22 @@ #ifndef FLUTTER_FLOW_DISPLAY_LIST_H_ #define FLUTTER_FLOW_DISPLAY_LIST_H_ +#include + #include "third_party/skia/include/core/SkBlender.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/SkMaskFilter.h" #include "third_party/skia/include/core/SkPathEffect.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" +#include "flutter/fml/logging.h" + // The Flutter DisplayList mechanism encapsulates a persistent sequence of // rendering operations. // @@ -463,6 +468,278 @@ class Dispatcher { SkScalar dpr) = 0; }; +/// The base class for the classes that maintain a list of +/// attributes that might be important for a number of operations +/// including which rendering attributes need to be set before +/// calling a rendering method (all |drawSomething| calls), +/// or for determining which exceptional conditions may need +/// to be accounted for in bounds calculations. +/// This class contains only protected definitions and helper methods +/// for the public classes |DisplayListAttributeFlags| and +/// |DisplayListSpecialGeometryFlags|. +class DisplayListFlags { + protected: + // A drawing operation that is not geometric in nature (but which + // may still apply a MaskFilter - see |kUsesMaskFilter_| below). + static constexpr int kIsNonGeometric_ = 0; + + // A geometric operation that is defined as a fill operation + // regardless of what the current paint Style is set to. + // This flag will automatically assume |kUsesMaskFilter_|. + static constexpr int kIsFilledGeometry_ = 1 << 0; + + // A geometric operation that is defined as a stroke operation + // regardless of what the current paint Style is set to. + // This flag will automatically assume |kUsesMaskFilter_|. + static constexpr int kIsStrokedGeometry_ = 1 << 1; + + // A geometric operation that may be a stroke or fill operation + // depending on the current state of the paint Style attribute. + // This flag will automatically assume |kUsesMaskFilter_|. + static constexpr int kIsDrawnGeometry_ = 1 << 2; + + static constexpr int kIsAnyGeometryMask_ = // + kIsFilledGeometry_ | // + kIsStrokedGeometry_ | // + kIsDrawnGeometry_; + + // A primitive that floods the surface (or clip) with no + // natural bounds, such as |drawColor| or |drawPaint|. + static constexpr int kFloodsSurface_ = 1 << 3; + + static constexpr int kMayHaveCaps_ = 1 << 4; + static constexpr int kMayHaveJoins_ = 1 << 5; + static constexpr int kButtCapIsSquare_ = 1 << 6; + + // A geometric operation which has a path that might have + // end caps that are not rectilinear which means that square + // end caps might project further than half the stroke width + // from the geometry bounds. + // A rectilinear path such as |drawRect| will not have + // diagonal end caps. |drawLine| might have diagonal end + // caps depending on the angle of the line, and more likely + // |drawPath| will often have such end caps. + static constexpr int kMayHaveDiagonalCaps_ = 1 << 7; + + // A geometric operation which has joined vertices that are + // not guaranteed to be smooth (angles of incoming and outgoing) + // segments at some joins may not have the same angle) or + // rectilinear (squares have right angles at the corners, but + // those corners will never extend past the bounding box of + // the geometry pre-transform). + // |drawRect|, |drawOval| and |drawRRect| all have well + // behaved joins, but |drawPath| might have joins that cause + // mitered extensions outside the pre-transformed bounding box. + static constexpr int kMayHaveAcuteJoins_ = 1 << 8; + + static constexpr int kAnySpecialGeometryMask_ = // + kMayHaveCaps_ | kMayHaveJoins_ | kButtCapIsSquare_ | // + kMayHaveDiagonalCaps_ | kMayHaveAcuteJoins_; + + // clang-format off + static constexpr int kUsesAntiAlias_ = 1 << 10; + static constexpr int kUsesDither_ = 1 << 11; + static constexpr int kUsesAlpha_ = 1 << 12; + static constexpr int kUsesColor_ = 1 << 13; + static constexpr int kUsesBlend_ = 1 << 14; + static constexpr int kUsesShader_ = 1 << 15; + static constexpr int kUsesColorFilter_ = 1 << 16; + static constexpr int kUsesPathEffect_ = 1 << 17; + static constexpr int kUsesMaskFilter_ = 1 << 18; + static constexpr int kUsesImageFilter_ = 1 << 19; + + static constexpr int kIgnoresPaint_ = 1 << 30; + // clang-format on + + static constexpr int kAnyAttributeMask_ = // + kUsesAntiAlias_ | kUsesDither_ | kUsesAlpha_ | kUsesColor_ | kUsesBlend_ | + kUsesShader_ | kUsesColorFilter_ | kUsesPathEffect_ | kUsesMaskFilter_ | + kUsesImageFilter_; +}; + +class DisplayListFlagsBase : protected DisplayListFlags { + protected: + DisplayListFlagsBase(int flags) : flags_(flags) {} + + const int flags_; + + bool has_any(int qFlags) const { return (flags_ & qFlags) != 0; } + bool has_all(int qFlags) const { return (flags_ & qFlags) == qFlags; } + bool has_none(int qFlags) const { return (flags_ & qFlags) == 0; } +}; + +/// An attribute class for advertising specific properties of +/// a geometric attribute that can affect the computation of +/// the bounds of the primitive. +class DisplayListSpecialGeometryFlags : DisplayListFlagsBase { + public: + /// The geometry may have segments that end without closing the path. + bool may_have_end_caps() const { return has_any(kMayHaveCaps_); } + + /// The geometry may have segments connect non-continuously. + bool may_have_joins() const { return has_any(kMayHaveJoins_); } + + /// Mainly for drawPoints(PointMode) where Butt caps are rendered as squares. + bool butt_cap_becomes_square() const { return has_any(kButtCapIsSquare_); } + + /// The geometry may have segments that end on a diagonal + /// such that their end caps extend further than the default + /// |strokeWidth * 0.5| margin around the geometry. + bool may_have_diagonal_caps() const { return has_any(kMayHaveDiagonalCaps_); } + + /// The geometry may have segments that meet at vertices at + /// an acute angle such that the miter joins will extend + /// further than the default |strokeWidth * 0.5| margin around + /// the geometry. + bool may_have_acute_joins() const { return has_any(kMayHaveAcuteJoins_); } + + private: + DisplayListSpecialGeometryFlags(int flags) : DisplayListFlagsBase(flags) { + FML_DCHECK((flags & kAnySpecialGeometryMask_) == flags); + } + + const DisplayListSpecialGeometryFlags with(int extra) const { + return extra == 0 ? *this : DisplayListSpecialGeometryFlags(flags_ | extra); + } + + friend class DisplayListAttributeFlags; +}; + +class DisplayListAttributeFlags : DisplayListFlagsBase { + public: + const DisplayListSpecialGeometryFlags WithPathEffect( + sk_sp effect) const { + if (is_geometric() && effect) { + SkPathEffect::DashInfo info; + if (effect->asADash(&info) == SkPathEffect::kDash_DashType) { + // A dash effect has a very simple impact. It cannot introduce any + // miter joins that weren't already present in the original path + // and it does not grow the bounds of the path, but it can add + // end caps to areas that might not have had them before so all + // we need to do is to indicate the potential for diagonal + // end caps and move on. + return special_flags_.with(kMayHaveCaps_ | kMayHaveDiagonalCaps_); + } else { + // An arbitrary path effect can introduce joins at an arbitrary + // angle and may change the geometry of the end caps + return special_flags_.with(kMayHaveCaps_ | kMayHaveDiagonalCaps_ | + kMayHaveJoins_ | kMayHaveAcuteJoins_); + } + } + return special_flags_; + } + + bool ignores_paint() const { return has_any(kIgnoresPaint_); } + + bool applies_anti_alias() const { return has_any(kUsesAntiAlias_); } + bool applies_dither() const { return has_any(kUsesDither_); } + bool applies_color() const { return has_any(kUsesColor_); } + bool applies_alpha() const { return has_any(kUsesAlpha_); } + bool applies_alpha_or_color() const { + return has_any(kUsesAlpha_ | kUsesColor_); + } + + /// The primitive dynamically determines whether it is a stroke or fill + /// operation (or both) based on the setting of the |Style| attribute. + bool applies_style() const { return has_any(kIsDrawnGeometry_); } + /// The primitive can use any of the stroke attributes, such as + /// StrokeWidth, StrokeMiter, StrokeCap, or StrokeJoin. This + /// method will return if the primitive is defined as one that + /// strokes its geometry (such as |drawLine|) or if it is defined + /// as one that honors the Style attribute. If the Style attribute + /// is known then a more accurate answer can be returned from + /// the |is_stroked| method by supplying the actual setting of + /// the style. + // bool applies_stroke_attributes() const { return is_stroked(); } + + bool applies_shader() const { return has_any(kUsesShader_); } + /// The primitive honors the current SkColorFilter, including + /// the related attribute InvertColors + bool applies_color_filter() const { return has_any(kUsesColorFilter_); } + /// The primitive honors the SkBlendMode or SkBlender + bool applies_blend() const { return has_any(kUsesBlend_); } + bool applies_path_effect() const { return has_any(kUsesPathEffect_); } + /// The primitive honors the SkMaskFilter whether set using the + /// filter object or using the convenience method |setMaskBlurFilter| + bool applies_mask_filter() const { return has_any(kUsesMaskFilter_); } + bool applies_image_filter() const { return has_any(kUsesImageFilter_); } + + bool is_geometric() const { return has_any(kIsAnyGeometryMask_); } + bool always_stroked() const { return has_any(kIsStrokedGeometry_); } + bool is_stroked(SkPaint::Style style = SkPaint::Style::kStroke_Style) const { + return ( + has_any(kIsStrokedGeometry_) || + (style != SkPaint::Style::kFill_Style && has_any(kIsDrawnGeometry_))); + } + + bool is_flood() const { return has_any(kFloodsSurface_); } + + private: + DisplayListAttributeFlags(int flags) + : DisplayListFlagsBase(flags), + special_flags_(flags & kAnySpecialGeometryMask_) { + FML_DCHECK((flags & kIsAnyGeometryMask_) == kIsNonGeometric_ || + (flags & kIsAnyGeometryMask_) == kIsFilledGeometry_ || + (flags & kIsAnyGeometryMask_) == kIsStrokedGeometry_ || + (flags & kIsAnyGeometryMask_) == kIsDrawnGeometry_); + FML_DCHECK(((flags & kAnyAttributeMask_) == 0) != + ((flags & kIgnoresPaint_) == 0)); + FML_DCHECK((flags & kIsAnyGeometryMask_) != 0 || + (flags & kAnySpecialGeometryMask_) == 0); + } + + const DisplayListAttributeFlags with(int extra) const { + return extra == 0 ? *this : DisplayListAttributeFlags(flags_ | extra); + } + + const DisplayListAttributeFlags without(int remove) const { + FML_DCHECK(has_all(remove)); + return (flags_ & ~remove); + } + + const DisplayListSpecialGeometryFlags special_flags_; + + friend class DisplayListOpFlags; +}; + +class DisplayListOpFlags : DisplayListFlags { + public: + static const DisplayListAttributeFlags kSaveLayerFlags; + static const DisplayListAttributeFlags kSaveLayerWithPaintFlags; + static const DisplayListAttributeFlags kDrawColorFlags; + static const DisplayListAttributeFlags kDrawPaintFlags; + static const DisplayListAttributeFlags kDrawLineFlags; + // Special case flags for horizonal and vertical lines + static const DisplayListAttributeFlags kDrawHVLineFlags; + static const DisplayListAttributeFlags kDrawRectFlags; + static const DisplayListAttributeFlags kDrawOvalFlags; + static const DisplayListAttributeFlags kDrawCircleFlags; + static const DisplayListAttributeFlags kDrawRRectFlags; + static const DisplayListAttributeFlags kDrawDRRectFlags; + static const DisplayListAttributeFlags kDrawPathFlags; + static const DisplayListAttributeFlags kDrawArcNoCenterFlags; + static const DisplayListAttributeFlags kDrawArcWithCenterFlags; + static const DisplayListAttributeFlags kDrawPointsAsPointsFlags; + static const DisplayListAttributeFlags kDrawPointsAsLinesFlags; + static const DisplayListAttributeFlags kDrawPointsAsPolygonFlags; + static const DisplayListAttributeFlags kDrawVerticesFlags; + static const DisplayListAttributeFlags kDrawImageFlags; + static const DisplayListAttributeFlags kDrawImageWithPaintFlags; + static const DisplayListAttributeFlags kDrawImageRectFlags; + static const DisplayListAttributeFlags kDrawImageRectWithPaintFlags; + static const DisplayListAttributeFlags kDrawImageNineFlags; + static const DisplayListAttributeFlags kDrawImageNineWithPaintFlags; + static const DisplayListAttributeFlags kDrawImageLatticeFlags; + static const DisplayListAttributeFlags kDrawImageLatticeWithPaintFlags; + static const DisplayListAttributeFlags kDrawAtlasFlags; + static const DisplayListAttributeFlags kDrawAtlasWithPaintFlags; + static const DisplayListAttributeFlags kDrawPictureFlags; + static const DisplayListAttributeFlags kDrawPictureWithPaintFlags; + static const DisplayListAttributeFlags kDrawDisplayListFlags; + static const DisplayListAttributeFlags kDrawTextBlobFlags; + static const DisplayListAttributeFlags kDrawShadowFlags; +}; + // The primary class used to build a display list. The list of methods // here matches the list of methods invoked on a |Dispatcher|. // If there is some code that already renders to an SkCanvas object, @@ -473,33 +750,145 @@ class DisplayListBuilder final : public virtual Dispatcher, public SkRefCnt { DisplayListBuilder(const SkRect& cull_rect = kMaxCullRect_); ~DisplayListBuilder(); - void setAntiAlias(bool aa) override; - void setDither(bool dither) override; - void setInvertColors(bool invert) override; - void setStrokeCap(SkPaint::Cap cap) override; - void setStrokeJoin(SkPaint::Join join) override; - void setStyle(SkPaint::Style style) override; - void setStrokeWidth(SkScalar width) override; - void setStrokeMiter(SkScalar limit) override; - void setColor(SkColor color) override; - void setBlendMode(SkBlendMode mode) override; - void setBlender(sk_sp blender) override; - void setShader(sk_sp shader) override; - void setImageFilter(sk_sp filter) override; - void setColorFilter(sk_sp filter) override; - void setPathEffect(sk_sp effect) override; - void setMaskFilter(sk_sp filter) override; - void setMaskBlurFilter(SkBlurStyle style, SkScalar sigma) override; + void setAntiAlias(bool aa) override { + if (current_anti_alias_ != aa) { + onSetAntiAlias(aa); + } + } + void setDither(bool dither) override { + if (current_dither_ != dither) { + onSetDither(dither); + } + } + void setInvertColors(bool invert) override { + if (current_invert_colors_ != invert) { + onSetInvertColors(invert); + } + } + void setStrokeCap(SkPaint::Cap cap) override { + if (current_stroke_cap_ != cap) { + onSetStrokeCap(cap); + } + } + void setStrokeJoin(SkPaint::Join join) override { + if (current_stroke_join_ != join) { + onSetStrokeJoin(join); + } + } + void setStyle(SkPaint::Style style) override { + if (current_style_ != style) { + onSetStyle(style); + } + } + void setStrokeWidth(SkScalar width) override { + if (current_stroke_width_ != width) { + onSetStrokeWidth(width); + } + } + void setStrokeMiter(SkScalar limit) override { + if (current_stroke_miter_ != limit) { + onSetStrokeMiter(limit); + } + } + void setColor(SkColor color) override { + if (current_color_ != color) { + onSetColor(color); + } + } + void setBlendMode(SkBlendMode mode) override { + if (current_blender_ || current_blend_mode_ != mode) { + onSetBlendMode(mode); + } + } + void setBlender(sk_sp blender) override { + if (!blender) { + setBlendMode(SkBlendMode::kSrcOver); + } else if (current_blender_ != blender) { + onSetBlender(std::move(blender)); + } + } + void setShader(sk_sp shader) override { + if (current_shader_ != shader) { + onSetShader(std::move(shader)); + } + } + void setImageFilter(sk_sp filter) override { + if (current_image_filter_ != filter) { + onSetImageFilter(std::move(filter)); + } + } + void setColorFilter(sk_sp filter) override { + if (current_color_filter_ != filter) { + onSetColorFilter(std::move(filter)); + } + } + void setPathEffect(sk_sp effect) override { + if (current_path_effect_ != effect) { + onSetPathEffect(std::move(effect)); + } + } + void setMaskFilter(sk_sp filter) override { + if (mask_sigma_valid(current_mask_sigma_) || + current_mask_filter_ != filter) { + onSetMaskFilter(std::move(filter)); + } + } + void setMaskBlurFilter(SkBlurStyle style, SkScalar sigma) override { + if (!mask_sigma_valid(sigma)) { + // SkMastFilter::MakeBlur(invalid sigma) returns a nullptr, so we + // reset the mask filter here rather than recording the invalid values. + setMaskFilter(nullptr); + } else if (current_mask_style_ != style || current_mask_sigma_ != sigma) { + onSetMaskBlurFilter(style, sigma); + } + } + + bool isAntiAlias() const { return current_anti_alias_; } + bool isDither() const { return current_dither_; } + SkPaint::Style getStyle() const { return current_style_; } + SkColor getColor() const { return current_color_; } + SkScalar getStrokeWidth() const { return current_stroke_width_; } + SkScalar getStrokeMiter() const { return current_stroke_miter_; } + SkPaint::Cap getStrokeCap() const { return current_stroke_cap_; } + SkPaint::Join getStrokeJoin() const { return current_stroke_join_; } + sk_sp getShader() const { return current_shader_; } + sk_sp getColorFilter() const { return current_color_filter_; } + bool isInvertColors() const { return current_invert_colors_; } + std::optional getBlendMode() const { + if (current_blender_) { + // The setters will turn "Mode" style blenders into "blend_mode"s + return {}; + } + return current_blend_mode_; + } + sk_sp getBlender() const { + return current_blender_ ? current_blender_ + : SkBlender::Mode(current_blend_mode_); + } + sk_sp getPathEffect() const { return current_path_effect_; } + sk_sp getMaskFilter() const { + return mask_sigma_valid(current_mask_sigma_) + ? SkMaskFilter::MakeBlur(current_mask_style_, + current_mask_sigma_) + : current_mask_filter_; + } + // No utility getter for the utility setter: + // void setMaskBlurFilter (SkBlurStyle style, SkScalar sigma) + sk_sp getImageFilter() const { return current_image_filter_; } void save() override; - void saveLayer(const SkRect* bounds, bool restoreWithPaint) override; + void saveLayer(const SkRect* bounds, bool restore_with_paint) override; void restore() override; + int getSaveCount() { return save_level_ + 1; } 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 setAttributesFromPaint(const SkPaint& paint, + const DisplayListAttributeFlags flags); + // clang-format off // 2x3 2D affine subset of a 4x4 transform in row major order @@ -514,9 +903,9 @@ class DisplayListBuilder final : public virtual Dispatcher, public SkRefCnt { // clang-format on - void clipRect(const SkRect& rect, SkClipOp clip_op, bool isAA) override; - void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool isAA) override; - void clipPath(const SkPath& path, SkClipOp clip_op, bool isAA) override; + void clipRect(const SkRect& rect, SkClipOp clip_op, bool is_aa) override; + void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool is_aa) override; + void clipPath(const SkPath& path, SkClipOp clip_op, bool is_aa) override; void drawPaint() override; void drawColor(SkColor color, SkBlendMode mode) override; @@ -599,6 +988,51 @@ class DisplayListBuilder final : public virtual Dispatcher, public SkRefCnt { template void* Push(size_t extra, int op_inc, Args&&... args); + + // kInvalidSigma is used to indicate that no MaskBlur is currently set. + static constexpr SkScalar kInvalidSigma = 0.0; + static bool mask_sigma_valid(SkScalar sigma) { + return SkScalarIsFinite(sigma) && sigma > 0.0; + } + + void onSetAntiAlias(bool aa); + void onSetDither(bool dither); + void onSetInvertColors(bool invert); + void onSetStrokeCap(SkPaint::Cap cap); + void onSetStrokeJoin(SkPaint::Join join); + void onSetStyle(SkPaint::Style style); + void onSetStrokeWidth(SkScalar width); + void onSetStrokeMiter(SkScalar limit); + void onSetColor(SkColor color); + void onSetBlendMode(SkBlendMode mode); + void onSetBlender(sk_sp blender); + void onSetShader(sk_sp shader); + void onSetImageFilter(sk_sp filter); + void onSetColorFilter(sk_sp filter); + void onSetPathEffect(sk_sp effect); + void onSetMaskFilter(sk_sp filter); + void onSetMaskBlurFilter(SkBlurStyle style, SkScalar sigma); + + // These values should match the defaults of the Dart Paint object. + bool current_anti_alias_ = false; + bool current_dither_ = false; + bool current_invert_colors_ = false; + SkColor current_color_ = 0xFF000000; + SkPaint::Style current_style_ = SkPaint::Style::kFill_Style; + SkScalar current_stroke_width_ = 0.0; + SkScalar current_stroke_miter_ = 4.0; + SkPaint::Cap current_stroke_cap_ = SkPaint::Cap::kButt_Cap; + SkPaint::Join current_stroke_join_ = SkPaint::Join::kMiter_Join; + // If |current_blender_| is set then |current_blend_mode_| should be ignored + SkBlendMode current_blend_mode_ = SkBlendMode::kSrcOver; + sk_sp current_blender_; + sk_sp current_shader_; + sk_sp current_color_filter_; + sk_sp current_image_filter_; + sk_sp current_path_effect_; + sk_sp current_mask_filter_; + SkBlurStyle current_mask_style_; + SkScalar current_mask_sigma_ = kInvalidSigma; }; } // namespace flutter diff --git a/flow/display_list_canvas.cc b/flow/display_list_canvas.cc index 72b8574d3c9f..7f66a37515ec 100644 --- a/flow/display_list_canvas.cc +++ b/flow/display_list_canvas.cc @@ -261,7 +261,7 @@ void DisplayListCanvasRecorder::willSave() { SkCanvas::SaveLayerStrategy DisplayListCanvasRecorder::getSaveLayerStrategy( const SaveLayerRec& rec) { if (rec.fPaint) { - RecordPaintAttributes(rec.fPaint, DrawType::kSaveLayerOpType); + builder_->setAttributesFromPaint(*rec.fPaint, kSaveLayerWithPaintFlags); builder_->saveLayer(rec.fBounds, true); } else { builder_->saveLayer(rec.fBounds, false); @@ -273,28 +273,28 @@ void DisplayListCanvasRecorder::didRestore() { } void DisplayListCanvasRecorder::onDrawPaint(const SkPaint& paint) { - RecordPaintAttributes(&paint, DrawType::kFillOpType); + builder_->setAttributesFromPaint(paint, kDrawPaintFlags); builder_->drawPaint(); } void DisplayListCanvasRecorder::onDrawRect(const SkRect& rect, const SkPaint& paint) { - RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->setAttributesFromPaint(paint, kDrawRectFlags); builder_->drawRect(rect); } void DisplayListCanvasRecorder::onDrawRRect(const SkRRect& rrect, const SkPaint& paint) { - RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->setAttributesFromPaint(paint, kDrawRRectFlags); builder_->drawRRect(rrect); } void DisplayListCanvasRecorder::onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint) { - RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->setAttributesFromPaint(paint, kDrawDRRectFlags); builder_->drawDRRect(outer, inner); } void DisplayListCanvasRecorder::onDrawOval(const SkRect& rect, const SkPaint& paint) { - RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->setAttributesFromPaint(paint, kDrawOvalFlags); builder_->drawOval(rect); } void DisplayListCanvasRecorder::onDrawArc(const SkRect& rect, @@ -302,12 +302,15 @@ void DisplayListCanvasRecorder::onDrawArc(const SkRect& rect, SkScalar sweepAngle, bool useCenter, const SkPaint& paint) { - RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->setAttributesFromPaint(paint, + useCenter // + ? kDrawArcWithCenterFlags + : kDrawArcNoCenterFlags); builder_->drawArc(rect, startAngle, sweepAngle, useCenter); } void DisplayListCanvasRecorder::onDrawPath(const SkPath& path, const SkPaint& paint) { - RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->setAttributesFromPaint(paint, kDrawPathFlags); builder_->drawPath(path); } @@ -315,7 +318,17 @@ void DisplayListCanvasRecorder::onDrawPoints(SkCanvas::PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) { - RecordPaintAttributes(&paint, DrawType::kStrokeOpType); + switch (mode) { + case SkCanvas::kPoints_PointMode: + builder_->setAttributesFromPaint(paint, kDrawPointsAsPointsFlags); + break; + case SkCanvas::kLines_PointMode: + builder_->setAttributesFromPaint(paint, kDrawPointsAsLinesFlags); + break; + case SkCanvas::kPolygon_PointMode: + builder_->setAttributesFromPaint(paint, kDrawPointsAsPolygonFlags); + break; + } if (mode == SkCanvas::PointMode::kLines_PointMode && count == 2) { builder_->drawLine(pts[0], pts[1]); } else { @@ -330,7 +343,7 @@ void DisplayListCanvasRecorder::onDrawPoints(SkCanvas::PointMode mode, void DisplayListCanvasRecorder::onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint) { - RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->setAttributesFromPaint(paint, kDrawVerticesFlags); builder_->drawVertices(sk_ref_sp(vertices), mode); } @@ -340,7 +353,7 @@ void DisplayListCanvasRecorder::onDrawImage2(const SkImage* image, const SkSamplingOptions& sampling, const SkPaint* paint) { if (paint != nullptr) { - RecordPaintAttributes(paint, DrawType::kImageOpType); + builder_->setAttributesFromPaint(*paint, kDrawImageWithPaintFlags); } builder_->drawImage(sk_ref_sp(image), SkPoint::Make(dx, dy), sampling, paint != nullptr); @@ -353,7 +366,7 @@ void DisplayListCanvasRecorder::onDrawImageRect2( const SkPaint* paint, SrcRectConstraint constraint) { if (paint != nullptr) { - RecordPaintAttributes(paint, DrawType::kImageRectOpType); + builder_->setAttributesFromPaint(*paint, kDrawImageRectWithPaintFlags); } builder_->drawImageRect(sk_ref_sp(image), src, dst, sampling, paint != nullptr, constraint); @@ -370,7 +383,7 @@ void DisplayListCanvasRecorder::onDrawImageLattice2(const SkImage* image, if (*paint == default_paint) { paint = nullptr; } else { - RecordPaintAttributes(paint, DrawType::kImageOpType); + builder_->setAttributesFromPaint(*paint, kDrawImageLatticeWithPaintFlags); } } builder_->drawImageLattice(sk_ref_sp(image), lattice, dst, filter, @@ -386,7 +399,7 @@ void DisplayListCanvasRecorder::onDrawAtlas2(const SkImage* image, const SkRect* cull, const SkPaint* paint) { if (paint != nullptr) { - RecordPaintAttributes(paint, DrawType::kImageOpType); + builder_->setAttributesFromPaint(*paint, kDrawAtlasWithPaintFlags); } builder_->drawAtlas(sk_ref_sp(image), xform, src, colors, count, mode, sampling, cull, paint != nullptr); @@ -396,7 +409,7 @@ void DisplayListCanvasRecorder::onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint) { - RecordPaintAttributes(&paint, DrawType::kDrawOpType); + builder_->setAttributesFromPaint(paint, kDrawTextBlobFlags); builder_->drawTextBlob(sk_ref_sp(blob), x, y); } void DisplayListCanvasRecorder::onDrawShadowRec(const SkPath& path, @@ -411,119 +424,9 @@ void DisplayListCanvasRecorder::onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint) { if (paint != nullptr) { - RecordPaintAttributes(paint, DrawType::kSaveLayerOpType); + builder_->setAttributesFromPaint(*paint, kDrawPictureWithPaintFlags); } 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_->setAntiAlias(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_)) { - skstd::optional mode_optional = paint->asBlendMode(); - if (mode_optional) { - SkBlendMode mode = mode_optional.value(); - if (current_blender_ || current_blend_ != mode) { - builder_->setBlendMode(current_blend_ = mode); - current_blender_ = nullptr; - } - } else { - if (current_blender_.get() != paint->getBlender()) { - builder_->setBlender(current_blender_ = sk_ref_sp(paint->getBlender())); - } - } - } - // 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_->setStyle(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_->setStrokeCap(current_cap_ = paint->getStrokeCap()); - } - if (current_join_ != paint->getStrokeJoin()) { - builder_->setStrokeJoin(current_join_ = paint->getStrokeJoin()); - } - if (current_miter_limit_ != paint->getStrokeMiter()) { - builder_->setStrokeMiter(current_miter_limit_ = paint->getStrokeMiter()); - } - } - 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 & kPathEffectNeeded_) != 0 && - current_path_effect_.get() != paint->getPathEffect()) { - builder_->setPathEffect(current_path_effect_ = - sk_ref_sp(paint->getPathEffect())); - } - 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 index a6ef6902f6b1..d48b7760eaf8 100644 --- a/flow/display_list_canvas.h +++ b/flow/display_list_canvas.h @@ -49,9 +49,9 @@ class DisplayListCanvasDispatcher : public virtual Dispatcher, SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) override; // clang-format on - void clipRect(const SkRect& rect, SkClipOp clip_op, bool isAA) override; - void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool isAA) override; - void clipPath(const SkPath& path, SkClipOp clip_op, bool isAA) override; + void clipRect(const SkRect& rect, SkClipOp clip_op, bool is_aa) override; + void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool is_aa) override; + void clipPath(const SkPath& path, SkClipOp clip_op, bool is_aa) override; void drawPaint() override; void drawColor(SkColor color, SkBlendMode mode) override; @@ -120,7 +120,8 @@ class DisplayListCanvasDispatcher : public virtual Dispatcher, // Receives all methods on SkCanvas and sends them to a DisplayListBuilder class DisplayListCanvasRecorder : public SkCanvasVirtualEnforcer, - public SkRefCnt { + public SkRefCnt, + DisplayListOpFlags { public: DisplayListCanvasRecorder(const SkRect& bounds); @@ -233,80 +234,8 @@ class DisplayListCanvasRecorder 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 kPaintStyleNeeded_ = 1 << 4; - static constexpr int kStrokeStyleNeeded_ = 1 << 5; - static constexpr int kShaderNeeded_ = 1 << 6; - static constexpr int kColorFilterNeeded_ = 1 << 7; - static constexpr int kImageFilterNeeded_ = 1 << 8; - static constexpr int kPathEffectNeeded_ = 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_ | kPathEffectNeeded_; - static constexpr int kStrokeMask_ = kPaintMask_ | kStrokeStyleNeeded_ | - kMaskFilterNeeded_ | kPathEffectNeeded_; - static constexpr int kImageMask_ = kColorNeeded_ | kBlendNeeded_ | - kInvertColorsNeeded_ | - kColorFilterNeeded_ | kDitherNeeded_ | - kImageFilterNeeded_ | 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; - sk_sp current_blender_; - sk_sp current_shader_; - sk_sp current_color_filter_; - sk_sp current_image_filter_; - sk_sp current_path_effect_; - sk_sp current_mask_filter_; }; } // namespace flutter diff --git a/flow/display_list_canvas_unittests.cc b/flow/display_list_canvas_unittests.cc index 8a4cc37b818f..22716abb497f 100644 --- a/flow/display_list_canvas_unittests.cc +++ b/flow/display_list_canvas_unittests.cc @@ -70,11 +70,13 @@ constexpr SkScalar Miter4DiamondOffsetY = RenderWidth * 0.14; // Render 3 vertical and horizontal diamonds each // designed to break at the tested miter limits // 0.0, 4.0 and 10.0 -constexpr SkScalar x_off_0 = RenderCenterX; -constexpr SkScalar x_off_l1 = RenderCenterX - Miter4DiamondOffsetX; +// Center is biased by 0.5 to include more pixel centers in the +// thin miters +constexpr SkScalar x_off_0 = RenderCenterX + 0.5; +constexpr SkScalar x_off_l1 = x_off_0 - Miter4DiamondOffsetX; constexpr SkScalar x_off_l2 = x_off_l1 - Miter10DiamondOffsetX; constexpr SkScalar x_off_l3 = x_off_l2 - Miter10DiamondOffsetX; -constexpr SkScalar x_off_r1 = RenderCenterX + Miter4DiamondOffsetX; +constexpr SkScalar x_off_r1 = x_off_0 + Miter4DiamondOffsetX; constexpr SkScalar x_off_r2 = x_off_r1 + MiterExtremeDiamondOffsetX; constexpr SkScalar x_off_r3 = x_off_r2 + MiterExtremeDiamondOffsetX; constexpr SkPoint VerticalMiterDiamondPoints[] = { @@ -104,11 +106,11 @@ constexpr SkPoint VerticalMiterDiamondPoints[] = { const int VerticalMiterDiamondPointCount = sizeof(VerticalMiterDiamondPoints) / sizeof(VerticalMiterDiamondPoints[0]); -constexpr SkScalar y_off_0 = RenderCenterY; -constexpr SkScalar y_off_u1 = RenderCenterY - Miter4DiamondOffsetY; +constexpr SkScalar y_off_0 = RenderCenterY + 0.5; +constexpr SkScalar y_off_u1 = x_off_0 - Miter4DiamondOffsetY; constexpr SkScalar y_off_u2 = y_off_u1 - Miter10DiamondOffsetY; constexpr SkScalar y_off_u3 = y_off_u2 - Miter10DiamondOffsetY; -constexpr SkScalar y_off_d1 = RenderCenterY + Miter4DiamondOffsetY; +constexpr SkScalar y_off_d1 = x_off_0 + Miter4DiamondOffsetY; constexpr SkScalar y_off_d2 = y_off_d1 + MiterExtremeDiamondOffsetY; constexpr SkScalar y_off_d3 = y_off_d2 + MiterExtremeDiamondOffsetY; const SkPoint HorizontalMiterDiamondPoints[] = { @@ -153,75 +155,67 @@ const int HorizontalMiterDiamondPointCount = // avoid false bounds overflow notifications. class BoundsTolerance { public: - BoundsTolerance() : BoundsTolerance(0, 0, 1, 1, 0, 0, 0) {} - BoundsTolerance(SkScalar bounds_pad_x, - SkScalar bounds_pad_y, - SkScalar scale_x, - SkScalar scale_y, - SkScalar absolute_pad_x, - SkScalar absolute_pad_y, - SkScalar discrete_offset) - : bounds_pad_x_(bounds_pad_x), - bounds_pad_y_(bounds_pad_y), - scale_x_(scale_x), - scale_y_(scale_y), - absolute_pad_x_(absolute_pad_x), - absolute_pad_y_(absolute_pad_y), - discrete_offset_(discrete_offset) {} + BoundsTolerance() = default; + BoundsTolerance(const BoundsTolerance&) = default; BoundsTolerance addBoundsPadding(SkScalar bounds_pad_x, SkScalar bounds_pad_y) const { - return {bounds_pad_x_ + bounds_pad_x, - bounds_pad_y_ + bounds_pad_y, - scale_x_, - scale_y_, - absolute_pad_x_, - absolute_pad_y_, - discrete_offset_}; + BoundsTolerance copy = BoundsTolerance(*this); + copy.bounds_pad_.offset(bounds_pad_x, bounds_pad_y); + return copy; } - BoundsTolerance addScale(SkScalar scale_x, SkScalar scale_y) const { - return {bounds_pad_x_, // - bounds_pad_y_, // - scale_x_ * scale_x, // - scale_y_ * scale_y, // - absolute_pad_x_, // - absolute_pad_y_, // - discrete_offset_}; + BoundsTolerance mulScale(SkScalar scale_x, SkScalar scale_y) const { + BoundsTolerance copy = BoundsTolerance(*this); + copy.scale_.fX *= scale_x; + copy.scale_.fY *= scale_y; + return copy; } BoundsTolerance addAbsolutePadding(SkScalar absolute_pad_x, SkScalar absolute_pad_y) const { - return {bounds_pad_x_, - bounds_pad_y_, - scale_x_, - scale_y_, - absolute_pad_x_ + absolute_pad_x, - absolute_pad_y_ + absolute_pad_y, - discrete_offset_}; + BoundsTolerance copy = BoundsTolerance(*this); + copy.absolute_pad_.offset(absolute_pad_x, absolute_pad_y); + return copy; } BoundsTolerance addDiscreteOffset(SkScalar discrete_offset) const { - return {bounds_pad_x_, - bounds_pad_y_, - scale_x_, - scale_y_, - absolute_pad_x_, - absolute_pad_y_, - discrete_offset_ + discrete_offset}; + BoundsTolerance copy = BoundsTolerance(*this); + copy.discrete_offset_ += discrete_offset; + return copy; } - bool overflows(SkISize pix_size, + BoundsTolerance clip(SkRect clip) const { + BoundsTolerance copy = BoundsTolerance(*this); + if (!copy.clip_.intersect(clip)) { + copy.clip_.setEmpty(); + } + return copy; + } + + static SkRect Scale(const SkRect& rect, const SkPoint& scales) { + SkScalar outset_x = rect.width() * (scales.fX - 1); + SkScalar outset_y = rect.height() * (scales.fY - 1); + return rect.makeOutset(outset_x, outset_y); + } + + bool overflows(SkIRect pix_bounds, int worst_bounds_pad_x, int worst_bounds_pad_y) const { - int scaled_bounds_pad_x = - std::ceil((pix_size.width() + bounds_pad_x_) * scale_x_); - int allowed_width = scaled_bounds_pad_x + absolute_pad_x_; - int scaled_bounds_pad_y = - std::ceil((pix_size.height() + bounds_pad_y_) * scale_y_); - int allowed_height = scaled_bounds_pad_y + absolute_pad_y_; - int allowed_pad_x = allowed_width - pix_size.width(); - int allowed_pad_y = allowed_height - pix_size.height(); + SkRect allowed = SkRect::Make(pix_bounds); + allowed.outset(bounds_pad_.fX, bounds_pad_.fY); + allowed = Scale(allowed, scale_); + allowed.outset(absolute_pad_.fX, absolute_pad_.fY); + if (!allowed.intersect(clip_)) { + allowed.setEmpty(); + } + SkIRect rounded = allowed.roundOut(); + int padLeft = std::max(0, pix_bounds.fLeft - rounded.fLeft); + int padTop = std::max(0, pix_bounds.fTop - rounded.fTop); + int padRight = std::max(0, pix_bounds.fRight - rounded.fRight); + int padBottom = std::max(0, pix_bounds.fBottom - rounded.fBottom); + int allowed_pad_x = std::max(padLeft, padRight); + int allowed_pad_y = std::max(padTop, padBottom); if (worst_bounds_pad_x > allowed_pad_x || worst_bounds_pad_y > allowed_pad_y) { FML_LOG(ERROR) << "allowed pad: " // @@ -234,18 +228,301 @@ class BoundsTolerance { SkScalar discrete_offset() const { return discrete_offset_; } private: - SkScalar bounds_pad_x_; - SkScalar bounds_pad_y_; - SkScalar scale_x_; - SkScalar scale_y_; - SkScalar absolute_pad_x_; - SkScalar absolute_pad_y_; - - SkScalar discrete_offset_; + SkPoint bounds_pad_ = {0, 0}; + SkPoint scale_ = {1, 1}; + SkPoint absolute_pad_ = {0, 0}; + SkRect clip_ = {-1E9, -1E9, 1E9, 1E9}; + + SkScalar discrete_offset_ = 0; }; -class CanvasCompareTester { +typedef const std::function CvSetup; +typedef const std::function CvRenderer; +typedef const std::function DlRenderer; +static void EmptyCvRenderer(SkCanvas*, const SkPaint&) {} +static void EmptyDlRenderer(DisplayListBuilder&) {} + +class RenderSurface { + public: + RenderSurface(sk_sp surface) : surface_(surface) {} + ~RenderSurface() { sk_free(addr_); } + + SkCanvas* canvas() { return surface_->getCanvas(); } + + const SkPixmap* pixmap() { + if (!pixmap_.addr()) { + SkImageInfo info = surface_->imageInfo(); + if (info.colorType() != kN32_SkColorType || + !surface_->peekPixels(&pixmap_)) { + info = SkImageInfo::MakeN32Premul(info.dimensions()); + addr_ = malloc(info.computeMinByteSize() * info.height()); + pixmap_.reset(info, addr_, info.minRowBytes()); + EXPECT_TRUE(surface_->readPixels(pixmap_, 0, 0)); + } + } + return &pixmap_; + } + + private: + sk_sp surface_; + SkPixmap pixmap_; + void* addr_ = nullptr; +}; + +class RenderEnvironment { + public: + static RenderEnvironment Make565() { + return RenderEnvironment(SkImageInfo::Make({1, 1}, kRGB_565_SkColorType, + kOpaque_SkAlphaType, nullptr)); + } + + static RenderEnvironment MakeN32() { + return RenderEnvironment(SkImageInfo::MakeN32Premul(1, 1)); + } + + RenderSurface MakeSurface(const SkColor bg = SK_ColorTRANSPARENT, + int width = TestWidth, + int height = TestHeight) const { + sk_sp surface = + SkSurface::MakeRaster(info_.makeWH(width, height)); + surface->getCanvas()->clear(bg); + return RenderSurface(surface); + } + + void init_ref(CvRenderer& cv_renderer, SkColor bg = SK_ColorTRANSPARENT) { + init_ref([=](SkCanvas*, SkPaint&) {}, cv_renderer, bg); + } + + void init_ref(CvSetup& cv_setup, + CvRenderer& cv_renderer, + SkColor bg = SK_ColorTRANSPARENT) { + ref_canvas()->clear(bg); + cv_setup(ref_canvas(), ref_paint_); + ref_matrix_ = ref_canvas()->getTotalMatrix(); + ref_clip_ = ref_canvas()->getDeviceClipBounds(); + cv_renderer(ref_canvas(), ref_paint_); + ref_pixmap_ = ref_surface_.pixmap(); + } + + SkCanvas* ref_canvas() { return ref_surface_.canvas(); } + const SkPaint& ref_paint() const { return ref_paint_; } + const SkMatrix& ref_matrix() const { return ref_matrix_; } + const SkIRect& ref_clip_bounds() const { return ref_clip_; } + const SkPixmap* ref_pixmap() const { return ref_pixmap_; } + private: + RenderEnvironment(const SkImageInfo& info) + : info_(info), ref_surface_(MakeSurface()) {} + + const SkImageInfo info_; + + SkPaint ref_paint_; + SkMatrix ref_matrix_; + SkIRect ref_clip_; + RenderSurface ref_surface_; + const SkPixmap* ref_pixmap_ = nullptr; +}; + +class TestParameters { + public: + TestParameters(const CvRenderer& cv_renderer, + const DlRenderer& dl_renderer, + const DisplayListAttributeFlags& flags) + : cv_renderer_(cv_renderer), dl_renderer_(dl_renderer), flags_(flags) {} + + bool uses_paint() const { return !flags_.ignores_paint(); } + + bool should_match(const RenderEnvironment& env, + const SkPaint& paint, + const SkMatrix& matrix, + const SkIRect& device_clip, + bool has_diff_clip, + bool has_mutating_save_layer) const { + if (has_mutating_save_layer) { + return false; + } + if (env.ref_clip_bounds() != device_clip || has_diff_clip) { + return false; + } + if (env.ref_matrix() != matrix && !flags_.is_flood()) { + return false; + } + if (flags_.ignores_paint()) { + return true; + } + const SkPaint& ref_paint = env.ref_paint(); + if (flags_.applies_anti_alias() && // + ref_paint.isAntiAlias() != paint.isAntiAlias()) { + return false; + } + if (flags_.applies_dither() && // + ref_paint.isDither() != paint.isDither()) { + return false; + } + if (flags_.applies_color() && // + ref_paint.getColor() != paint.getColor()) { + return false; + } + if (flags_.applies_alpha() && // + ref_paint.getAlpha() != paint.getAlpha()) { + return false; + } + if (flags_.applies_blend() && // + ref_paint.getBlender() != paint.getBlender()) { + return false; + } + if (flags_.applies_color_filter() && // + ref_paint.getColorFilter() != paint.getColorFilter()) { + return false; + } + if (flags_.applies_mask_filter() && // + ref_paint.getMaskFilter() != paint.getMaskFilter()) { + return false; + } + if (flags_.applies_image_filter() && // + ref_paint.getImageFilter() != paint.getImageFilter()) { + return false; + } + if (flags_.applies_shader() && // + ref_paint.getShader() != paint.getShader()) { + return false; + } + DisplayListSpecialGeometryFlags geo_flags = + flags_.WithPathEffect(paint.refPathEffect()); + if (flags_.applies_path_effect() && // + ref_paint.getPathEffect() != paint.getPathEffect()) { + SkPathEffect::DashInfo info; + if (paint.getPathEffect()->asADash(&info) != + SkPathEffect::kDash_DashType) { + return false; + } + if (!ignores_dashes()) { + return false; + } + } + bool is_stroked = flags_.is_stroked(ref_paint.getStyle()); + if (flags_.is_stroked(paint.getStyle()) != is_stroked) { + return false; + } + if (!is_stroked) { + return true; + } + if (ref_paint.getStrokeWidth() != paint.getStrokeWidth()) { + return false; + } + if (geo_flags.may_have_end_caps() && // + getCap(ref_paint, geo_flags) != getCap(paint, geo_flags)) { + return false; + } + if (geo_flags.may_have_joins()) { + if (ref_paint.getStrokeJoin() != paint.getStrokeJoin()) { + return false; + } + if (ref_paint.getStrokeJoin() == SkPaint::kMiter_Join) { + SkScalar ref_miter = ref_paint.getStrokeMiter(); + SkScalar test_miter = paint.getStrokeMiter(); + // miter limit < 1.4 affects right angles + if (geo_flags.may_have_acute_joins() || // + ref_miter < 1.4 || test_miter < 1.4) { + if (ref_miter != test_miter) { + return false; + } + } + } + } + return true; + } + + SkPaint::Cap getCap(const SkPaint& paint, + DisplayListSpecialGeometryFlags geo_flags) const { + SkPaint::Cap cap = paint.getStrokeCap(); + if (geo_flags.butt_cap_becomes_square() && cap == SkPaint::kButt_Cap) { + return SkPaint::kSquare_Cap; + } + return cap; + } + + const BoundsTolerance adjust(const BoundsTolerance& tolerance, + const SkPaint& paint, + const SkMatrix& matrix) const { + if (is_draw_text_blob() && tolerance.discrete_offset() > 0) { + // drawTextBlob needs just a little more leeway when using a + // discrete path effect. + return tolerance.addBoundsPadding(2, 2); + } + if (is_draw_line()) { + return lineAdjust(tolerance, paint, matrix); + } + if (is_draw_arc_center()) { + if (paint.getStyle() != SkPaint::kFill_Style && + paint.getStrokeJoin() == SkPaint::kMiter_Join) { + // the miter join at the center of an arc does not really affect + // its bounds in any of our test cases, but the bounds code needs + // to take it into account for the cases where it might, so we + // relax our tolerance to reflect the miter bounds padding. + SkScalar miter_pad = + paint.getStrokeMiter() * paint.getStrokeWidth() * 0.5f; + return tolerance.addBoundsPadding(miter_pad, miter_pad); + } + } + return tolerance; + } + + const BoundsTolerance lineAdjust(const BoundsTolerance& tolerance, + const SkPaint& paint, + const SkMatrix& matrix) const { + SkScalar adjust = 0.0; + SkScalar half_width = paint.getStrokeWidth() * 0.5f; + if (tolerance.discrete_offset() > 0) { + // When a discrete path effect is added, the bounds calculations must + // allow for miters in any direction, but a horizontal line will not + // have miters in the horizontal direction, similarly for vertical + // lines, and diagonal lines will have miters off at a "45 degree" + // angle that don't expand the bounds much at all. + // Also, the discrete offset will not move any points parallel with + // the line, so provide tolerance for both miters and offset. + adjust = + half_width * paint.getStrokeMiter() + tolerance.discrete_offset(); + } + DisplayListSpecialGeometryFlags geo_flags = + flags_.WithPathEffect(paint.refPathEffect()); + if (paint.getStrokeCap() == SkPaint::kButt_Cap && + !geo_flags.butt_cap_becomes_square()) { + adjust = std::max(adjust, half_width); + } + if (adjust == 0) { + return tolerance; + } + SkScalar hTolerance; + SkScalar vTolerance; + if (is_horizontal_line()) { + FML_DCHECK(!is_vertical_line()); + hTolerance = adjust; + vTolerance = 0; + } else if (is_vertical_line()) { + hTolerance = 0; + vTolerance = adjust; + } else { + // The perpendicular miters just do not impact the bounds of + // diagonal lines at all as they are aimed in the wrong direction + // to matter. So allow tolerance in both axes. + hTolerance = vTolerance = adjust; + } + BoundsTolerance new_tolerance = + tolerance.addBoundsPadding(hTolerance, vTolerance); + return new_tolerance; + } + + const CvRenderer& cv_renderer() const { return cv_renderer_; } + void render_to(SkCanvas* canvas, SkPaint& paint) const { + cv_renderer_(canvas, paint); + } + + const DlRenderer& dl_renderer() const { return dl_renderer_; } + void render_to(DisplayListBuilder& builder) const { // + dl_renderer_(builder); + } + // 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 @@ -253,334 +530,576 @@ class CanvasCompareTester { // 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 TestingDrawShadows; + bool is_draw_shadows() const { return is_draw_shadows_; } // The CPU renders nothing for drawVertices with a Blender. // See: https://bugs.chromium.org/p/skia/issues/detail?id=12200 - static bool TestingDrawVertices; + bool is_draw_vertices() const { return is_draw_vertices_; } // The CPU renders nothing for drawAtlas with a Blender. // See: https://bugs.chromium.org/p/skia/issues/detail?id=12199 - static bool TestingDrawAtlas; + bool is_draw_atlas() const { return is_draw_atlas_; } + // Tests that call drawTextBlob with an sk_ref paint attribute will cause + // those attributes to be stored in an internal Skia cache so we need + // to expect that the |sk_ref.unique()| call will fail in those cases. + // See: (TBD(flar) - file Skia bug) + bool is_draw_text_blob() const { return is_draw_text_blob_; } + bool is_draw_display_list() const { return is_draw_display_list_; } + bool is_draw_line() const { return is_draw_line_; } + bool is_draw_arc_center() const { return is_draw_arc_center_; } + bool is_horizontal_line() const { return is_horizontal_line_; } + bool is_vertical_line() const { return is_vertical_line_; } + bool ignores_dashes() const { return ignores_dashes_; } + + TestParameters& set_draw_shadows() { + is_draw_shadows_ = true; + return *this; + } + TestParameters& set_draw_vertices() { + is_draw_vertices_ = true; + return *this; + } + TestParameters& set_draw_text_blob() { + is_draw_text_blob_ = true; + return *this; + } + TestParameters& set_draw_atlas() { + is_draw_atlas_ = true; + return *this; + } + TestParameters& set_draw_display_list() { + is_draw_display_list_ = true; + return *this; + } + TestParameters& set_draw_line() { + is_draw_line_ = true; + return *this; + } + TestParameters& set_draw_arc_center() { + is_draw_arc_center_ = true; + return *this; + } + TestParameters& set_ignores_dashes() { + ignores_dashes_ = true; + return *this; + } + TestParameters& set_horizontal_line() { + is_horizontal_line_ = true; + return *this; + } + TestParameters& set_vertical_line() { + is_vertical_line_ = true; + return *this; + } - public: - typedef const std::function CvRenderer; - typedef const std::function DlRenderer; - typedef const std::function - ToleranceAdjuster; + private: + const CvRenderer& cv_renderer_; + const DlRenderer& dl_renderer_; + const DisplayListAttributeFlags& flags_; + + bool is_draw_shadows_ = false; + bool is_draw_vertices_ = false; + bool is_draw_text_blob_ = false; + bool is_draw_atlas_ = false; + bool is_draw_display_list_ = false; + bool is_draw_line_ = false; + bool is_draw_arc_center_ = false; + bool ignores_dashes_ = false; + bool is_horizontal_line_ = false; + bool is_vertical_line_ = false; +}; - static BoundsTolerance DefaultTolerance; - static const BoundsTolerance DefaultAdjuster(const BoundsTolerance& tolerance, - const SkPaint& paint, - const SkMatrix& matrix) { - return tolerance; +class CaseParameters { + public: + CaseParameters(std::string info) + : CaseParameters(info, EmptyCvRenderer, EmptyDlRenderer) {} + + CaseParameters(std::string info, CvSetup cv_setup, DlRenderer dl_setup) + : CaseParameters(info, + cv_setup, + dl_setup, + EmptyCvRenderer, + EmptyDlRenderer, + SK_ColorTRANSPARENT, + false, + false) {} + + CaseParameters(std::string info, + CvSetup cv_setup, + DlRenderer dl_setup, + CvRenderer cv_restore, + DlRenderer dl_restore, + SkColor bg, + bool has_diff_clip, + bool has_mutating_save_layer) + : info_(info), + bg_(bg), + cv_setup_(cv_setup), + dl_setup_(dl_setup), + cv_restore_(cv_restore), + dl_restore_(dl_restore), + has_diff_clip_(has_diff_clip), + has_mutating_save_layer_(has_mutating_save_layer) {} + + CaseParameters with_restore(CvRenderer cv_restore, + DlRenderer dl_restore, + bool mutating_layer) { + return CaseParameters(info_, cv_setup_, dl_setup_, cv_restore, dl_restore, + bg_, has_diff_clip_, mutating_layer); } - // All of the tests should eventually use this method except for the - // tests that call |RenderNoAttributes| because they do not use the - // SkPaint object. - // But there are a couple of conditions beyond our control which require - // the use of one of the variant methods below (|RenderShadows|, - // |RenderVertices|, |RenderAtlas|). - static void RenderAll(CvRenderer& cv_renderer, - DlRenderer& dl_renderer, - ToleranceAdjuster& adjuster = DefaultAdjuster, - const BoundsTolerance& tolerance = DefaultTolerance) { - RenderNoAttributes(cv_renderer, dl_renderer, adjuster, tolerance); - RenderWithAttributes(cv_renderer, dl_renderer, adjuster, tolerance); + CaseParameters with_bg(SkColor bg) { + return CaseParameters(info_, cv_setup_, dl_setup_, cv_restore_, dl_restore_, + bg, has_diff_clip_, has_mutating_save_layer_); } - // Used by the tests that render shadows to deal with a condition where - // we cannot recapture the shadow information from an SkCanvas stream - // due to the DrawShadowRec used by Skia is not properly exported. - // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125 - static void RenderShadows( - CvRenderer& cv_renderer, - DlRenderer& dl_renderer, - ToleranceAdjuster& adjuster = DefaultAdjuster, - const BoundsTolerance& tolerance = DefaultTolerance) { - TestingDrawShadows = true; - RenderNoAttributes(cv_renderer, dl_renderer, adjuster, tolerance); - TestingDrawShadows = false; + CaseParameters with_diff_clip() { + return CaseParameters(info_, cv_setup_, dl_setup_, cv_restore_, dl_restore_, + bg_, true, has_mutating_save_layer_); } - // Used by the tests that call drawVertices to avoid using an SkBlender - // during testing because the CPU renderer appears not to render anything. - // See: https://bugs.chromium.org/p/skia/issues/detail?id=12200 - static void RenderVertices(CvRenderer& cv_renderer, DlRenderer& dl_renderer) { - TestingDrawVertices = true; - RenderAll(cv_renderer, dl_renderer); - TestingDrawVertices = false; + std::string info() const { return info_; } + SkColor bg() const { return bg_; } + bool has_diff_clip() const { return has_diff_clip_; } + bool has_mutating_save_layer() const { return has_mutating_save_layer_; } + + CvSetup cv_setup() const { return cv_setup_; } + DlRenderer dl_setup() const { return dl_setup_; } + CvRenderer cv_restore() const { return cv_restore_; } + DlRenderer dl_restore() const { return dl_restore_; } + + const SkPaint render_to(SkCanvas* canvas, // + const TestParameters& testP) const { + SkPaint paint; + cv_setup_(canvas, paint); + testP.render_to(canvas, paint); + cv_restore_(canvas, paint); + return paint; } - // Used by the tests that call drawAtlas to avoid using an SkBlender - // during testing because the CPU renderer appears not to render anything. - // See: https://bugs.chromium.org/p/skia/issues/detail?id=12199 - static void RenderAtlas(CvRenderer& cv_renderer, DlRenderer& dl_renderer) { - TestingDrawAtlas = true; - RenderAll(cv_renderer, dl_renderer); - TestingDrawAtlas = false; + void render_to(DisplayListBuilder& builder, + const TestParameters& testP) const { + dl_setup_(builder); + testP.render_to(builder); + dl_restore_(builder); } - // Used by the tests that call a draw method that does not take a paint - // call. Those tests could use |RenderAll| but there would be a lot of - // wasted test runs that prepare an SkPaint that is never used. - static void RenderNoAttributes( - CvRenderer& cv_renderer, - DlRenderer& dl_renderer, - ToleranceAdjuster& adjuster = DefaultAdjuster, - const BoundsTolerance& tolerance = DefaultTolerance) { - RenderWith([=](SkCanvas*, SkPaint& p) {}, // - [=](DisplayListBuilder& d) {}, // - cv_renderer, dl_renderer, adjuster, tolerance, "Base Test"); - RenderWithTransforms(cv_renderer, dl_renderer, adjuster, tolerance); - RenderWithClips(cv_renderer, dl_renderer, adjuster, tolerance); - RenderWithSaveRestore(cv_renderer, dl_renderer, adjuster, tolerance); + private: + const std::string info_; + const SkColor bg_; + const CvSetup cv_setup_; + const DlRenderer dl_setup_; + const CvRenderer cv_restore_; + const DlRenderer dl_restore_; + const bool has_diff_clip_; + const bool has_mutating_save_layer_; +}; + +class CanvasCompareTester { + public: + static BoundsTolerance DefaultTolerance; + + static void RenderAll(const TestParameters& params, + const BoundsTolerance& tolerance = DefaultTolerance) { + RenderEnvironment env = RenderEnvironment::MakeN32(); + env.init_ref(params.cv_renderer()); + RenderWithTransforms(params, env, tolerance); + RenderWithClips(params, env, tolerance); + RenderWithSaveRestore(params, env, tolerance); + // Only test attributes if the canvas version uses the paint object + if (params.uses_paint()) { + RenderWithAttributes(params, env, tolerance); + } } - static void RenderWithSaveRestore(CvRenderer& cv_renderer, - DlRenderer& dl_renderer, - ToleranceAdjuster& adjuster, + static void RenderWithSaveRestore(const TestParameters& testP, + const RenderEnvironment& env, const BoundsTolerance& tolerance) { - SkRect clip = SkRect::MakeLTRB(0, 0, 10, 10); - SkRect rect = SkRect::MakeLTRB(5, 5, 15, 15); + SkRect clip = SkRect::MakeXYWH(RenderCenterX - 1, RenderCenterY - 1, 2, 2); + SkRect rect = SkRect::MakeXYWH(RenderCenterX, RenderCenterY, 10, 10); SkColor alpha_layer_color = SkColorSetARGB(0x7f, 0x00, 0xff, 0xff); SkColor default_color = SkPaint().getColor(); - CvRenderer cv_restored = [=](SkCanvas* cv, SkPaint& p) { - // Draw more than one primitive to disable peephole optimizations - cv->drawRect(RenderBounds.makeOutset(5, 5), p); - cv_renderer(cv, p); + CvRenderer cv_restore = [=](SkCanvas* cv, const SkPaint& p) { + // Draw another primitive to disable peephole optimizations + cv->drawRect(RenderBounds.makeOffset(500, 500), p); cv->restore(); }; - DlRenderer dl_restored = [=](DisplayListBuilder& b) { - // Draw more than one primitive to disable peephole optimizations - b.drawRect(RenderBounds.makeOutset(5, 5)); - dl_renderer(b); + DlRenderer dl_restore = [=](DisplayListBuilder& b) { + // Draw another primitive to disable peephole optimizations + b.drawRect(RenderBounds.makeOffset(500, 500)); b.restore(); }; - 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, SkClipOp::kIntersect, false); - b.drawRect(rect); - b.restore(); - }, - cv_renderer, dl_renderer, adjuster, tolerance, - "With prior save/clip/restore"); - RenderWith( - [=](SkCanvas* cv, SkPaint& p) { // - cv->saveLayer(nullptr, nullptr); - }, - [=](DisplayListBuilder& b) { // - b.saveLayer(nullptr, false); - }, - cv_restored, dl_restored, adjuster, tolerance, - "saveLayer no paint, no bounds"); - RenderWith( - [=](SkCanvas* cv, SkPaint& p) { // - cv->saveLayer(RenderBounds, nullptr); - }, - [=](DisplayListBuilder& b) { // - b.saveLayer(&RenderBounds, false); - }, - cv_restored, dl_restored, adjuster, tolerance, - "saveLayer no paint, with bounds"); - RenderWith( - [=](SkCanvas* cv, SkPaint& p) { - SkPaint save_p; - save_p.setColor(alpha_layer_color); - cv->saveLayer(nullptr, &save_p); - }, - [=](DisplayListBuilder& b) { - b.setColor(alpha_layer_color); - b.saveLayer(nullptr, true); - b.setColor(default_color); - }, - cv_restored, dl_restored, adjuster, tolerance, - "saveLayer with alpha, no bounds"); - RenderWith( - [=](SkCanvas* cv, SkPaint& p) { - SkPaint save_p; - save_p.setColor(alpha_layer_color); - cv->saveLayer(RenderBounds, &save_p); - }, - [=](DisplayListBuilder& b) { - b.setColor(alpha_layer_color); - b.saveLayer(&RenderBounds, true); - b.setColor(default_color); - }, - cv_restored, dl_restored, adjuster, tolerance, - "saveLayer with alpha and bounds"); + SkRect layer_bounds = RenderBounds.makeInset(15, 15); + RenderWith(testP, env, tolerance, + CaseParameters( + "With prior save/clip/restore", + [=](SkCanvas* cv, SkPaint& p) { + cv->save(); + cv->clipRect(clip, SkClipOp::kIntersect, false); + SkPaint p2; + cv->drawRect(rect, p2); + p2.setBlendMode(SkBlendMode::kClear); + cv->drawRect(rect, p2); + cv->restore(); + }, + [=](DisplayListBuilder& b) { + b.save(); + b.clipRect(clip, SkClipOp::kIntersect, false); + b.drawRect(rect); + b.setBlendMode(SkBlendMode::kClear); + b.drawRect(rect); + b.setBlendMode(SkBlendMode::kSrcOver); + b.restore(); + })); + RenderWith(testP, env, tolerance, + CaseParameters( + "saveLayer no paint, no bounds", + [=](SkCanvas* cv, SkPaint& p) { // + cv->saveLayer(nullptr, nullptr); + }, + [=](DisplayListBuilder& b) { // + b.saveLayer(nullptr, false); + }) + .with_restore(cv_restore, dl_restore, false)); + RenderWith(testP, env, tolerance, + CaseParameters( + "saveLayer no paint, with bounds", + [=](SkCanvas* cv, SkPaint& p) { // + cv->saveLayer(layer_bounds, nullptr); + }, + [=](DisplayListBuilder& b) { // + b.saveLayer(&layer_bounds, false); + }) + .with_restore(cv_restore, dl_restore, true)); + RenderWith(testP, env, tolerance, + CaseParameters( + "saveLayer with alpha, no bounds", + [=](SkCanvas* cv, SkPaint& p) { + SkPaint save_p; + save_p.setColor(alpha_layer_color); + cv->saveLayer(nullptr, &save_p); + }, + [=](DisplayListBuilder& b) { + b.setColor(alpha_layer_color); + b.saveLayer(nullptr, true); + b.setColor(default_color); + }) + .with_restore(cv_restore, dl_restore, true)); + RenderWith(testP, env, tolerance, + CaseParameters( + "saveLayer with alpha and bounds", + [=](SkCanvas* cv, SkPaint& p) { + SkPaint save_p; + save_p.setColor(alpha_layer_color); + cv->saveLayer(layer_bounds, &save_p); + }, + [=](DisplayListBuilder& b) { + b.setColor(alpha_layer_color); + b.saveLayer(&layer_bounds, true); + b.setColor(default_color); + }) + .with_restore(cv_restore, dl_restore, true)); { - sk_sp filter = - SkImageFilters::Blur(5.0, 5.0, SkTileMode::kDecal, nullptr, nullptr); - BoundsTolerance blur5Tolerance = tolerance.addBoundsPadding(4, 4); + // clang-format off + constexpr float rotate_alpha_color_matrix[20] = { + 0, 1, 0, 0 , 0, + 0, 0, 1, 0 , 0, + 1, 0, 0, 0 , 0, + 0, 0, 0, 0.5, 0, + }; + // clang-format on + sk_sp filter = + SkColorFilters::Matrix(rotate_alpha_color_matrix); + { + RenderWith(testP, env, tolerance, + CaseParameters( + "saveLayer ColorFilter, no bounds", + [=](SkCanvas* cv, SkPaint& p) { + SkPaint save_p; + save_p.setColorFilter(filter); + cv->saveLayer(nullptr, &save_p); + p.setStrokeWidth(5.0); + }, + [=](DisplayListBuilder& b) { + b.setColorFilter(filter); + b.saveLayer(nullptr, true); + b.setColorFilter(nullptr); + b.setStrokeWidth(5.0); + }) + .with_restore(cv_restore, dl_restore, true)); + } + EXPECT_TRUE(filter->unique()) + << "saveLayer ColorFilter, no bounds Cleanup"; { - RenderWith( - [=](SkCanvas* cv, SkPaint& p) { - SkPaint save_p; - save_p.setImageFilter(filter); - cv->saveLayer(nullptr, &save_p); - p.setStrokeWidth(5.0); - }, - [=](DisplayListBuilder& b) { - b.setImageFilter(filter); - b.saveLayer(nullptr, true); - b.setImageFilter(nullptr); - b.setStrokeWidth(5.0); - }, - cv_restored, dl_restored, adjuster, blur5Tolerance, - "saveLayer ImageFilter, no bounds"); + RenderWith(testP, env, tolerance, + CaseParameters( + "saveLayer ColorFilter and bounds", + [=](SkCanvas* cv, SkPaint& p) { + SkPaint save_p; + save_p.setColorFilter(filter); + cv->saveLayer(RenderBounds, &save_p); + p.setStrokeWidth(5.0); + }, + [=](DisplayListBuilder& b) { + b.setColorFilter(filter); + b.saveLayer(&RenderBounds, true); + b.setColorFilter(nullptr); + b.setStrokeWidth(5.0); + }) + .with_restore(cv_restore, dl_restore, true)); } - ASSERT_TRUE(filter->unique()) + EXPECT_TRUE(filter->unique()) + << "saveLayer ColorFilter and bounds Cleanup"; + } + { + sk_sp filter = SkImageFilters::Arithmetic( + 0.1, 0.1, 0.1, 0.25, true, nullptr, nullptr); + { + RenderWith(testP, env, tolerance, + CaseParameters( + "saveLayer ImageFilter, no bounds", + [=](SkCanvas* cv, SkPaint& p) { + SkPaint save_p; + save_p.setImageFilter(filter); + cv->saveLayer(nullptr, &save_p); + p.setStrokeWidth(5.0); + }, + [=](DisplayListBuilder& b) { + b.setImageFilter(filter); + b.saveLayer(nullptr, true); + b.setImageFilter(nullptr); + b.setStrokeWidth(5.0); + }) + .with_restore(cv_restore, dl_restore, true)); + } + EXPECT_TRUE(filter->unique()) << "saveLayer ImageFilter, no bounds Cleanup"; { - RenderWith( - [=](SkCanvas* cv, SkPaint& p) { - SkPaint save_p; - save_p.setImageFilter(filter); - cv->saveLayer(RenderBounds, &save_p); - p.setStrokeWidth(5.0); - }, - [=](DisplayListBuilder& b) { - b.setImageFilter(filter); - b.saveLayer(&RenderBounds, true); - b.setImageFilter(nullptr); - b.setStrokeWidth(5.0); - }, - cv_restored, dl_restored, adjuster, blur5Tolerance, - "saveLayer ImageFilter and bounds"); + RenderWith(testP, env, tolerance, + CaseParameters( + "saveLayer ImageFilter and bounds", + [=](SkCanvas* cv, SkPaint& p) { + SkPaint save_p; + save_p.setImageFilter(filter); + cv->saveLayer(RenderBounds, &save_p); + p.setStrokeWidth(5.0); + }, + [=](DisplayListBuilder& b) { + b.setImageFilter(filter); + b.saveLayer(&RenderBounds, true); + b.setImageFilter(nullptr); + b.setStrokeWidth(5.0); + }) + .with_restore(cv_restore, dl_restore, true)); } - ASSERT_TRUE(filter->unique()) + EXPECT_TRUE(filter->unique()) << "saveLayer ImageFilter and bounds Cleanup"; } } - static void RenderWithAttributes(CvRenderer& cv_renderer, - DlRenderer& dl_renderer, - ToleranceAdjuster& adjuster, + static void RenderWithAttributes(const TestParameters& testP, + const RenderEnvironment& env, const BoundsTolerance& tolerance) { - RenderWith([=](SkCanvas*, SkPaint& p) {}, // - [=](DisplayListBuilder& d) {}, // - cv_renderer, dl_renderer, adjuster, tolerance, "Base Test"); - - RenderWith([=](SkCanvas*, SkPaint& p) { p.setAntiAlias(true); }, // - [=](DisplayListBuilder& b) { b.setAntiAlias(true); }, // - cv_renderer, dl_renderer, adjuster, tolerance, - "AntiAlias == True"); - RenderWith([=](SkCanvas*, SkPaint& p) { p.setAntiAlias(false); }, // - [=](DisplayListBuilder& b) { b.setAntiAlias(false); }, // - cv_renderer, dl_renderer, adjuster, tolerance, - "AntiAlias == False"); - - RenderWith([=](SkCanvas*, SkPaint& p) { p.setDither(true); }, // - [=](DisplayListBuilder& b) { b.setDither(true); }, // - cv_renderer, dl_renderer, adjuster, tolerance, "Dither == True"); - RenderWith([=](SkCanvas*, SkPaint& p) { p.setDither(false); }, // - [=](DisplayListBuilder& b) { b.setDither(false); }, // - cv_renderer, dl_renderer, adjuster, tolerance, "Dither = False"); - - RenderWith([=](SkCanvas*, SkPaint& p) { p.setColor(SK_ColorBLUE); }, // - [=](DisplayListBuilder& b) { b.setColor(SK_ColorBLUE); }, // - cv_renderer, dl_renderer, adjuster, tolerance, "Color == Blue"); - RenderWith([=](SkCanvas*, SkPaint& p) { p.setColor(SK_ColorGREEN); }, // - [=](DisplayListBuilder& b) { b.setColor(SK_ColorGREEN); }, // - cv_renderer, dl_renderer, adjuster, tolerance, "Color == Green"); - - RenderWithStrokes(cv_renderer, dl_renderer, adjuster, tolerance); + RenderWith(testP, env, tolerance, CaseParameters("Defaults Test")); + + { + // CPU renderer with default line width of 0 does not show antialiasing + // for stroked primitives, so we make a new reference with a non-trivial + // stroke width to demonstrate the differences + RenderEnvironment aa_env = RenderEnvironment::MakeN32(); + // Tweak the bounds tolerance for the displacement of 1/10 of a pixel + const BoundsTolerance aa_tolerance = tolerance.addBoundsPadding(1, 1); + CvSetup cv_aa_setup = [=](SkCanvas* cv, SkPaint& p) { + cv->translate(0.1, 0.1); + p.setStrokeWidth(5.0); + }; + DlRenderer dl_aa_setup = [=](DisplayListBuilder& b) { + b.translate(0.1, 0.1); + b.setStrokeWidth(5.0); + }; + aa_env.init_ref(cv_aa_setup, testP.cv_renderer()); + RenderWith(testP, aa_env, aa_tolerance, + CaseParameters( + "AntiAlias == True", + [=](SkCanvas* cv, SkPaint& p) { + cv_aa_setup(cv, p); + p.setAntiAlias(true); + }, + [=](DisplayListBuilder& b) { + dl_aa_setup(b); + b.setAntiAlias(true); + })); + RenderWith(testP, aa_env, aa_tolerance, + CaseParameters( + "AntiAlias == False", + [=](SkCanvas* cv, SkPaint& p) { + cv_aa_setup(cv, p); + p.setAntiAlias(false); + }, + [=](DisplayListBuilder& b) { + dl_aa_setup(b); + b.setAntiAlias(false); + })); + } + + { + // The CPU renderer does not always dither for solid colors and we + // need to use a non-default color (default is black) on an opaque + // surface, so we use a shader instead of a color. Also, thin stroked + // primitives (mainly drawLine and drawPoints) do not show much + // dithering so we use a non-trivial stroke width as well. + RenderEnvironment dither_env = RenderEnvironment::Make565(); + SkColor dither_bg = SK_ColorBLACK; + CvSetup cv_dither_setup = [=](SkCanvas*, SkPaint& p) { + p.setShader(testImageShader); + p.setAlpha(0xf0); + p.setStrokeWidth(5.0); + }; + DlRenderer dl_dither_setup = [=](DisplayListBuilder& b) { + b.setShader(testImageShader); + b.setColor(SkColor(0xf0000000)); + b.setStrokeWidth(5.0); + }; + dither_env.init_ref(cv_dither_setup, testP.cv_renderer(), dither_bg); + RenderWith(testP, dither_env, tolerance, + CaseParameters( + "Dither == True", + [=](SkCanvas* cv, SkPaint& p) { + cv_dither_setup(cv, p); + p.setDither(true); + }, + [=](DisplayListBuilder& b) { + dl_dither_setup(b); + b.setDither(true); + }) + .with_bg(dither_bg)); + RenderWith(testP, dither_env, tolerance, + CaseParameters( + "Dither = False", + [=](SkCanvas* cv, SkPaint& p) { + cv_dither_setup(cv, p); + p.setDither(false); + }, + [=](DisplayListBuilder& b) { + dl_dither_setup(b); + b.setDither(false); + }) + .with_bg(dither_bg)); + } + EXPECT_TRUE(testImageShader->unique()) << "Dither Cleanup"; + + RenderWith(testP, env, tolerance, + CaseParameters( + "Color == Blue", + [=](SkCanvas*, SkPaint& p) { p.setColor(SK_ColorBLUE); }, + [=](DisplayListBuilder& b) { b.setColor(SK_ColorBLUE); })); + RenderWith(testP, env, tolerance, + CaseParameters( + "Color == Green", + [=](SkCanvas*, SkPaint& p) { p.setColor(SK_ColorGREEN); }, + [=](DisplayListBuilder& b) { b.setColor(SK_ColorGREEN); })); + + RenderWithStrokes(testP, env, tolerance); { // 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, adjuster, tolerance, "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, adjuster, tolerance, "Blend == DstIn", &bg); + RenderWith(testP, env, tolerance, + CaseParameters( + "Blend == SrcIn", + [=](SkCanvas*, SkPaint& p) { + p.setBlendMode(SkBlendMode::kSrcIn); + p.setColor(blendableColor); + }, + [=](DisplayListBuilder& b) { + b.setBlendMode(SkBlendMode::kSrcIn); + b.setColor(blendableColor); + }) + .with_bg(bg)); + RenderWith(testP, env, tolerance, + CaseParameters( + "Blend == DstIn", + [=](SkCanvas*, SkPaint& p) { + p.setBlendMode(SkBlendMode::kDstIn); + p.setColor(blendableColor); + }, + [=](DisplayListBuilder& b) { + b.setBlendMode(SkBlendMode::kDstIn); + b.setColor(blendableColor); + }) + .with_bg(bg)); } - if (!(TestingDrawAtlas || TestingDrawVertices)) { + if (!(testP.is_draw_atlas() || testP.is_draw_vertices())) { sk_sp blender = SkBlenders::Arithmetic(0.25, 0.25, 0.25, 0.25, false); { - RenderWith([=](SkCanvas*, SkPaint& p) { p.setBlender(blender); }, - [=](DisplayListBuilder& b) { b.setBlender(blender); }, - cv_renderer, dl_renderer, adjuster, tolerance, - "ImageFilter == Blender Arithmetic 0.25-false"); + RenderWith(testP, env, tolerance, + CaseParameters( + "ImageFilter == Blender Arithmetic 0.25-false", + [=](SkCanvas*, SkPaint& p) { p.setBlender(blender); }, + [=](DisplayListBuilder& b) { b.setBlender(blender); })); } - ASSERT_TRUE(blender->unique()) << "Blender Cleanup"; + EXPECT_TRUE(blender->unique()) << "Blender Cleanup"; blender = SkBlenders::Arithmetic(0.25, 0.25, 0.25, 0.25, true); { - RenderWith([=](SkCanvas*, SkPaint& p) { p.setBlender(blender); }, - [=](DisplayListBuilder& b) { b.setBlender(blender); }, - cv_renderer, dl_renderer, adjuster, tolerance, - "ImageFilter == Blender Arithmetic 0.25-true"); + RenderWith(testP, env, tolerance, + CaseParameters( + "ImageFilter == Blender Arithmetic 0.25-true", + [=](SkCanvas*, SkPaint& p) { p.setBlender(blender); }, + [=](DisplayListBuilder& b) { b.setBlender(blender); })); } - ASSERT_TRUE(blender->unique()) << "Blender Cleanup"; + EXPECT_TRUE(blender->unique()) << "Blender Cleanup"; } { + // Being able to see a blur requires some non-default attributes, + // like a non-trivial stroke width and a shader rather than a color + // (for drawPaint) so we create a new environment for these tests. + RenderEnvironment blur_env = RenderEnvironment::MakeN32(); + CvSetup cv_blur_setup = [=](SkCanvas*, SkPaint& p) { + p.setShader(testImageShader); + p.setStrokeWidth(5.0); + }; + DlRenderer dl_blur_setup = [=](DisplayListBuilder& b) { + b.setShader(testImageShader); + b.setStrokeWidth(5.0); + }; + blur_env.init_ref(cv_blur_setup, testP.cv_renderer()); sk_sp filter = SkImageFilters::Blur(5.0, 5.0, SkTileMode::kDecal, nullptr, nullptr); BoundsTolerance blur5Tolerance = tolerance.addBoundsPadding(4, 4); { - RenderWith( - [=](SkCanvas*, SkPaint& p) { - // Provide some non-trivial stroke size to get blurred - p.setStrokeWidth(5.0); - p.setImageFilter(filter); - }, - [=](DisplayListBuilder& b) { - // Provide some non-trivial stroke size to get blurred - b.setStrokeWidth(5.0); - b.setImageFilter(filter); - }, - cv_renderer, dl_renderer, adjuster, blur5Tolerance, - "ImageFilter == Decal Blur 5"); + RenderWith(testP, blur_env, blur5Tolerance, + CaseParameters( + "ImageFilter == Decal Blur 5", + [=](SkCanvas* cv, SkPaint& p) { + cv_blur_setup(cv, p); + p.setImageFilter(filter); + }, + [=](DisplayListBuilder& b) { + dl_blur_setup(b); + b.setImageFilter(filter); + })); } - ASSERT_TRUE(filter->unique()) << "ImageFilter Cleanup"; + EXPECT_TRUE(filter->unique()) << "ImageFilter Cleanup"; filter = SkImageFilters::Blur(5.0, 5.0, SkTileMode::kClamp, nullptr, nullptr); { - RenderWith( - [=](SkCanvas*, SkPaint& p) { - // Provide some non-trivial stroke size to get blurred - p.setStrokeWidth(5.0); - p.setImageFilter(filter); - }, - [=](DisplayListBuilder& b) { - // Provide some non-trivial stroke size to get blurred - b.setStrokeWidth(5.0); - b.setImageFilter(filter); - }, - cv_renderer, dl_renderer, adjuster, blur5Tolerance, - "ImageFilter == Clamp Blur 5"); + RenderWith(testP, blur_env, blur5Tolerance, + CaseParameters( + "ImageFilter == Clamp Blur 5", + [=](SkCanvas* cv, SkPaint& p) { + cv_blur_setup(cv, p); + p.setImageFilter(filter); + }, + [=](DisplayListBuilder& b) { + dl_blur_setup(b); + b.setImageFilter(filter); + })); } - ASSERT_TRUE(filter->unique()) << "ImageFilter Cleanup"; + EXPECT_TRUE(filter->unique()) << "ImageFilter Cleanup"; } { @@ -601,35 +1120,37 @@ class CanvasCompareTester { 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, adjuster, tolerance, - "ColorFilter == RotateRGB", &bg); + RenderWith(testP, env, tolerance, + CaseParameters( + "ColorFilter == RotateRGB", + [=](SkCanvas*, SkPaint& p) { + p.setColor(SK_ColorYELLOW); + p.setColorFilter(filter); + }, + [=](DisplayListBuilder& b) { + b.setColor(SK_ColorYELLOW); + b.setColorFilter(filter); + }) + .with_bg(bg)); } - ASSERT_TRUE(filter->unique()) << "ColorFilter == RotateRGB Cleanup"; + EXPECT_TRUE(filter->unique()) << "ColorFilter == RotateRGB 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, adjuster, tolerance, - "ColorFilter == Invert", &bg); + RenderWith(testP, env, tolerance, + CaseParameters( + "ColorFilter == Invert", + [=](SkCanvas*, SkPaint& p) { + p.setColor(SK_ColorYELLOW); + p.setColorFilter(filter); + }, + [=](DisplayListBuilder& b) { + b.setColor(SK_ColorYELLOW); + b.setInvertColors(true); + }) + .with_bg(bg)); } - ASSERT_TRUE(filter->unique()) << "ColorFilter == Invert Cleanup"; + EXPECT_TRUE(filter->unique()) << "ColorFilter == Invert Cleanup"; } { @@ -637,58 +1158,60 @@ class CanvasCompareTester { { // Discrete path effects need a stroke width for drawPointsAsPoints // to do something realistic - RenderWith( - [=](SkCanvas*, SkPaint& p) { - p.setStrokeWidth(5.0); - // A Discrete(3, 5) effect produces miters that are near - // maximal for a miter limit of 3.0. - p.setStrokeMiter(3.0); - p.setPathEffect(effect); - }, - [=](DisplayListBuilder& b) { - b.setStrokeWidth(5.0); - // A Discrete(3, 5) effect produces miters that are near - // maximal for a miter limit of 3.0. - b.setStrokeMiter(3.0); - b.setPathEffect(effect); - }, - cv_renderer, dl_renderer, adjuster, + // And a Discrete(3, 5) effect produces miters that are near + // maximal for a miter limit of 3.0. + BoundsTolerance discrete_tolerance = tolerance // register the discrete offset so adjusters can compensate .addDiscreteOffset(5) // the miters in the 3-5 discrete effect don't always fill // their conservative bounds, so tolerate a couple of pixels - .addBoundsPadding(2, 2), - "PathEffect == Discrete-3-5"); + .addBoundsPadding(2, 2); + RenderWith(testP, env, discrete_tolerance, + CaseParameters( + "PathEffect == Discrete-3-5", + [=](SkCanvas*, SkPaint& p) { + p.setStrokeWidth(5.0); + p.setStrokeMiter(3.0); + p.setPathEffect(effect); + }, + [=](DisplayListBuilder& b) { + b.setStrokeWidth(5.0); + b.setStrokeMiter(3.0); + b.setPathEffect(effect); + })); } - ASSERT_TRUE(effect->unique()) << "PathEffect == Discrete-3-5 Cleanup"; + EXPECT_TRUE(testP.is_draw_text_blob() || effect->unique()) + << "PathEffect == Discrete-3-5 Cleanup"; effect = SkDiscretePathEffect::Make(2, 3); { - RenderWith( - [=](SkCanvas*, SkPaint& p) { - p.setStrokeWidth(5.0); - // A Discrete(2, 3) effect produces miters that are near - // maximal for a miter limit of 2.5. - p.setStrokeMiter(2.5); - p.setPathEffect(effect); - }, - [=](DisplayListBuilder& b) { - b.setStrokeWidth(5.0); - // A Discrete(2, 3) effect produces miters that are near - // maximal for a miter limit of 2.5. - b.setStrokeMiter(2.5); - b.setPathEffect(effect); - }, - cv_renderer, dl_renderer, adjuster, + // Discrete path effects need a stroke width for drawPointsAsPoints + // to do something realistic + // And a Discrete(2, 3) effect produces miters that are near + // maximal for a miter limit of 2.5. + BoundsTolerance discrete_tolerance = tolerance // register the discrete offset so adjusters can compensate .addDiscreteOffset(3) // the miters in the 3-5 discrete effect don't always fill // their conservative bounds, so tolerate a couple of pixels - .addBoundsPadding(2, 2), - "PathEffect == Discrete-2-3"); + .addBoundsPadding(2, 2); + RenderWith(testP, env, discrete_tolerance, + CaseParameters( + "PathEffect == Discrete-2-3", + [=](SkCanvas*, SkPaint& p) { + p.setStrokeWidth(5.0); + p.setStrokeMiter(2.5); + p.setPathEffect(effect); + }, + [=](DisplayListBuilder& b) { + b.setStrokeWidth(5.0); + b.setStrokeMiter(2.5); + b.setPathEffect(effect); + })); } - ASSERT_TRUE(effect->unique()) << "PathEffect == Discrete-2-3 Cleanup"; + EXPECT_TRUE(testP.is_draw_text_blob() || effect->unique()) + << "PathEffect == Discrete-2-3 Cleanup"; } { @@ -696,37 +1219,35 @@ class CanvasCompareTester { SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5.0); BoundsTolerance blur5Tolerance = tolerance.addBoundsPadding(4, 4); { - RenderWith( - [=](SkCanvas*, SkPaint& p) { - // Provide some non-trivial stroke size to get blurred - p.setStrokeWidth(5.0); - p.setMaskFilter(filter); - }, - [=](DisplayListBuilder& b) { - // Provide some non-trivial stroke size to get blurred - b.setStrokeWidth(5.0); - b.setMaskFilter(filter); - }, - cv_renderer, dl_renderer, adjuster, blur5Tolerance, - "MaskFilter == Blur 5"); + // Stroked primitives need some non-trivial stroke size to be blurred + RenderWith(testP, env, blur5Tolerance, + CaseParameters( + "MaskFilter == Blur 5", + [=](SkCanvas*, SkPaint& p) { + p.setStrokeWidth(5.0); + p.setMaskFilter(filter); + }, + [=](DisplayListBuilder& b) { + b.setStrokeWidth(5.0); + b.setMaskFilter(filter); + })); } - ASSERT_TRUE(filter->unique()) << "MaskFilter == Blur 5 Cleanup"; + EXPECT_TRUE(testP.is_draw_text_blob() || filter->unique()) + << "MaskFilter == Blur 5 Cleanup"; { - RenderWith( - [=](SkCanvas*, SkPaint& p) { - // Provide some non-trivial stroke size to get blurred - p.setStrokeWidth(5.0); - p.setMaskFilter(filter); - }, - [=](DisplayListBuilder& b) { - // Provide some non-trivial stroke size to get blurred - b.setStrokeWidth(5.0); - b.setMaskBlurFilter(kNormal_SkBlurStyle, 5.0); - }, - cv_renderer, dl_renderer, adjuster, blur5Tolerance, - "MaskFilter == Blur(Normal, 5.0)"); + RenderWith(testP, env, blur5Tolerance, + CaseParameters( + "MaskFilter == Blur(Normal, 5.0)", + [=](SkCanvas*, SkPaint& p) { + p.setStrokeWidth(5.0); + p.setMaskFilter(filter); + }, + [=](DisplayListBuilder& b) { + b.setStrokeWidth(5.0); + b.setMaskBlurFilter(kNormal_SkBlurStyle, 5.0); + })); } - ASSERT_TRUE(filter->unique()) + EXPECT_TRUE(testP.is_draw_text_blob() || filter->unique()) << "MaskFilter == Blur(Normal, 5.0) Cleanup"; } @@ -737,7 +1258,7 @@ class CanvasCompareTester { }; SkColor colors[] = { SK_ColorGREEN, - SK_ColorYELLOW, + SkColorSetA(SK_ColorYELLOW, 0x7f), SK_ColorBLUE, }; float stops[] = { @@ -748,241 +1269,260 @@ class CanvasCompareTester { 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, adjuster, tolerance, - "LinearGradient GYB"); + RenderWith(testP, env, tolerance, + CaseParameters( + "LinearGradient GYB", + [=](SkCanvas*, SkPaint& p) { p.setShader(shader); }, + [=](DisplayListBuilder& b) { b.setShader(shader); })); } - ASSERT_TRUE(shader->unique()) << "LinearGradient GYB Cleanup"; + EXPECT_TRUE(shader->unique()) << "LinearGradient GYB Cleanup"; } } - static void RenderWithStrokes(CvRenderer& cv_renderer, - DlRenderer& dl_renderer, - ToleranceAdjuster& adjuster, + static void RenderWithStrokes(const TestParameters& testP, + const RenderEnvironment env, const BoundsTolerance& tolerance_in) { // The test cases were generated with geometry that will try to fill // out the various miter limits used for testing, but they can be off // by a couple of pixels so we will relax bounds testing for strokes by // a couple of pixels. BoundsTolerance tolerance = tolerance_in.addBoundsPadding(2, 2); - RenderWith( // - [=](SkCanvas*, SkPaint& p) { p.setStyle(SkPaint::kFill_Style); }, - [=](DisplayListBuilder& b) { b.setStyle(SkPaint::kFill_Style); }, - cv_renderer, dl_renderer, adjuster, tolerance, "Fill"); - RenderWith( - [=](SkCanvas*, SkPaint& p) { p.setStyle(SkPaint::kStroke_Style); }, - [=](DisplayListBuilder& b) { b.setStyle(SkPaint::kStroke_Style); }, - cv_renderer, dl_renderer, adjuster, tolerance, "Stroke + defaults"); - - RenderWith( - [=](SkCanvas*, SkPaint& p) { - p.setStyle(SkPaint::kFill_Style); - p.setStrokeWidth(10.0); - }, - [=](DisplayListBuilder& b) { - b.setStyle(SkPaint::kFill_Style); - b.setStrokeWidth(10.0); - }, - cv_renderer, dl_renderer, adjuster, tolerance, - "Fill + unnecessary StrokeWidth 10"); - - RenderWith( - [=](SkCanvas*, SkPaint& p) { - p.setStyle(SkPaint::kStroke_Style); - p.setStrokeWidth(10.0); - }, - [=](DisplayListBuilder& b) { - b.setStyle(SkPaint::kStroke_Style); - b.setStrokeWidth(10.0); - }, - cv_renderer, dl_renderer, adjuster, tolerance, "Stroke Width 10"); - RenderWith( - [=](SkCanvas*, SkPaint& p) { - p.setStyle(SkPaint::kStroke_Style); - p.setStrokeWidth(5.0); - }, - [=](DisplayListBuilder& b) { - b.setStyle(SkPaint::kStroke_Style); - b.setStrokeWidth(5.0); - }, - cv_renderer, dl_renderer, adjuster, tolerance, "Stroke Width 5"); - - RenderWith( - [=](SkCanvas*, SkPaint& p) { - p.setStyle(SkPaint::kStroke_Style); - p.setStrokeWidth(5.0); - p.setStrokeCap(SkPaint::kButt_Cap); - }, - [=](DisplayListBuilder& b) { - b.setStyle(SkPaint::kStroke_Style); - b.setStrokeWidth(5.0); - b.setStrokeCap(SkPaint::kButt_Cap); - }, - cv_renderer, dl_renderer, adjuster, tolerance, - "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.setStyle(SkPaint::kStroke_Style); - b.setStrokeWidth(5.0); - b.setStrokeCap(SkPaint::kRound_Cap); - }, - cv_renderer, dl_renderer, adjuster, tolerance, - "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.setStyle(SkPaint::kStroke_Style); - b.setStrokeWidth(5.0); - b.setStrokeJoin(SkPaint::kBevel_Join); - }, - cv_renderer, dl_renderer, adjuster, tolerance, - "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.setStyle(SkPaint::kStroke_Style); - b.setStrokeWidth(5.0); - b.setStrokeJoin(SkPaint::kRound_Join); - }, - cv_renderer, dl_renderer, adjuster, tolerance, - "Stroke Width 5, Round Join"); - - RenderWith( - [=](SkCanvas*, SkPaint& p) { - p.setStyle(SkPaint::kStroke_Style); - p.setStrokeWidth(5.0); - p.setStrokeMiter(10.0); - p.setStrokeJoin(SkPaint::kMiter_Join); - // AA helps fill in the peaks of the really thin miters better - // for bounds accuracy testing - p.setAntiAlias(true); - }, - [=](DisplayListBuilder& b) { - b.setStyle(SkPaint::kStroke_Style); - b.setStrokeWidth(5.0); - b.setStrokeMiter(10.0); - b.setStrokeJoin(SkPaint::kMiter_Join); - // AA helps fill in the peaks of the really thin miters better - // for bounds accuracy testing - b.setAntiAlias(true); - }, - cv_renderer, dl_renderer, adjuster, tolerance, - "Stroke Width 5, Miter 10"); - - 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.setStyle(SkPaint::kStroke_Style); - b.setStrokeWidth(5.0); - b.setStrokeMiter(0.0); - b.setStrokeJoin(SkPaint::kMiter_Join); - }, - cv_renderer, dl_renderer, adjuster, tolerance, - "Stroke Width 5, Miter 0"); + RenderWith(testP, env, tolerance, + CaseParameters( + "Fill", + [=](SkCanvas*, SkPaint& p) { // + p.setStyle(SkPaint::kFill_Style); + }, + [=](DisplayListBuilder& b) { // + b.setStyle(SkPaint::kFill_Style); + })); + RenderWith(testP, env, tolerance, + CaseParameters( + "Stroke + defaults", + [=](SkCanvas*, SkPaint& p) { // + p.setStyle(SkPaint::kStroke_Style); + }, + [=](DisplayListBuilder& b) { // + b.setStyle(SkPaint::kStroke_Style); + })); + + RenderWith(testP, env, tolerance, + CaseParameters( + "Fill + unnecessary StrokeWidth 10", + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kFill_Style); + p.setStrokeWidth(10.0); + }, + [=](DisplayListBuilder& b) { + b.setStyle(SkPaint::kFill_Style); + b.setStrokeWidth(10.0); + })); + + RenderEnvironment stroke_base_env = RenderEnvironment::MakeN32(); + CvSetup cv_stroke_setup = [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + }; + stroke_base_env.init_ref(cv_stroke_setup, testP.cv_renderer()); + + RenderWith(testP, stroke_base_env, tolerance, + CaseParameters( + "Stroke Width 10", + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(10.0); + }, + [=](DisplayListBuilder& b) { + b.setStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(10.0); + })); + RenderWith(testP, stroke_base_env, tolerance, + CaseParameters( + "Stroke Width 5", + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + }, + [=](DisplayListBuilder& b) { + b.setStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + })); + + RenderWith(testP, stroke_base_env, tolerance, + CaseParameters( + "Stroke Width 5, Square Cap", + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeCap(SkPaint::kSquare_Cap); + }, + [=](DisplayListBuilder& b) { + b.setStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setStrokeCap(SkPaint::kSquare_Cap); + })); + RenderWith(testP, stroke_base_env, tolerance, + CaseParameters( + "Stroke Width 5, Round Cap", + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeCap(SkPaint::kRound_Cap); + }, + [=](DisplayListBuilder& b) { + b.setStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setStrokeCap(SkPaint::kRound_Cap); + })); + + RenderWith(testP, stroke_base_env, tolerance, + CaseParameters( + "Stroke Width 5, Bevel Join", + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeJoin(SkPaint::kBevel_Join); + }, + [=](DisplayListBuilder& b) { + b.setStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setStrokeJoin(SkPaint::kBevel_Join); + })); + RenderWith(testP, stroke_base_env, tolerance, + CaseParameters( + "Stroke Width 5, Round Join", + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeJoin(SkPaint::kRound_Join); + }, + [=](DisplayListBuilder& b) { + b.setStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setStrokeJoin(SkPaint::kRound_Join); + })); + + RenderWith(testP, stroke_base_env, tolerance, + CaseParameters( + "Stroke Width 5, Miter 10", + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeMiter(10.0); + p.setStrokeJoin(SkPaint::kMiter_Join); + }, + [=](DisplayListBuilder& b) { + b.setStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setStrokeMiter(10.0); + b.setStrokeJoin(SkPaint::kMiter_Join); + })); + + RenderWith(testP, stroke_base_env, tolerance, + CaseParameters( + "Stroke Width 5, Miter 0", + [=](SkCanvas*, SkPaint& p) { + p.setStyle(SkPaint::kStroke_Style); + p.setStrokeWidth(5.0); + p.setStrokeMiter(0.0); + p.setStrokeJoin(SkPaint::kMiter_Join); + }, + [=](DisplayListBuilder& b) { + b.setStyle(SkPaint::kStroke_Style); + b.setStrokeWidth(5.0); + b.setStrokeMiter(0.0); + b.setStrokeJoin(SkPaint::kMiter_Join); + })); { const SkScalar TestDashes1[] = {29.0, 2.0}; const SkScalar TestDashes2[] = {17.0, 1.5}; sk_sp effect = SkDashPathEffect::Make(TestDashes1, 2, 0.0f); { - RenderWith( - [=](SkCanvas*, SkPaint& p) { - // Need stroke style to see dashing properly - p.setStyle(SkPaint::kStroke_Style); - // Provide some non-trivial stroke size to get dashed - p.setStrokeWidth(5.0); - p.setPathEffect(effect); - }, - [=](DisplayListBuilder& b) { - // Need stroke style to see dashing properly - b.setStyle(SkPaint::kStroke_Style); - // Provide some non-trivial stroke size to get dashed - b.setStrokeWidth(5.0); - b.setPathEffect(effect); - }, - cv_renderer, dl_renderer, adjuster, tolerance, - "PathEffect == Dash-29-2"); + RenderWith(testP, stroke_base_env, tolerance, + CaseParameters( + "PathEffect == Dash-29-2", + [=](SkCanvas*, SkPaint& p) { + // Need stroke style to see dashing properly + p.setStyle(SkPaint::kStroke_Style); + // Provide some non-trivial stroke size to get dashed + p.setStrokeWidth(5.0); + p.setPathEffect(effect); + }, + [=](DisplayListBuilder& b) { + // Need stroke style to see dashing properly + b.setStyle(SkPaint::kStroke_Style); + // Provide some non-trivial stroke size to get dashed + b.setStrokeWidth(5.0); + b.setPathEffect(effect); + })); } - ASSERT_TRUE(effect->unique()) << "PathEffect == Dash-29-2 Cleanup"; + EXPECT_TRUE(testP.is_draw_text_blob() || effect->unique()) + << "PathEffect == Dash-29-2 Cleanup"; effect = SkDashPathEffect::Make(TestDashes2, 2, 0.0f); { - RenderWith( - [=](SkCanvas*, SkPaint& p) { - // Need stroke style to see dashing properly - p.setStyle(SkPaint::kStroke_Style); - // Provide some non-trivial stroke size to get dashed - p.setStrokeWidth(5.0); - p.setPathEffect(effect); - }, - [=](DisplayListBuilder& b) { - // Need stroke style to see dashing properly - b.setStyle(SkPaint::kStroke_Style); - // Provide some non-trivial stroke size to get dashed - b.setStrokeWidth(5.0); - b.setPathEffect(effect); - }, - cv_renderer, dl_renderer, adjuster, tolerance, - "PathEffect == Dash-17-1.5"); + RenderWith(testP, stroke_base_env, tolerance, + CaseParameters( + "PathEffect == Dash-17-1.5", + [=](SkCanvas*, SkPaint& p) { + // Need stroke style to see dashing properly + p.setStyle(SkPaint::kStroke_Style); + // Provide some non-trivial stroke size to get dashed + p.setStrokeWidth(5.0); + p.setPathEffect(effect); + }, + [=](DisplayListBuilder& b) { + // Need stroke style to see dashing properly + b.setStyle(SkPaint::kStroke_Style); + // Provide some non-trivial stroke size to get dashed + b.setStrokeWidth(5.0); + b.setPathEffect(effect); + })); } - ASSERT_TRUE(effect->unique()) << "PathEffect == Dash-17-1.5 Cleanup"; + EXPECT_TRUE(testP.is_draw_text_blob() || effect->unique()) + << "PathEffect == Dash-17-1.5 Cleanup"; } } - static void RenderWithTransforms(CvRenderer& cv_renderer, - DlRenderer& dl_renderer, - ToleranceAdjuster& adjuster, + static void RenderWithTransforms(const TestParameters& testP, + const RenderEnvironment& env, const BoundsTolerance& tolerance) { - // If there is bounds padding for some conservative bounds overestimate - // then that padding will be even more pronounced in rotated or skewed - // coordinate systems so we scale the padding by about 5% to compensate. - BoundsTolerance skewed_tolerance = tolerance.addScale(1.05, 1.05); - RenderWith([=](SkCanvas* c, SkPaint&) { c->translate(5, 10); }, // - [=](DisplayListBuilder& b) { b.translate(5, 10); }, // - cv_renderer, dl_renderer, adjuster, tolerance, - "Translate 5, 10"); - RenderWith([=](SkCanvas* c, SkPaint&) { c->scale(1.05, 1.05); }, // - [=](DisplayListBuilder& b) { b.scale(1.05, 1.05); }, // - cv_renderer, dl_renderer, adjuster, tolerance, // - "Scale +5%"); - RenderWith([=](SkCanvas* c, SkPaint&) { c->rotate(5); }, // - [=](DisplayListBuilder& b) { b.rotate(5); }, // - cv_renderer, dl_renderer, adjuster, skewed_tolerance, - "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, adjuster, skewed_tolerance, // - "Skew 5%"); + // If the rendering method does not fill the corners of the original + // bounds, then the estimate under rotation or skewing will be off + // so we scale the padding by about 5% to compensate. + BoundsTolerance skewed_tolerance = tolerance.mulScale(1.05, 1.05); + RenderWith(testP, env, tolerance, + CaseParameters( + "Translate 5, 10", // + [=](SkCanvas* c, SkPaint&) { c->translate(5, 10); }, + [=](DisplayListBuilder& b) { b.translate(5, 10); })); + RenderWith(testP, env, tolerance, + CaseParameters( + "Scale +5%", // + [=](SkCanvas* c, SkPaint&) { c->scale(1.05, 1.05); }, + [=](DisplayListBuilder& b) { b.scale(1.05, 1.05); })); + RenderWith(testP, env, skewed_tolerance, + CaseParameters( + "Rotate 5 degrees", // + [=](SkCanvas* c, SkPaint&) { c->rotate(5); }, + [=](DisplayListBuilder& b) { b.rotate(5); })); + RenderWith(testP, env, skewed_tolerance, + CaseParameters( + "Skew 5%", // + [=](SkCanvas* c, SkPaint&) { c->skew(0.05, 0.05); }, + [=](DisplayListBuilder& b) { b.skew(0.05, 0.05); })); { SkMatrix tx = SkMatrix::MakeAll(1.10, 0.10, 5, // 0.05, 1.05, 10, // 0, 0, 1); - RenderWith([=](SkCanvas* c, SkPaint&) { c->concat(tx); }, // - [=](DisplayListBuilder& b) { - b.transform2DAffine(tx[0], tx[1], tx[2], // - tx[3], tx[4], tx[5]); - }, // - cv_renderer, dl_renderer, adjuster, skewed_tolerance, - "Transform 2D Affine"); + RenderWith(testP, env, skewed_tolerance, + CaseParameters( + "Transform 2D Affine", + [=](SkCanvas* c, SkPaint&) { c->concat(tx); }, + [=](DisplayListBuilder& b) { + b.transform2DAffine(tx[0], tx[1], tx[2], // + tx[3], tx[4], tx[5]); + })); } { SkM44 m44 = SkM44(1, 0, 0, RenderCenterX, // @@ -992,207 +1532,222 @@ class CanvasCompareTester { m44.preConcat(SkM44::Rotate({1, 0, 0}, M_PI / 60)); // 3 degrees around X m44.preConcat(SkM44::Rotate({0, 1, 0}, M_PI / 45)); // 4 degrees around Y m44.preTranslate(-RenderCenterX, -RenderCenterY); - RenderWith([=](SkCanvas* c, SkPaint&) { c->concat(m44); }, // - [=](DisplayListBuilder& b) { - b.transformFullPerspective( - m44.rc(0, 0), m44.rc(0, 1), m44.rc(0, 2), m44.rc(0, 3), - m44.rc(1, 0), m44.rc(1, 1), m44.rc(1, 2), m44.rc(1, 3), - m44.rc(2, 0), m44.rc(2, 1), m44.rc(2, 2), m44.rc(2, 3), - m44.rc(3, 0), m44.rc(3, 1), m44.rc(3, 2), m44.rc(3, 3)); - }, // - cv_renderer, dl_renderer, adjuster, skewed_tolerance, - "Transform Full Perspective"); + RenderWith( + testP, env, skewed_tolerance, + CaseParameters( + "Transform Full Perspective", + [=](SkCanvas* c, SkPaint&) { c->concat(m44); }, // + [=](DisplayListBuilder& b) { + b.transformFullPerspective( + m44.rc(0, 0), m44.rc(0, 1), m44.rc(0, 2), m44.rc(0, 3), + m44.rc(1, 0), m44.rc(1, 1), m44.rc(1, 2), m44.rc(1, 3), + m44.rc(2, 0), m44.rc(2, 1), m44.rc(2, 2), m44.rc(2, 3), + m44.rc(3, 0), m44.rc(3, 1), m44.rc(3, 2), m44.rc(3, 3)); + })); } } - static void RenderWithClips(CvRenderer& cv_renderer, - DlRenderer& dl_renderer, - ToleranceAdjuster& diff_adjuster, + static void RenderWithClips(const TestParameters& testP, + const RenderEnvironment& env, const BoundsTolerance& diff_tolerance) { SkRect r_clip = RenderBounds.makeInset(15.5, 15.5); - // For kIntersect clips we can be really strict on tolerance - ToleranceAdjuster& intersect_adjuster = DefaultAdjuster; - BoundsTolerance& intersect_tolerance = DefaultTolerance; - RenderWith( - [=](SkCanvas* c, SkPaint&) { - c->clipRect(r_clip, SkClipOp::kIntersect, false); - }, - [=](DisplayListBuilder& b) { - b.clipRect(r_clip, SkClipOp::kIntersect, false); - }, - cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, - "Hard ClipRect inset by 15.5"); - RenderWith( - [=](SkCanvas* c, SkPaint&) { - c->clipRect(r_clip, SkClipOp::kIntersect, true); - }, - [=](DisplayListBuilder& b) { - b.clipRect(r_clip, SkClipOp::kIntersect, true); - }, - cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, - "AntiAlias ClipRect inset by 15.5"); - RenderWith( - [=](SkCanvas* c, SkPaint&) { - c->clipRect(r_clip, SkClipOp::kDifference, false); - }, - [=](DisplayListBuilder& b) { - b.clipRect(r_clip, SkClipOp::kDifference, false); - }, - cv_renderer, dl_renderer, diff_adjuster, diff_tolerance, - "Hard ClipRect Diff, inset by 15.5"); + BoundsTolerance intersect_tolerance = diff_tolerance.clip(r_clip); + RenderWith(testP, env, intersect_tolerance, + CaseParameters( + "Hard ClipRect inset by 15.5", + [=](SkCanvas* c, SkPaint&) { + c->clipRect(r_clip, SkClipOp::kIntersect, false); + }, + [=](DisplayListBuilder& b) { + b.clipRect(r_clip, SkClipOp::kIntersect, false); + })); + RenderWith(testP, env, intersect_tolerance, + CaseParameters( + "AntiAlias ClipRect inset by 15.5", + [=](SkCanvas* c, SkPaint&) { + c->clipRect(r_clip, SkClipOp::kIntersect, true); + }, + [=](DisplayListBuilder& b) { + b.clipRect(r_clip, SkClipOp::kIntersect, true); + })); + RenderWith(testP, env, diff_tolerance, + CaseParameters( + "Hard ClipRect Diff, inset by 15.5", + [=](SkCanvas* c, SkPaint&) { + c->clipRect(r_clip, SkClipOp::kDifference, false); + }, + [=](DisplayListBuilder& b) { + b.clipRect(r_clip, SkClipOp::kDifference, false); + }) + .with_diff_clip()); 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, SkClipOp::kIntersect, false); - }, - cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, - "Hard ClipRRect inset by 15.5"); - RenderWith( - [=](SkCanvas* c, SkPaint&) { - c->clipRRect(rr_clip, SkClipOp::kIntersect, true); - }, - [=](DisplayListBuilder& b) { - b.clipRRect(rr_clip, SkClipOp::kIntersect, true); - }, - cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, - "AntiAlias ClipRRect inset by 15.5"); - RenderWith( - [=](SkCanvas* c, SkPaint&) { - c->clipRRect(rr_clip, SkClipOp::kDifference, false); - }, - [=](DisplayListBuilder& b) { - b.clipRRect(rr_clip, SkClipOp::kDifference, false); - }, - cv_renderer, dl_renderer, diff_adjuster, diff_tolerance, - "Hard ClipRRect Diff, inset by 15.5"); + RenderWith(testP, env, intersect_tolerance, + CaseParameters( + "Hard ClipRRect inset by 15.5", + [=](SkCanvas* c, SkPaint&) { + c->clipRRect(rr_clip, SkClipOp::kIntersect, false); + }, + [=](DisplayListBuilder& b) { + b.clipRRect(rr_clip, SkClipOp::kIntersect, false); + })); + RenderWith(testP, env, intersect_tolerance, + CaseParameters( + "AntiAlias ClipRRect inset by 15.5", + [=](SkCanvas* c, SkPaint&) { + c->clipRRect(rr_clip, SkClipOp::kIntersect, true); + }, + [=](DisplayListBuilder& b) { + b.clipRRect(rr_clip, SkClipOp::kIntersect, true); + })); + RenderWith(testP, env, diff_tolerance, + CaseParameters( + "Hard ClipRRect Diff, inset by 15.5", + [=](SkCanvas* c, SkPaint&) { + c->clipRRect(rr_clip, SkClipOp::kDifference, false); + }, + [=](DisplayListBuilder& b) { + b.clipRRect(rr_clip, SkClipOp::kDifference, false); + }) + .with_diff_clip()); 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, SkClipOp::kIntersect, false); - }, - cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, - "Hard ClipPath inset by 15.5"); - RenderWith( - [=](SkCanvas* c, SkPaint&) { - c->clipPath(path_clip, SkClipOp::kIntersect, true); - }, - [=](DisplayListBuilder& b) { - b.clipPath(path_clip, SkClipOp::kIntersect, true); - }, - cv_renderer, dl_renderer, intersect_adjuster, intersect_tolerance, - "AntiAlias ClipPath inset by 15.5"); - RenderWith( - [=](SkCanvas* c, SkPaint&) { - c->clipPath(path_clip, SkClipOp::kDifference, false); - }, - [=](DisplayListBuilder& b) { - b.clipPath(path_clip, SkClipOp::kDifference, false); - }, - cv_renderer, dl_renderer, diff_adjuster, diff_tolerance, - "Hard ClipPath Diff, inset by 15.5"); + RenderWith(testP, env, intersect_tolerance, + CaseParameters( + "Hard ClipPath inset by 15.5", + [=](SkCanvas* c, SkPaint&) { + c->clipPath(path_clip, SkClipOp::kIntersect, false); + }, + [=](DisplayListBuilder& b) { + b.clipPath(path_clip, SkClipOp::kIntersect, false); + })); + RenderWith(testP, env, intersect_tolerance, + CaseParameters( + "AntiAlias ClipPath inset by 15.5", + [=](SkCanvas* c, SkPaint&) { + c->clipPath(path_clip, SkClipOp::kIntersect, true); + }, + [=](DisplayListBuilder& b) { + b.clipPath(path_clip, SkClipOp::kIntersect, true); + })); + RenderWith(testP, env, diff_tolerance, + CaseParameters( + "Hard ClipPath Diff, inset by 15.5", + [=](SkCanvas* c, SkPaint&) { + c->clipPath(path_clip, SkClipOp::kDifference, false); + }, + [=](DisplayListBuilder& b) { + b.clipPath(path_clip, SkClipOp::kDifference, false); + }) + .with_diff_clip()); } - static sk_sp getSkPicture(CvRenderer& cv_setup, - CvRenderer& cv_render) { + static sk_sp getSkPicture(const TestParameters& testP, + const CaseParameters& caseP) { SkPictureRecorder recorder; SkRTreeFactory rtree_factory; SkCanvas* cv = recorder.beginRecording(TestBounds, &rtree_factory); - SkPaint p; - cv_setup(cv, p); - cv_render(cv, p); + caseP.render_to(cv, testP); return recorder.finishRecordingAsPicture(); } - static void RenderWith(CvRenderer& cv_setup, - DlRenderer& dl_setup, - CvRenderer& cv_render, - DlRenderer& dl_render, - ToleranceAdjuster& adjuster, + static void RenderWith(const TestParameters& testP, + const RenderEnvironment& env, const BoundsTolerance& tolerance_in, - const std::string info, - const SkColor* bg = nullptr) { - // surface1 is direct rendering via SkCanvas to SkSurface + const CaseParameters& caseP) { + // sk_surface is a 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); - const BoundsTolerance tolerance = adjuster( - tolerance_in, paint1, ref_surface->getCanvas()->getTotalMatrix()); - cv_render(ref_surface->getCanvas(), paint1); - sk_sp ref_picture = getSkPicture(cv_setup, cv_render); - SkRect ref_bounds = ref_picture->cullRect(); - 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 + " (Skia reference)", bg); + const std::string info = caseP.info(); + const SkColor bg = caseP.bg(); + RenderSurface sk_surface = env.MakeSurface(bg); + SkCanvas* sk_canvas = sk_surface.canvas(); + SkPaint sk_paint; + caseP.cv_setup()(sk_canvas, sk_paint); + SkMatrix sk_matrix = sk_canvas->getTotalMatrix(); + SkIRect sk_clip = sk_canvas->getDeviceClipBounds(); + const BoundsTolerance tolerance = + testP.adjust(tolerance_in, sk_paint, sk_canvas->getTotalMatrix()); + testP.render_to(sk_canvas, sk_paint); + caseP.cv_restore()(sk_canvas, sk_paint); + const sk_sp sk_picture = getSkPicture(testP, caseP); + SkRect sk_bounds = sk_picture->cullRect(); + const SkPixmap* sk_pixels = sk_surface.pixmap(); + ASSERT_EQ(sk_pixels->width(), TestWidth) << info; + ASSERT_EQ(sk_pixels->height(), TestHeight) << info; + ASSERT_EQ(sk_pixels->info().bytesPerPixel(), 4) << info; + checkPixels(sk_pixels, sk_bounds, info + " (Skia reference)", bg); + + if (testP.should_match(env, sk_paint, sk_matrix, sk_clip, + caseP.has_diff_clip(), + caseP.has_mutating_save_layer())) { + quickCompareToReference(env.ref_pixmap(), sk_pixels, true, + info + " (attribute has no effect)"); + } else { + quickCompareToReference(env.ref_pixmap(), sk_pixels, false, + info + " (attribute affects rendering)"); + } { // This sequence plays the provided equivalently constructed // DisplayList onto the SkCanvas of the surface // DisplayList => direct rendering - sk_sp test_surface = makeSurface(bg); + RenderSurface dl_surface = env.MakeSurface(bg); DisplayListBuilder builder(TestBounds); - dl_setup(builder); - dl_render(builder); + caseP.render_to(builder, testP); sk_sp display_list = builder.Build(); SkRect dl_bounds = display_list->bounds(); - if (!ref_bounds.roundOut().contains(dl_bounds)) { + if (!sk_bounds.roundOut().contains(dl_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) << "sk ref: " // + << sk_bounds.fLeft << ", " << sk_bounds.fTop << " => " + << sk_bounds.fRight << ", " << sk_bounds.fBottom; FML_LOG(ERROR) << "dl: " // << dl_bounds.fLeft << ", " << dl_bounds.fTop << " => " << dl_bounds.fRight << ", " << dl_bounds.fBottom; - if (!dl_bounds.contains(ref_bounds)) { + if (!dl_bounds.contains(sk_bounds)) { FML_LOG(ERROR) << "DisplayList bounds are too small!"; } - if (!ref_bounds.roundOut().contains(dl_bounds.roundOut())) { + if (!sk_bounds.roundOut().contains(dl_bounds.roundOut())) { FML_LOG(ERROR) << "###### DisplayList bounds larger than reference!"; } } - // 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. + // This EXPECT 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; - EXPECT_EQ(display_list->op_count(), ref_picture->approximateOpCount()) - << info; + // When we are drawing a DisplayList, the display_list built above + // will contain just a single drawDisplayList call plus the case + // attribute. The sk_picture will, however, contain a list of all + // of the embedded calls in the display list and so the op counts + // will not be equal between the two. + if (!testP.is_draw_display_list()) { + EXPECT_EQ(display_list->op_count(), sk_picture->approximateOpCount()) + << info; + } - display_list->RenderTo(test_surface->getCanvas()); - compareToReference(test_surface.get(), &ref_pixels, + display_list->RenderTo(dl_surface.canvas()); + compareToReference(dl_surface.pixmap(), sk_pixels, info + " (DisplayList built directly -> surface)", &dl_bounds, &tolerance, bg); } // This test cannot work if the rendering is using shadows until // we can access the Skia ShadowRec via public headers. - if (!TestingDrawShadows) { + if (!testP.is_draw_shadows()) { // 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); + RenderSurface cv_dl_surface = env.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, + caseP.render_to(&dl_recorder, testP); + dl_recorder.builder()->Build()->RenderTo(cv_dl_surface.canvas()); + compareToReference(cv_dl_surface.pixmap(), sk_pixels, info + " (Skia calls -> DisplayList -> surface)", - nullptr, nullptr, nullptr); + nullptr, nullptr, bg); } { @@ -1205,41 +1760,41 @@ class CanvasCompareTester { const int TestHeight2 = TestHeight * 2; const SkScalar TestScale = 2.0; - SkPictureRecorder sk_recorder; - SkCanvas* ref_canvas = sk_recorder.beginRecording(TestBounds); + SkPictureRecorder sk_x2_recorder; + SkCanvas* ref_canvas = sk_x2_recorder.beginRecording(TestBounds); SkPaint ref_paint; - cv_setup(ref_canvas, ref_paint); - cv_render(ref_canvas, ref_paint); - sk_sp ref_picture = sk_recorder.finishRecordingAsPicture(); - sk_sp ref_surface2 = makeSurface(bg, TestWidth2, TestHeight2); - SkCanvas* ref_canvas2 = ref_surface2->getCanvas(); - ref_canvas2->scale(TestScale, TestScale); - ref_picture->playback(ref_canvas2); - SkPixmap ref_pixels2; - ASSERT_TRUE(ref_surface2->peekPixels(&ref_pixels2)) << info; - ASSERT_EQ(ref_pixels2.width(), TestWidth2) << info; - ASSERT_EQ(ref_pixels2.height(), TestHeight2) << info; - ASSERT_EQ(ref_pixels2.info().bytesPerPixel(), 4) << info; - - DisplayListBuilder builder(TestBounds); - dl_setup(builder); - dl_render(builder); - sk_sp display_list = builder.Build(); - sk_sp test_surface = makeSurface(bg, TestWidth2, TestHeight2); - SkCanvas* test_canvas = test_surface->getCanvas(); - test_canvas->scale(TestScale, TestScale); - display_list->RenderTo(test_canvas); - compareToReference(test_surface.get(), &ref_pixels2, + caseP.render_to(ref_canvas, testP); + sk_sp ref_x2_picture = + sk_x2_recorder.finishRecordingAsPicture(); + RenderSurface ref_x2_surface = + env.MakeSurface(bg, TestWidth2, TestHeight2); + SkCanvas* ref_x2_canvas = ref_x2_surface.canvas(); + ref_x2_canvas->scale(TestScale, TestScale); + ref_x2_picture->playback(ref_x2_canvas); + const SkPixmap* ref_x2_pixels = ref_x2_surface.pixmap(); + ASSERT_EQ(ref_x2_pixels->width(), TestWidth2) << info; + ASSERT_EQ(ref_x2_pixels->height(), TestHeight2) << info; + ASSERT_EQ(ref_x2_pixels->info().bytesPerPixel(), 4) << info; + + DisplayListBuilder builder_x2(TestBounds); + caseP.render_to(builder_x2, testP); + sk_sp display_list_x2 = builder_x2.Build(); + RenderSurface test_x2_surface = + env.MakeSurface(bg, TestWidth2, TestHeight2); + SkCanvas* test_x2_canvas = test_x2_surface.canvas(); + test_x2_canvas->scale(TestScale, TestScale); + display_list_x2->RenderTo(test_x2_canvas); + compareToReference(test_x2_surface.pixmap(), ref_x2_pixels, info + " (Both rendered scaled 2x)", nullptr, nullptr, - nullptr, TestWidth2, TestHeight2, false); + bg, TestWidth2, TestHeight2, false); } } - static void checkPixels(SkPixmap* ref_pixels, + static void checkPixels(const SkPixmap* ref_pixels, SkRect ref_bounds, const std::string info, - const SkColor* bg) { - SkPMColor untouched = (bg) ? SkPreMultiplyColor(*bg) : 0; + const SkColor bg) { + SkPMColor untouched = SkPreMultiplyColor(bg); int pixels_touched = 0; int pixels_oob = 0; SkIRect i_bounds = ref_bounds.roundOut(); @@ -1258,21 +1813,45 @@ class CanvasCompareTester { ASSERT_GT(pixels_touched, 0) << info; } - static void compareToReference(SkSurface* test_surface, - SkPixmap* reference, + static void quickCompareToReference(const SkPixmap* ref_pixels, + const SkPixmap* test_pixels, + bool should_match, + const std::string info) { + ASSERT_EQ(test_pixels->width(), ref_pixels->width()) << info; + ASSERT_EQ(test_pixels->height(), ref_pixels->height()) << info; + ASSERT_EQ(test_pixels->info().bytesPerPixel(), 4) << info; + ASSERT_EQ(ref_pixels->info().bytesPerPixel(), 4) << info; + int pixels_different = 0; + for (int y = 0; y < test_pixels->height(); y++) { + const uint32_t* ref_row = ref_pixels->addr32(0, y); + const uint32_t* test_row = test_pixels->addr32(0, y); + for (int x = 0; x < test_pixels->width(); x++) { + if (ref_row[x] != test_row[x]) { + pixels_different++; + } + } + } + if (should_match) { + ASSERT_EQ(pixels_different, 0) << info; + } else { + ASSERT_NE(pixels_different, 0) << info; + } + } + + static void compareToReference(const SkPixmap* test_pixels, + const SkPixmap* ref_pixels, const std::string info, SkRect* bounds, const BoundsTolerance* tolerance, - const SkColor* bg, + const SkColor bg, int width = TestWidth, int height = TestHeight, bool printMismatches = false) { - SkPMColor untouched = (bg) ? SkPreMultiplyColor(*bg) : 0; - SkPixmap test_pixels; - ASSERT_TRUE(test_surface->peekPixels(&test_pixels)) << info; - ASSERT_EQ(test_pixels.width(), width) << info; - ASSERT_EQ(test_pixels.height(), height) << info; - ASSERT_EQ(test_pixels.info().bytesPerPixel(), 4) << info; + SkPMColor untouched = SkPreMultiplyColor(bg); + ASSERT_EQ(test_pixels->width(), width) << info; + ASSERT_EQ(test_pixels->height(), height) << info; + ASSERT_EQ(test_pixels->info().bytesPerPixel(), 4) << info; + ASSERT_EQ(ref_pixels->info().bytesPerPixel(), 4) << info; SkIRect i_bounds = bounds ? bounds->roundOut() : SkIRect::MakeWH(width, height); @@ -1283,8 +1862,8 @@ class CanvasCompareTester { int maxX = 0; int maxY = 0; for (int y = 0; y < height; y++) { - const uint32_t* ref_row = reference->addr32(0, y); - const uint32_t* test_row = test_pixels.addr32(0, y); + const uint32_t* ref_row = ref_pixels->addr32(0, y); + const uint32_t* test_row = test_pixels->addr32(0, y); for (int x = 0; x < width; x++) { if (bounds && test_row[x] != untouched) { if (minX > x) { @@ -1340,12 +1919,14 @@ class CanvasCompareTester { int pad_top = std::max(0, pixTop - bounds.fTop); int pad_right = std::max(0, bounds.fRight - pixRight); int pad_bottom = std::max(0, bounds.fBottom - pixBottom); - int pixWidth = pixRight - pixLeft; - int pixHeight = pixBottom - pixTop; - SkISize pixSize = SkISize::Make(pixWidth, pixHeight); + SkIRect pix_bounds = + SkIRect::MakeLTRB(pixLeft, pixTop, pixRight, pixBottom); + SkISize pix_size = pix_bounds.size(); + int pixWidth = pix_size.width(); + int pixHeight = pix_size.height(); int worst_pad_x = std::max(pad_left, pad_right); int worst_pad_y = std::max(pad_top, pad_bottom); - if (tolerance->overflows(pixSize, worst_pad_x, worst_pad_y)) { + if (tolerance->overflows(pix_bounds, worst_pad_x, worst_pad_y)) { FML_LOG(ERROR) << "Overflow for " << info; FML_LOG(ERROR) << "pix bounds[" // << pixLeft << ", " << pixTop << " => " // @@ -1360,7 +1941,7 @@ class CanvasCompareTester { << worst_pad_x << ", " << worst_pad_y // << " (" << (worst_pad_x * 100.0 / pixWidth) // << "%, " << (worst_pad_y * 100.0 / pixHeight) << "%)"; - int pix_area = pixSize.area(); + int pix_area = pix_size.area(); int dl_area = bounds.width() * bounds.height(); FML_LOG(ERROR) << "Total overflow area: " << (dl_area - pix_area) // << " (+" << (dl_area * 100.0 / pix_area - 100.0) << "%)"; @@ -1368,16 +1949,6 @@ class CanvasCompareTester { } } - static sk_sp makeSurface(const SkColor* bg, - int width = TestWidth, - int height = TestHeight) { - sk_sp surface = SkSurface::MakeRasterN32Premul(width, height); - if (bg) { - surface->getCanvas()->drawColor(*bg); - } - return surface; - } - static const sk_sp testImage; static const sk_sp makeTestImage() { sk_sp surface = @@ -1385,7 +1956,7 @@ class CanvasCompareTester { SkCanvas* canvas = surface->getCanvas(); SkPaint p0, p1; p0.setStyle(SkPaint::kFill_Style); - p0.setColor(SK_ColorGREEN); + p0.setColor(SkColorSetARGB(0xff, 0x00, 0xfe, 0x00)); // off-green p1.setStyle(SkPaint::kFill_Style); p1.setColor(SK_ColorBLUE); // Some pixels need some transparency for DstIn testing @@ -1400,6 +1971,8 @@ class CanvasCompareTester { return surface->makeImageSnapshot(); } + static const sk_sp testImageShader; + static sk_sp MakeTextBlob(std::string string, SkScalar font_height) { SkFont font(SkTypeface::MakeFromName("ahem", SkFontStyle::Normal()), @@ -1409,230 +1982,203 @@ class CanvasCompareTester { } }; -bool CanvasCompareTester::TestingDrawShadows = false; -bool CanvasCompareTester::TestingDrawVertices = false; -bool CanvasCompareTester::TestingDrawAtlas = false; BoundsTolerance CanvasCompareTester::DefaultTolerance = BoundsTolerance().addAbsolutePadding(1, 1); -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); - }); -} - -BoundsTolerance lineTolerance(const BoundsTolerance& tolerance, - const SkPaint& paint, - const SkMatrix& matrix, - bool is_horizontal, - bool is_vertical, - bool ignores_butt_cap) { - SkScalar adjust = 0.0; - SkScalar half_width = paint.getStrokeWidth() * 0.5f; - if (tolerance.discrete_offset() > 0) { - // When a discrete path effect is added, the bounds calculations must allow - // for miters in any direction, but a horizontal line will not have - // miters in the horizontal direction, similarly for vertical - // lines, and diagonal lines will have miters off at a "45 degree" angle - // that don't expand the bounds much at all. - // Also, the discrete offset will not move any points parallel with - // the line, so provide tolerance for both miters and offset. - adjust = half_width * paint.getStrokeMiter() + tolerance.discrete_offset(); - } - if (paint.getStrokeCap() == SkPaint::kButt_Cap && !ignores_butt_cap) { - adjust = std::max(adjust, half_width); - } - if (adjust == 0) { - return CanvasCompareTester::DefaultAdjuster(tolerance, paint, matrix); - } - SkScalar hTolerance; - SkScalar vTolerance; - if (is_horizontal) { - FML_DCHECK(!is_vertical); - hTolerance = adjust; - vTolerance = 0; - } else if (is_vertical) { - hTolerance = 0; - vTolerance = adjust; - } else { - // The perpendicular miters just do not impact the bounds of - // diagonal lines at all as they are aimed in the wrong direction - // to matter. So allow tolerance in both axes. - hTolerance = vTolerance = adjust; - } - BoundsTolerance new_tolerance = - tolerance.addBoundsPadding(hTolerance, vTolerance); - return CanvasCompareTester::DefaultAdjuster(new_tolerance, paint, matrix); -} - -// For drawing horizontal lines -BoundsTolerance hLineTolerance(const BoundsTolerance& tolerance, - const SkPaint& paint, - const SkMatrix& matrix) { - return lineTolerance(tolerance, paint, matrix, true, false, false); -} +const sk_sp CanvasCompareTester::testImage = makeTestImage(); +const sk_sp CanvasCompareTester::testImageShader = + makeTestImage()->makeShader(SkTileMode::kRepeat, + SkTileMode::kRepeat, + SkSamplingOptions()); + +// Eventually this bare bones testing::Test fixture will subsume the +// CanvasCompareTester and the TestParameters could then become just +// configuration calls made upon the fixture. +template +class DisplayListCanvasTestBase : public BaseT, protected DisplayListOpFlags { + public: + DisplayListCanvasTestBase() = default; -// For drawing vertical lines -BoundsTolerance vLineTolerance(const BoundsTolerance& tolerance, - const SkPaint& paint, - const SkMatrix& matrix) { - return lineTolerance(tolerance, paint, matrix, false, true, false); -} + private: + FML_DISALLOW_COPY_AND_ASSIGN(DisplayListCanvasTestBase); +}; +using DisplayListCanvas = DisplayListCanvasTestBase<::testing::Test>; -// For drawing diagonal lines -BoundsTolerance dLineTolerance(const BoundsTolerance& tolerance, - const SkPaint& paint, - const SkMatrix& matrix) { - return lineTolerance(tolerance, paint, matrix, false, false, false); +TEST_F(DisplayListCanvas, DrawPaint) { + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawPaint(paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPaint(); + }, + kDrawPaintFlags)); } -// For drawing individual points (drawPoints(Point_Mode)) -BoundsTolerance pointsTolerance(const BoundsTolerance& tolerance, - const SkPaint& paint, - const SkMatrix& matrix) { - return lineTolerance(tolerance, paint, matrix, false, false, true); +TEST_F(DisplayListCanvas, DrawColor) { + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { + canvas->drawColor(SK_ColorMAGENTA); + }, + [=](DisplayListBuilder& builder) { + builder.drawColor(SK_ColorMAGENTA, SkBlendMode::kSrcOver); + }, + kDrawColorFlags)); } -TEST(DisplayListCanvas, DrawDiagonalLines) { +TEST_F(DisplayListCanvas, DrawDiagonalLines) { SkPoint p1 = SkPoint::Make(RenderLeft, RenderTop); SkPoint p2 = SkPoint::Make(RenderRight, RenderBottom); SkPoint p3 = SkPoint::Make(RenderLeft, RenderBottom); SkPoint p4 = SkPoint::Make(RenderRight, RenderTop); - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - // Skia requires kStroke style on horizontal and vertical - // lines to get the bounds correct. - // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 - SkPaint p = paint; - p.setStyle(SkPaint::kStroke_Style); - canvas->drawLine(p1, p2, p); - canvas->drawLine(p3, p4, p); - }, - [=](DisplayListBuilder& builder) { // - builder.drawLine(p1, p2); - builder.drawLine(p3, p4); - }, - dLineTolerance); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + // Skia requires kStroke style on horizontal and vertical + // lines to get the bounds correct. + // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 + SkPaint p = paint; + p.setStyle(SkPaint::kStroke_Style); + canvas->drawLine(p1, p2, p); + canvas->drawLine(p3, p4, p); + }, + [=](DisplayListBuilder& builder) { // + builder.drawLine(p1, p2); + builder.drawLine(p3, p4); + }, + kDrawLineFlags) + .set_draw_line()); } -TEST(DisplayListCanvas, DrawHorizontalLine) { +TEST_F(DisplayListCanvas, DrawHorizontalLine) { SkPoint p1 = SkPoint::Make(RenderLeft, RenderCenterY); SkPoint p2 = SkPoint::Make(RenderRight, RenderCenterY); - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - // Skia requires kStroke style on horizontal and vertical - // lines to get the bounds correct. - // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 - SkPaint p = paint; - p.setStyle(SkPaint::kStroke_Style); - canvas->drawLine(p1, p2, p); - }, - [=](DisplayListBuilder& builder) { // - builder.drawLine(p1, p2); - }, - hLineTolerance); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + // Skia requires kStroke style on horizontal and vertical + // lines to get the bounds correct. + // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 + SkPaint p = paint; + p.setStyle(SkPaint::kStroke_Style); + canvas->drawLine(p1, p2, p); + }, + [=](DisplayListBuilder& builder) { // + builder.drawLine(p1, p2); + }, + kDrawHVLineFlags) + .set_draw_line() + .set_horizontal_line()); } -TEST(DisplayListCanvas, DrawVerticalLine) { +TEST_F(DisplayListCanvas, DrawVerticalLine) { SkPoint p1 = SkPoint::Make(RenderCenterX, RenderTop); SkPoint p2 = SkPoint::Make(RenderCenterY, RenderBottom); - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - // Skia requires kStroke style on horizontal and vertical - // lines to get the bounds correct. - // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 - SkPaint p = paint; - p.setStyle(SkPaint::kStroke_Style); - canvas->drawLine(p1, p2, p); - }, - [=](DisplayListBuilder& builder) { // - builder.drawLine(p1, p2); - }, - vLineTolerance); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + // Skia requires kStroke style on horizontal and vertical + // lines to get the bounds correct. + // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 + SkPaint p = paint; + p.setStyle(SkPaint::kStroke_Style); + canvas->drawLine(p1, p2, p); + }, + [=](DisplayListBuilder& builder) { // + builder.drawLine(p1, p2); + }, + kDrawHVLineFlags) + .set_draw_line() + .set_vertical_line()); } -TEST(DisplayListCanvas, DrawRect) { - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawRect(RenderBounds, paint); - }, - [=](DisplayListBuilder& builder) { // - builder.drawRect(RenderBounds); - }); +TEST_F(DisplayListCanvas, DrawRect) { + // Bounds are offset by 0.5 pixels to induce AA + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawRect(RenderBounds.makeOffset(0.5, 0.5), paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawRect(RenderBounds.makeOffset(0.5, 0.5)); + }, + kDrawRectFlags)); } -TEST(DisplayListCanvas, DrawOval) { +TEST_F(DisplayListCanvas, DrawOval) { SkRect rect = RenderBounds.makeInset(0, 10); - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawOval(rect, paint); - }, - [=](DisplayListBuilder& builder) { // - builder.drawOval(rect); - }); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawOval(rect, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawOval(rect); + }, + kDrawOvalFlags)); } -TEST(DisplayListCanvas, DrawCircle) { - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawCircle(TestCenter, RenderRadius, paint); - }, - [=](DisplayListBuilder& builder) { // - builder.drawCircle(TestCenter, RenderRadius); - }); +TEST_F(DisplayListCanvas, DrawCircle) { + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawCircle(TestCenter, RenderRadius, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawCircle(TestCenter, RenderRadius); + }, + kDrawCircleFlags)); } -TEST(DisplayListCanvas, DrawRRect) { +TEST_F(DisplayListCanvas, DrawRRect) { SkRRect rrect = SkRRect::MakeRectXY(RenderBounds, RenderCornerRadius, RenderCornerRadius); - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawRRect(rrect, paint); - }, - [=](DisplayListBuilder& builder) { // - builder.drawRRect(rrect); - }); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawRRect(rrect, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawRRect(rrect); + }, + kDrawRRectFlags)); } -TEST(DisplayListCanvas, DrawDRRect) { +TEST_F(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); - }); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawDRRect(outer, inner, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawDRRect(outer, inner); + }, + kDrawDRRectFlags)); } -TEST(DisplayListCanvas, DrawPath) { +TEST_F(DisplayListCanvas, DrawPath) { SkPath path; + + // unclosed lines to show some caps + path.moveTo(RenderLeft + 15, RenderTop + 15); + path.lineTo(RenderRight - 15, RenderBottom - 15); + path.moveTo(RenderLeft + 15, RenderBottom - 15); + path.lineTo(RenderRight - 15, RenderTop + 15); + path.addRect(RenderBounds); + + // miter diamonds horizontally and vertically to show miters path.moveTo(VerticalMiterDiamondPoints[0]); for (int i = 1; i < VerticalMiterDiamondPointCount; i++) { path.lineTo(VerticalMiterDiamondPoints[i]); @@ -1643,43 +2189,61 @@ TEST(DisplayListCanvas, DrawPath) { path.lineTo(HorizontalMiterDiamondPoints[i]); } path.close(); - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawPath(path, paint); - }, - [=](DisplayListBuilder& builder) { // - builder.drawPath(path); - }); + + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawPath(path, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPath(path); + }, + kDrawPathFlags)); } -TEST(DisplayListCanvas, DrawArc) { - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawArc(RenderBounds, 60, 330, false, paint); - }, - [=](DisplayListBuilder& builder) { // - builder.drawArc(RenderBounds, 60, 330, false); - }); +TEST_F(DisplayListCanvas, DrawArc) { + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawArc(RenderBounds, 60, 330, false, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawArc(RenderBounds, 60, 330, false); + }, + kDrawArcNoCenterFlags)); } -TEST(DisplayListCanvas, DrawArcCenter) { - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawArc(RenderBounds, 60, 330, true, paint); - }, - [=](DisplayListBuilder& builder) { // - builder.drawArc(RenderBounds, 60, 330, true); - }); +TEST_F(DisplayListCanvas, DrawArcCenter) { + // Center arcs that inscribe nearly a whole circle except for a small + // arc extent gap have 2 angles that may appear or disappear at the + // various miter limits tested (0, 4, and 10). + // The center angle here is 12 degrees which shows a miter + // at limit=10, but not 0 or 4. + // The arcs at the corners where it turns in towards the + // center show miters at 4 and 10, but not 0. + // Limit == 0, neither corner does a miter + // Limit == 4, only the edge "turn-in" corners miter + // Limit == 10, edge and center corners all miter + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawArc(RenderBounds, 60, 360 - 12, true, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawArc(RenderBounds, 60, 360 - 12, true); + }, + kDrawArcWithCenterFlags) + .set_draw_arc_center()); } -TEST(DisplayListCanvas, DrawPointsAsPoints) { +TEST_F(DisplayListCanvas, DrawPointsAsPoints) { // The +/- 16 points are designed to fall just inside the clips // that are tested against so we avoid lots of undrawn pixels // in the accumulated bounds. const SkScalar x0 = RenderLeft; const SkScalar x1 = RenderLeft + 16; const SkScalar x2 = (RenderLeft + RenderCenterX) * 0.5; - const SkScalar x3 = RenderCenterX; + const SkScalar x3 = RenderCenterX + 0.1; const SkScalar x4 = (RenderRight + RenderCenterX) * 0.5; const SkScalar x5 = RenderRight - 16; const SkScalar x6 = RenderRight; @@ -1687,7 +2251,7 @@ TEST(DisplayListCanvas, DrawPointsAsPoints) { const SkScalar y0 = RenderTop; const SkScalar y1 = RenderTop + 16; const SkScalar y2 = (RenderTop + RenderCenterY) * 0.5; - const SkScalar y3 = RenderCenterY; + const SkScalar y3 = RenderCenterY + 0.1; const SkScalar y4 = (RenderBottom + RenderCenterY) * 0.5; const SkScalar y5 = RenderBottom - 16; const SkScalar y6 = RenderBottom; @@ -1705,22 +2269,25 @@ TEST(DisplayListCanvas, DrawPointsAsPoints) { // clang-format on const int count = sizeof(points) / sizeof(points[0]); - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - // Skia requires kStroke style on horizontal and vertical - // lines to get the bounds correct. - // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 - SkPaint p = paint; - p.setStyle(SkPaint::kStroke_Style); - canvas->drawPoints(SkCanvas::kPoints_PointMode, count, points, p); - }, - [=](DisplayListBuilder& builder) { // - builder.drawPoints(SkCanvas::kPoints_PointMode, count, points); - }, - pointsTolerance); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + // Skia requires kStroke style on horizontal and vertical + // lines to get the bounds correct. + // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 + SkPaint p = paint; + p.setStyle(SkPaint::kStroke_Style); + canvas->drawPoints(SkCanvas::kPoints_PointMode, count, points, p); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPoints(SkCanvas::kPoints_PointMode, count, points); + }, + kDrawPointsAsPointsFlags) + .set_draw_line() + .set_ignores_dashes()); } -TEST(DisplayListCanvas, DrawPointsAsLines) { +TEST_F(DisplayListCanvas, DrawPointsAsLines) { const SkScalar x0 = RenderLeft + 1; const SkScalar x1 = RenderLeft + 16; const SkScalar x2 = RenderRight - 16; @@ -1752,21 +2319,23 @@ TEST(DisplayListCanvas, DrawPointsAsLines) { const int count = sizeof(points) / sizeof(points[0]); ASSERT_TRUE((count & 1) == 0); - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - // Skia requires kStroke style on horizontal and vertical - // lines to get the bounds correct. - // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 - SkPaint p = paint; - p.setStyle(SkPaint::kStroke_Style); - canvas->drawPoints(SkCanvas::kLines_PointMode, count, points, p); - }, - [=](DisplayListBuilder& builder) { // - builder.drawPoints(SkCanvas::kLines_PointMode, count, points); - }); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + // Skia requires kStroke style on horizontal and vertical + // lines to get the bounds correct. + // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 + SkPaint p = paint; + p.setStyle(SkPaint::kStroke_Style); + canvas->drawPoints(SkCanvas::kLines_PointMode, count, points, p); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPoints(SkCanvas::kLines_PointMode, count, points); + }, + kDrawPointsAsLinesFlags)); } -TEST(DisplayListCanvas, DrawPointsAsPolygon) { +TEST_F(DisplayListCanvas, DrawPointsAsPolygon) { const SkPoint points1[] = { // RenderBounds box with a diagonal SkPoint::Make(RenderLeft, RenderTop), @@ -1778,21 +2347,24 @@ TEST(DisplayListCanvas, DrawPointsAsPolygon) { }; const int count1 = sizeof(points1) / sizeof(points1[0]); - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - // Skia requires kStroke style on horizontal and vertical - // lines to get the bounds correct. - // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 - SkPaint p = paint; - p.setStyle(SkPaint::kStroke_Style); - canvas->drawPoints(SkCanvas::kPolygon_PointMode, count1, points1, p); - }, - [=](DisplayListBuilder& builder) { // - builder.drawPoints(SkCanvas::kPolygon_PointMode, count1, points1); - }); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + // Skia requires kStroke style on horizontal and vertical + // lines to get the bounds correct. + // See https://bugs.chromium.org/p/skia/issues/detail?id=12446 + SkPaint p = paint; + p.setStyle(SkPaint::kStroke_Style); + canvas->drawPoints(SkCanvas::kPolygon_PointMode, count1, points1, + p); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPoints(SkCanvas::kPolygon_PointMode, count1, points1); + }, + kDrawPointsAsPolygonFlags)); } -TEST(DisplayListCanvas, DrawVerticesWithColors) { +TEST_F(DisplayListCanvas, DrawVerticesWithColors) { // Cover as many sides of the box with only 6 vertices: // +----------+ // |xxxxxxxxxx| @@ -1818,17 +2390,21 @@ TEST(DisplayListCanvas, DrawVerticesWithColors) { }; const sk_sp vertices = SkVertices::MakeCopy( SkVertices::kTriangles_VertexMode, 6, pts, nullptr, colors); - CanvasCompareTester::RenderVertices( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawVertices(vertices.get(), SkBlendMode::kSrcOver, paint); - }, - [=](DisplayListBuilder& builder) { // - builder.drawVertices(vertices, SkBlendMode::kSrcOver); - }); - ASSERT_TRUE(vertices->unique()); + + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawVertices(vertices.get(), SkBlendMode::kSrcOver, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawVertices(vertices, SkBlendMode::kSrcOver); + }, + kDrawVerticesFlags) + .set_draw_vertices()); + EXPECT_TRUE(vertices->unique()); } -TEST(DisplayListCanvas, DrawVerticesWithImage) { +TEST_F(DisplayListCanvas, DrawVerticesWithImage) { // Cover as many sides of the box with only 6 vertices: // +----------+ // |xxxxxxxxxx| @@ -1858,226 +2434,267 @@ TEST(DisplayListCanvas, DrawVerticesWithImage) { }; const sk_sp vertices = SkVertices::MakeCopy( SkVertices::kTriangles_VertexMode, 6, pts, tex, nullptr); - const sk_sp shader = CanvasCompareTester::testImage->makeShader( - SkTileMode::kRepeat, SkTileMode::kRepeat, SkSamplingOptions()); - CanvasCompareTester::RenderVertices( - [=](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()); + + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + SkPaint v_paint = paint; + if (v_paint.getShader() == nullptr) { + v_paint.setShader(CanvasCompareTester::testImageShader); + } + canvas->drawVertices(vertices.get(), SkBlendMode::kSrcOver, + v_paint); + }, + [=](DisplayListBuilder& builder) { // + if (builder.getShader() == nullptr) { + builder.setShader(CanvasCompareTester::testImageShader); + } + builder.drawVertices(vertices, SkBlendMode::kSrcOver); + }, + kDrawVerticesFlags) + .set_draw_vertices()); + + EXPECT_TRUE(vertices->unique()); + EXPECT_TRUE(CanvasCompareTester::testImageShader->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, true); - }); +TEST_F(DisplayListCanvas, DrawImageNearest) { + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawImage(CanvasCompareTester::testImage, // + RenderLeft, RenderTop, + DisplayList::NearestSampling, &paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImage(CanvasCompareTester::testImage, + SkPoint::Make(RenderLeft, RenderTop), + DisplayList::NearestSampling, true); + }, + kDrawImageWithPaintFlags)); } -TEST(DisplayListCanvas, DrawImageNearestNoPaint) { - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawImage(CanvasCompareTester::testImage, RenderLeft, RenderTop, - DisplayList::NearestSampling, nullptr); - }, - [=](DisplayListBuilder& builder) { // - builder.drawImage(CanvasCompareTester::testImage, - SkPoint::Make(RenderLeft, RenderTop), - DisplayList::NearestSampling, false); - }); +TEST_F(DisplayListCanvas, DrawImageNearestNoPaint) { + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawImage(CanvasCompareTester::testImage, // + RenderLeft, RenderTop, + DisplayList::NearestSampling, nullptr); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImage(CanvasCompareTester::testImage, + SkPoint::Make(RenderLeft, RenderTop), + DisplayList::NearestSampling, false); + }, + kDrawImageFlags)); } -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, true); - }); +TEST_F(DisplayListCanvas, DrawImageLinear) { + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawImage(CanvasCompareTester::testImage, // + RenderLeft, RenderTop, + DisplayList::LinearSampling, &paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImage(CanvasCompareTester::testImage, + SkPoint::Make(RenderLeft, RenderTop), + DisplayList::LinearSampling, true); + }, + kDrawImageWithPaintFlags)); } -TEST(DisplayListCanvas, DrawImageRectNearest) { +TEST_F(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, true); - }); + SkRect dst = RenderBounds.makeInset(10.5, 10.5); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawImageRect(CanvasCompareTester::testImage, src, dst, + DisplayList::NearestSampling, &paint, + SkCanvas::kFast_SrcRectConstraint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImageRect(CanvasCompareTester::testImage, src, dst, + DisplayList::NearestSampling, true); + }, + kDrawImageRectWithPaintFlags)); } -TEST(DisplayListCanvas, DrawImageRectNearestNoPaint) { +TEST_F(DisplayListCanvas, DrawImageRectNearestNoPaint) { 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, nullptr, - SkCanvas::kFast_SrcRectConstraint); - }, - [=](DisplayListBuilder& builder) { // - builder.drawImageRect(CanvasCompareTester::testImage, src, dst, - DisplayList::NearestSampling, false); - }); + SkRect dst = RenderBounds.makeInset(10.5, 10.5); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawImageRect(CanvasCompareTester::testImage, src, dst, + DisplayList::NearestSampling, nullptr, + SkCanvas::kFast_SrcRectConstraint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImageRect(CanvasCompareTester::testImage, src, dst, + DisplayList::NearestSampling, false); + }, + kDrawImageRectFlags)); } -TEST(DisplayListCanvas, DrawImageRectLinear) { +TEST_F(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, true); - }); + SkRect dst = RenderBounds.makeInset(10.5, 10.5); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawImageRect(CanvasCompareTester::testImage, src, dst, + DisplayList::LinearSampling, &paint, + SkCanvas::kFast_SrcRectConstraint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawImageRect(CanvasCompareTester::testImage, src, dst, + DisplayList::LinearSampling, true); + }, + kDrawImageRectWithPaintFlags)); } -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, true); - }); +TEST_F(DisplayListCanvas, DrawImageNineNearest) { + SkIRect src = SkIRect::MakeWH(RenderWidth, RenderHeight).makeInset(25, 25); + SkRect dst = RenderBounds.makeInset(10.5, 10.5); + sk_sp image = CanvasCompareTester::testImage; + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { + canvas->drawImageNine(image.get(), src, dst, SkFilterMode::kNearest, + &paint); + }, + [=](DisplayListBuilder& builder) { + builder.drawImageNine(image, src, dst, SkFilterMode::kNearest, + true); + }, + kDrawImageNineWithPaintFlags)); } -TEST(DisplayListCanvas, DrawImageNineNearestNoPaint) { - 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, nullptr); - }, - [=](DisplayListBuilder& builder) { // - builder.drawImageNine(CanvasCompareTester::testImage, src, dst, - SkFilterMode::kNearest, false); - }); +TEST_F(DisplayListCanvas, DrawImageNineNearestNoPaint) { + SkIRect src = SkIRect::MakeWH(RenderWidth, RenderHeight).makeInset(25, 25); + SkRect dst = RenderBounds.makeInset(10.5, 10.5); + sk_sp image = CanvasCompareTester::testImage; + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { + canvas->drawImageNine(image.get(), src, dst, SkFilterMode::kNearest, + nullptr); + }, + [=](DisplayListBuilder& builder) { + builder.drawImageNine(image, src, dst, SkFilterMode::kNearest, + false); + }, + kDrawImageNineFlags)); } -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, true); - }); +TEST_F(DisplayListCanvas, DrawImageNineLinear) { + SkIRect src = SkIRect::MakeWH(RenderWidth, RenderHeight).makeInset(25, 25); + SkRect dst = RenderBounds.makeInset(10.5, 10.5); + sk_sp image = CanvasCompareTester::testImage; + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { + canvas->drawImageNine(image.get(), src, dst, SkFilterMode::kLinear, + &paint); + }, + [=](DisplayListBuilder& builder) { + builder.drawImageNine(image, src, dst, SkFilterMode::kLinear, true); + }, + kDrawImageNineWithPaintFlags)); } -TEST(DisplayListCanvas, DrawImageLatticeNearest) { - const SkRect dst = RenderBounds.makeInset(15.5, 10.5); +TEST_F(DisplayListCanvas, DrawImageLatticeNearest) { + const SkRect dst = RenderBounds.makeInset(10.5, 10.5); const int divX[] = { - (RenderLeft + RenderCenterX) / 2, - RenderCenterX, - (RenderRight + RenderCenterX) / 2, + RenderWidth * 1 / 4, + RenderWidth * 2 / 4, + RenderWidth * 3 / 4, }; const int divY[] = { - (RenderTop + RenderCenterY) / 2, - RenderCenterY, - (RenderBottom + RenderCenterY) / 2, + RenderHeight * 1 / 4, + RenderHeight * 2 / 4, + RenderHeight * 3 / 4, }; 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); - }); + sk_sp image = CanvasCompareTester::testImage; + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { + canvas->drawImageLattice(image.get(), lattice, dst, + SkFilterMode::kNearest, &paint); + }, + [=](DisplayListBuilder& builder) { + builder.drawImageLattice(image, lattice, dst, + SkFilterMode::kNearest, true); + }, + kDrawImageLatticeWithPaintFlags)); } -TEST(DisplayListCanvas, DrawImageLatticeNearestNoPaint) { - const SkRect dst = RenderBounds.makeInset(15.5, 10.5); +TEST_F(DisplayListCanvas, DrawImageLatticeNearestNoPaint) { + const SkRect dst = RenderBounds.makeInset(10.5, 10.5); const int divX[] = { - (RenderLeft + RenderCenterX) / 2, - RenderCenterX, - (RenderRight + RenderCenterX) / 2, + RenderWidth * 1 / 4, + RenderWidth * 2 / 4, + RenderWidth * 3 / 4, }; const int divY[] = { - (RenderTop + RenderCenterY) / 2, - RenderCenterY, - (RenderBottom + RenderCenterY) / 2, + RenderHeight * 1 / 4, + RenderHeight * 2 / 4, + RenderHeight * 3 / 4, }; 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, nullptr); - }, - [=](DisplayListBuilder& builder) { // - builder.drawImageLattice(CanvasCompareTester::testImage, lattice, // - dst, SkFilterMode::kNearest, false); - }); + sk_sp image = CanvasCompareTester::testImage; + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { + canvas->drawImageLattice(image.get(), lattice, dst, + SkFilterMode::kNearest, nullptr); + }, + [=](DisplayListBuilder& builder) { + builder.drawImageLattice(image, lattice, dst, + SkFilterMode::kNearest, false); + }, + kDrawImageLatticeFlags)); } -TEST(DisplayListCanvas, DrawImageLatticeLinear) { - const SkRect dst = RenderBounds.makeInset(15.5, 10.5); +TEST_F(DisplayListCanvas, DrawImageLatticeLinear) { + const SkRect dst = RenderBounds.makeInset(10.5, 10.5); const int divX[] = { - (RenderLeft + RenderCenterX) / 2, - RenderCenterX, - (RenderRight + RenderCenterX) / 2, + RenderWidth / 4, + RenderWidth / 2, + RenderWidth * 3 / 4, }; const int divY[] = { - (RenderTop + RenderCenterY) / 2, - RenderCenterY, - (RenderBottom + RenderCenterY) / 2, + RenderHeight / 4, + RenderHeight / 2, + RenderHeight * 3 / 4, }; 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); - }); + sk_sp image = CanvasCompareTester::testImage; + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { + canvas->drawImageLattice(image.get(), lattice, dst, + SkFilterMode::kLinear, &paint); + }, + [=](DisplayListBuilder& builder) { + builder.drawImageLattice(image, lattice, dst, SkFilterMode::kLinear, + true); + }, + kDrawImageLatticeWithPaintFlags)); } -TEST(DisplayListCanvas, DrawAtlasNearest) { +TEST_F(DisplayListCanvas, DrawAtlasNearest) { const SkRSXform xform[] = { // clang-format off { 1.2f, 0.0f, RenderLeft, RenderTop}, @@ -2101,20 +2718,22 @@ TEST(DisplayListCanvas, DrawAtlasNearest) { SK_ColorMAGENTA, }; const sk_sp image = CanvasCompareTester::testImage; - CanvasCompareTester::RenderAtlas( - [=](SkCanvas* canvas, SkPaint& paint) { - canvas->drawAtlas(image.get(), xform, tex, colors, 4, - SkBlendMode::kSrcOver, DisplayList::NearestSampling, - nullptr, &paint); - }, - [=](DisplayListBuilder& builder) { - builder.drawAtlas(image, xform, tex, colors, 4, // - SkBlendMode::kSrcOver, DisplayList::NearestSampling, - nullptr, true); - }); + const SkSamplingOptions sampling = DisplayList::NearestSampling; + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { + canvas->drawAtlas(image.get(), xform, tex, colors, 4, + SkBlendMode::kSrcOver, sampling, nullptr, &paint); + }, + [=](DisplayListBuilder& builder) { + builder.drawAtlas(image, xform, tex, colors, 4, // + SkBlendMode::kSrcOver, sampling, nullptr, true); + }, + kDrawAtlasWithPaintFlags) + .set_draw_atlas()); } -TEST(DisplayListCanvas, DrawAtlasNearestNoPaint) { +TEST_F(DisplayListCanvas, DrawAtlasNearestNoPaint) { const SkRSXform xform[] = { // clang-format off { 1.2f, 0.0f, RenderLeft, RenderTop}, @@ -2138,20 +2757,24 @@ TEST(DisplayListCanvas, DrawAtlasNearestNoPaint) { SK_ColorMAGENTA, }; const sk_sp image = CanvasCompareTester::testImage; - CanvasCompareTester::RenderAtlas( - [=](SkCanvas* canvas, SkPaint& paint) { - canvas->drawAtlas(image.get(), xform, tex, colors, 4, - SkBlendMode::kSrcOver, DisplayList::NearestSampling, - nullptr, nullptr); - }, - [=](DisplayListBuilder& builder) { - builder.drawAtlas(image, xform, tex, colors, 4, // - SkBlendMode::kSrcOver, DisplayList::NearestSampling, - nullptr, false); - }); + const SkSamplingOptions sampling = DisplayList::NearestSampling; + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { + canvas->drawAtlas(image.get(), xform, tex, colors, 4, + SkBlendMode::kSrcOver, sampling, // + nullptr, nullptr); + }, + [=](DisplayListBuilder& builder) { + builder.drawAtlas(image, xform, tex, colors, 4, // + SkBlendMode::kSrcOver, sampling, // + nullptr, false); + }, + kDrawAtlasFlags) + .set_draw_atlas()); } -TEST(DisplayListCanvas, DrawAtlasLinear) { +TEST_F(DisplayListCanvas, DrawAtlasLinear) { const SkRSXform xform[] = { // clang-format off { 1.2f, 0.0f, RenderLeft, RenderTop}, @@ -2175,17 +2798,19 @@ TEST(DisplayListCanvas, DrawAtlasLinear) { SK_ColorMAGENTA, }; const sk_sp image = CanvasCompareTester::testImage; - CanvasCompareTester::RenderAtlas( - [=](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, true); - }); + const SkSamplingOptions sampling = DisplayList::LinearSampling; + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { + canvas->drawAtlas(image.get(), xform, tex, colors, 2, // + SkBlendMode::kSrcOver, sampling, nullptr, &paint); + }, + [=](DisplayListBuilder& builder) { + builder.drawAtlas(image, xform, tex, colors, 2, // + SkBlendMode::kSrcOver, sampling, nullptr, true); + }, + kDrawAtlasWithPaintFlags) + .set_draw_atlas()); } sk_sp makeTestPicture() { @@ -2193,91 +2818,86 @@ sk_sp makeTestPicture() { SkCanvas* cv = recorder.beginRecording(RenderBounds); SkPaint p; p.setStyle(SkPaint::kFill_Style); - SkScalar x_coords[] = { - RenderLeft, - RenderCenterX, - RenderRight, - }; - SkScalar y_coords[] = { - RenderTop, - RenderCenterY, - RenderBottom, - }; - SkColor colors[][2] = { - { - SK_ColorRED, - SK_ColorBLUE, - }, - { - SK_ColorGREEN, - SK_ColorYELLOW, - }, - }; - for (int j = 0; j < 2; j++) { - for (int i = 0; i < 2; i++) { - SkRect rect = { - x_coords[i], - y_coords[j], - x_coords[i + 1], - y_coords[j + 1], - }; - p.setColor(colors[i][j]); - cv->drawOval(rect, p); - } - } + p.setColor(SK_ColorRED); + cv->drawRect({RenderLeft, RenderTop, RenderCenterX, RenderCenterY}, p); + p.setColor(SK_ColorBLUE); + cv->drawRect({RenderCenterX, RenderTop, RenderRight, RenderCenterY}, p); + p.setColor(SK_ColorGREEN); + cv->drawRect({RenderLeft, RenderCenterY, RenderCenterX, RenderBottom}, p); + p.setColor(SK_ColorYELLOW); + cv->drawRect({RenderCenterX, RenderCenterY, RenderRight, RenderBottom}, p); return recorder.finishRecordingAsPicture(); } -TEST(DisplayListCanvas, DrawPicture) { +TEST_F(DisplayListCanvas, DrawPicture) { sk_sp picture = makeTestPicture(); - CanvasCompareTester::RenderNoAttributes( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawPicture(picture, nullptr, nullptr); - }, - [=](DisplayListBuilder& builder) { // - builder.drawPicture(picture, nullptr, false); - }); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawPicture(picture, nullptr, nullptr); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPicture(picture, nullptr, false); + }, + kDrawPictureFlags)); } -TEST(DisplayListCanvas, DrawPictureWithMatrix) { +TEST_F(DisplayListCanvas, DrawPictureWithMatrix) { sk_sp picture = makeTestPicture(); 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); - }); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawPicture(picture, &matrix, nullptr); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPicture(picture, &matrix, false); + }, + kDrawPictureFlags)); } -TEST(DisplayListCanvas, DrawPictureWithPaint) { +TEST_F(DisplayListCanvas, DrawPictureWithPaint) { sk_sp picture = makeTestPicture(); - CanvasCompareTester::RenderAll( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawPicture(picture, nullptr, &paint); - }, - [=](DisplayListBuilder& builder) { // - builder.drawPicture(picture, nullptr, true); - }); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawPicture(picture, nullptr, &paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawPicture(picture, nullptr, true); + }, + kDrawPictureWithPaintFlags)); } -TEST(DisplayListCanvas, DrawDisplayList) { +sk_sp makeTestDisplayList() { DisplayListBuilder builder; builder.setStyle(SkPaint::kFill_Style); + builder.setColor(SK_ColorRED); + builder.drawRect({RenderLeft, RenderTop, RenderCenterX, RenderCenterY}); 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); - }); + builder.drawRect({RenderCenterX, RenderTop, RenderRight, RenderCenterY}); + builder.setColor(SK_ColorGREEN); + builder.drawRect({RenderLeft, RenderCenterY, RenderCenterX, RenderBottom}); + builder.setColor(SK_ColorYELLOW); + builder.drawRect({RenderCenterX, RenderCenterY, RenderRight, RenderBottom}); + return builder.Build(); } -TEST(DisplayListCanvas, DrawTextBlob) { +TEST_F(DisplayListCanvas, DrawDisplayList) { + sk_sp display_list = makeTestDisplayList(); + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + display_list->RenderTo(canvas); + }, + [=](DisplayListBuilder& builder) { // + builder.drawDisplayList(display_list); + }, + kDrawDisplayListFlags) + .set_draw_display_list()); +} + +TEST_F(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. @@ -2286,38 +2906,31 @@ TEST(DisplayListCanvas, DrawTextBlob) { #endif // OS_FUCHSIA sk_sp blob = CanvasCompareTester::MakeTextBlob("Testing", RenderHeight * 0.33f); - SkScalar RenderY1_3 = RenderTop + RenderHeight * 0.33; - SkScalar RenderY2_3 = RenderTop + RenderHeight * 0.66; - CanvasCompareTester::RenderNoAttributes( - [=](SkCanvas* canvas, SkPaint& paint) { // - canvas->drawTextBlob(blob, RenderLeft, RenderY1_3, paint); - canvas->drawTextBlob(blob, RenderLeft, RenderY2_3, paint); - canvas->drawTextBlob(blob, RenderLeft, RenderBottom, paint); - }, - [=](DisplayListBuilder& builder) { // - builder.drawTextBlob(blob, RenderLeft, RenderY1_3); - builder.drawTextBlob(blob, RenderLeft, RenderY2_3); - builder.drawTextBlob(blob, RenderLeft, RenderBottom); - }, - CanvasCompareTester::DefaultAdjuster, + SkScalar RenderY1_3 = RenderTop + RenderHeight * 0.3; + SkScalar RenderY2_3 = RenderTop + RenderHeight * 0.6; + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + canvas->drawTextBlob(blob, RenderLeft, RenderY1_3, paint); + canvas->drawTextBlob(blob, RenderLeft, RenderY2_3, paint); + canvas->drawTextBlob(blob, RenderLeft, RenderBottom, paint); + }, + [=](DisplayListBuilder& builder) { // + builder.drawTextBlob(blob, RenderLeft, RenderY1_3); + builder.drawTextBlob(blob, RenderLeft, RenderY2_3); + builder.drawTextBlob(blob, RenderLeft, RenderBottom); + }, + kDrawTextBlobFlags) + .set_draw_text_blob(), // From examining the bounds differential for the "Default" case, the - // SkTextBlob adds a padding of ~31 on the left, ~30 on the right, - // ~12 on top and ~8 on the bottom, so we add 32h & 13v allowed + // SkTextBlob adds a padding of ~32 on the left, ~30 on the right, + // ~12 on top and ~8 on the bottom, so we add 33h & 13v allowed // padding to the tolerance - CanvasCompareTester::DefaultTolerance.addBoundsPadding(32, 13)); -} - -const BoundsTolerance shadowTolerance(const BoundsTolerance& tolerance, - const SkPaint& paint, - const SkMatrix& matrix) { - // Shadow primitives could use just a little more horizontal bounds - // tolerance when drawn with a perspective transform. - return CanvasCompareTester::DefaultAdjuster( - matrix.hasPerspective() ? tolerance.addScale(1.04, 1.0) : tolerance, - paint, matrix); + CanvasCompareTester::DefaultTolerance.addBoundsPadding(33, 13)); + EXPECT_TRUE(blob->unique()); } -TEST(DisplayListCanvas, DrawShadow) { +TEST_F(DisplayListCanvas, DrawShadow) { SkPath path; path.addRoundRect( { @@ -2330,19 +2943,21 @@ TEST(DisplayListCanvas, DrawShadow) { const SkColor color = SK_ColorDKGRAY; const SkScalar elevation = 5; - CanvasCompareTester::RenderShadows( - [=](SkCanvas* canvas, SkPaint& paint) { // - PhysicalShapeLayer::DrawShadow(canvas, path, color, elevation, false, - 1.0); - }, - [=](DisplayListBuilder& builder) { // - builder.drawShadow(path, color, elevation, false, 1.0); - }, - shadowTolerance, + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + PhysicalShapeLayer::DrawShadow(canvas, path, color, elevation, + false, 1.0); + }, + [=](DisplayListBuilder& builder) { // + builder.drawShadow(path, color, elevation, false, 1.0); + }, + kDrawShadowFlags) + .set_draw_shadows(), CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3)); } -TEST(DisplayListCanvas, DrawShadowTransparentOccluder) { +TEST_F(DisplayListCanvas, DrawShadowTransparentOccluder) { SkPath path; path.addRoundRect( { @@ -2355,19 +2970,21 @@ TEST(DisplayListCanvas, DrawShadowTransparentOccluder) { const SkColor color = SK_ColorDKGRAY; const SkScalar elevation = 5; - CanvasCompareTester::RenderShadows( - [=](SkCanvas* canvas, SkPaint& paint) { // - PhysicalShapeLayer::DrawShadow(canvas, path, color, elevation, true, - 1.0); - }, - [=](DisplayListBuilder& builder) { // - builder.drawShadow(path, color, elevation, true, 1.0); - }, - shadowTolerance, + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + PhysicalShapeLayer::DrawShadow(canvas, path, color, elevation, true, + 1.0); + }, + [=](DisplayListBuilder& builder) { // + builder.drawShadow(path, color, elevation, true, 1.0); + }, + kDrawShadowFlags) + .set_draw_shadows(), CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3)); } -TEST(DisplayListCanvas, DrawShadowDpr) { +TEST_F(DisplayListCanvas, DrawShadowDpr) { SkPath path; path.addRoundRect( { @@ -2380,15 +2997,17 @@ TEST(DisplayListCanvas, DrawShadowDpr) { const SkColor color = SK_ColorDKGRAY; const SkScalar elevation = 5; - CanvasCompareTester::RenderShadows( - [=](SkCanvas* canvas, SkPaint& paint) { // - PhysicalShapeLayer::DrawShadow(canvas, path, color, elevation, false, - 1.5); - }, - [=](DisplayListBuilder& builder) { // - builder.drawShadow(path, color, elevation, false, 1.5); - }, - shadowTolerance, + CanvasCompareTester::RenderAll( // + TestParameters( + [=](SkCanvas* canvas, const SkPaint& paint) { // + PhysicalShapeLayer::DrawShadow(canvas, path, color, elevation, + false, 1.5); + }, + [=](DisplayListBuilder& builder) { // + builder.drawShadow(path, color, elevation, false, 1.5); + }, + kDrawShadowFlags) + .set_draw_shadows(), CanvasCompareTester::DefaultTolerance.addBoundsPadding(3, 3)); } diff --git a/flow/display_list_unittests.cc b/flow/display_list_unittests.cc index 1c590c7caf7f..6228440a0f22 100644 --- a/flow/display_list_unittests.cc +++ b/flow/display_list_unittests.cc @@ -272,97 +272,99 @@ struct DisplayListInvocationGroup { std::vector allGroups = { { "SetAntiAlias", { - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setAntiAlias(false);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setAntiAlias(true);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setAntiAlias(false);}}, } }, { "SetDither", { - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setDither(false);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setDither(true);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setDither(false);}}, } }, { "SetInvertColors", { - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setInvertColors(false);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setInvertColors(true);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setInvertColors(false);}}, } }, { "SetStrokeCap", { - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeCap(SkPaint::kButt_Cap);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeCap(SkPaint::kRound_Cap);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeCap(SkPaint::kSquare_Cap);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setStrokeCap(SkPaint::kButt_Cap);}}, } }, { "SetStrokeJoin", { {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeJoin(SkPaint::kBevel_Join);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeJoin(SkPaint::kRound_Join);}}, - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeJoin(SkPaint::kMiter_Join);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setStrokeJoin(SkPaint::kMiter_Join);}}, } }, { "SetStyle", { - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStyle(SkPaint::kFill_Style);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStyle(SkPaint::kStroke_Style);}}, + {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStyle(SkPaint::kStrokeAndFill_Style);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setStyle(SkPaint::kFill_Style);}}, } }, { "SetStrokeWidth", { - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeWidth(0.0);}}, + {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeWidth(1.0);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeWidth(5.0);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setStrokeWidth(0.0);}}, } }, { "SetStrokeMiter", { {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeMiter(0.0);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setStrokeMiter(5.0);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setStrokeMiter(4.0);}}, } }, { "SetColor", { {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setColor(SK_ColorGREEN);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setColor(SK_ColorBLUE);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setColor(SK_ColorBLACK);}}, } }, - { "SetBlendMode", { + { "SetBlendModeOrBlender", { {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setBlendMode(SkBlendMode::kSrcIn);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setBlendMode(SkBlendMode::kDstIn);}}, - } - }, - { "SetBlender", { - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setBlender(nullptr);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setBlender(TestBlender1);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setBlender(TestBlender2);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setBlender(TestBlender3);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setBlendMode(SkBlendMode::kSrcOver);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setBlender(nullptr);}}, } }, { "SetShader", { - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setShader(nullptr);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setShader(TestShader1);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setShader(TestShader2);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setShader(TestShader3);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setShader(nullptr);}}, } }, { "SetImageFilter", { - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(nullptr);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(TestImageFilter1);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(TestImageFilter2);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setImageFilter(nullptr);}}, } }, { "SetColorFilter", { - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setColorFilter(nullptr);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setColorFilter(TestColorFilter1);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setColorFilter(TestColorFilter2);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setColorFilter(nullptr);}}, } }, { "SetPathEffect", { - {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setPathEffect(nullptr);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setPathEffect(TestPathEffect1);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setPathEffect(TestPathEffect2);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setPathEffect(nullptr);}}, } }, { "SetMaskFilter", { - {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setMaskFilter(nullptr);}}, {0, 16, 0, 0, [](DisplayListBuilder& b) {b.setMaskFilter(TestMaskFilter);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setMaskBlurFilter(kNormal_SkBlurStyle, 3.0);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setMaskBlurFilter(kNormal_SkBlurStyle, 5.0);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setMaskBlurFilter(kSolid_SkBlurStyle, 3.0);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setMaskBlurFilter(kInner_SkBlurStyle, 3.0);}}, {0, 8, 0, 0, [](DisplayListBuilder& b) {b.setMaskBlurFilter(kOuter_SkBlurStyle, 3.0);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.setMaskFilter(nullptr);}}, } }, { "Save(Layer)+Restore", { @@ -376,33 +378,34 @@ std::vector allGroups = { }, { "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);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.translate(0, 0);}}, } }, { "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);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.scale(1, 1);}}, } }, { "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);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.rotate(0);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.rotate(360);}}, } }, { "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);}}, + {0, 0, 0, 0, [](DisplayListBuilder& b) {b.skew(0, 0);}}, } }, { "Transform2DAffine", { @@ -833,7 +836,9 @@ TEST(DisplayList, SingleOpDisplayListsCompareToEachOther) { 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) { + if (i == j || + (group.variants[i].is_empty() && group.variants[j].is_empty())) { + // They are the same variant, or both variants are NOPs ASSERT_EQ(listA->op_count(false), listB->op_count(false)) << desc; ASSERT_EQ(listA->bytes(false), listB->bytes(false)) << desc; ASSERT_EQ(listA->op_count(true), listB->op_count(true)) << desc; @@ -852,6 +857,31 @@ TEST(DisplayList, SingleOpDisplayListsCompareToEachOther) { } } +TEST(DisplayList, FullRotationsAreNop) { + DisplayListBuilder builder; + builder.rotate(0); + builder.rotate(360); + builder.rotate(720); + builder.rotate(1080); + builder.rotate(1440); + sk_sp dl = builder.Build(); + ASSERT_EQ(dl->bytes(false), sizeof(DisplayList)); + ASSERT_EQ(dl->bytes(true), sizeof(DisplayList)); + ASSERT_EQ(dl->op_count(false), 0); + ASSERT_EQ(dl->op_count(true), 0); +} + +TEST(DisplayList, AllBlendModeNops) { + DisplayListBuilder builder; + builder.setBlendMode(SkBlendMode::kSrcOver); + builder.setBlender(nullptr); + sk_sp dl = builder.Build(); + ASSERT_EQ(dl->bytes(false), sizeof(DisplayList)); + ASSERT_EQ(dl->bytes(true), sizeof(DisplayList)); + ASSERT_EQ(dl->op_count(false), 0); + ASSERT_EQ(dl->op_count(true), 0); +} + static sk_sp Build(size_t g_index, size_t v_index) { DisplayListBuilder builder; int op_count = 0; @@ -873,7 +903,7 @@ static sk_sp Build(size_t g_index, size_t v_index) { name = "Default"; } else { name = allGroups[g_index].op_name; - if (v_index < 0) { + if (v_index >= allGroups[g_index].variants.size()) { name += " skipped"; } else { name += " variant " + std::to_string(v_index + 1); @@ -890,12 +920,12 @@ TEST(DisplayList, DisplayListsWithVaryingOpComparisons) { 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]"; + auto desc = "[Group " + group.op_name + " 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 " + + auto desc = "[Group " + group.op_name + " variant " + std::to_string(vi + 1) + "]"; sk_sp variant_dl = Build(gi, vi); ASSERT_TRUE(variant_dl->Equals(*variant_dl)) << desc << " == itself"; @@ -1118,5 +1148,254 @@ TEST(DisplayList, NestedOpCountMetricsSameAsSkPicture) { ASSERT_EQ(display_list->op_count(true), 36); } +class AttributeRefTester { + public: + virtual void setRefToPaint(SkPaint& paint) const = 0; + virtual void setRefToDisplayList(DisplayListBuilder& builder) const = 0; + virtual bool ref_is_unique() const = 0; + + void testDisplayList() { + { + DisplayListBuilder builder; + setRefToDisplayList(builder); + builder.drawRect(SkRect::MakeLTRB(50, 50, 100, 100)); + ASSERT_FALSE(ref_is_unique()); + } + ASSERT_TRUE(ref_is_unique()); + } + void testPaint() { + { + SkPaint paint; + setRefToPaint(paint); + ASSERT_FALSE(ref_is_unique()); + } + ASSERT_TRUE(ref_is_unique()); + } + void testCanvasRecorder() { + { + sk_sp display_list; + { + DisplayListCanvasRecorder recorder(SkRect::MakeLTRB(0, 0, 200, 200)); + { + { + SkPaint paint; + setRefToPaint(paint); + recorder.drawRect(SkRect::MakeLTRB(50, 50, 100, 100), paint); + ASSERT_FALSE(ref_is_unique()); + } + ASSERT_FALSE(ref_is_unique()); + } + display_list = recorder.Build(); + ASSERT_FALSE(ref_is_unique()); + } + ASSERT_FALSE(ref_is_unique()); + } + ASSERT_TRUE(ref_is_unique()); + } + + void test() { + testDisplayList(); + testPaint(); + testCanvasRecorder(); + } +}; + +TEST(DisplayList, DisplayListImageFilterRefHandling) { + class ImageFilterRefTester : public virtual AttributeRefTester { + public: + void setRefToPaint(SkPaint& paint) const override { + paint.setImageFilter(image_filter); + } + void setRefToDisplayList(DisplayListBuilder& builder) const override { + builder.setImageFilter(image_filter); + } + bool ref_is_unique() const override { return image_filter->unique(); } + + private: + sk_sp image_filter = SkImageFilters::Blur(2.0, 2.0, nullptr); + }; + + ImageFilterRefTester tester; + tester.test(); + ASSERT_TRUE(tester.ref_is_unique()); +} + +TEST(DisplayList, DisplayListColorFilterRefHandling) { + class ColorFilterRefTester : public virtual AttributeRefTester { + public: + void setRefToPaint(SkPaint& paint) const override { + paint.setColorFilter(color_filter); + } + void setRefToDisplayList(DisplayListBuilder& builder) const override { + builder.setColorFilter(color_filter); + } + bool ref_is_unique() const override { return color_filter->unique(); } + + private: + sk_sp color_filter = + SkColorFilters::Blend(SK_ColorBLUE, SkBlendMode::kSrcIn); + }; + + ColorFilterRefTester tester; + tester.test(); + ASSERT_TRUE(tester.ref_is_unique()); +} + +TEST(DisplayList, DisplayListMaskFilterRefHandling) { + class MaskFilterRefTester : public virtual AttributeRefTester { + public: + void setRefToPaint(SkPaint& paint) const override { + paint.setMaskFilter(mask_filter); + } + void setRefToDisplayList(DisplayListBuilder& builder) const override { + builder.setMaskFilter(mask_filter); + } + bool ref_is_unique() const override { return mask_filter->unique(); } + + private: + sk_sp mask_filter = + SkMaskFilter::MakeBlur(SkBlurStyle::kNormal_SkBlurStyle, 2.0); + }; + + MaskFilterRefTester tester; + tester.test(); + ASSERT_TRUE(tester.ref_is_unique()); +} + +TEST(DisplayList, DisplayListBlenderRefHandling) { + class BlenderRefTester : public virtual AttributeRefTester { + public: + void setRefToPaint(SkPaint& paint) const override { + paint.setBlender(blender); + } + void setRefToDisplayList(DisplayListBuilder& builder) const override { + builder.setBlender(blender); + } + bool ref_is_unique() const override { return blender->unique(); } + + private: + sk_sp blender = + SkBlenders::Arithmetic(0.25, 0.25, 0.25, 0.25, true); + }; + + BlenderRefTester tester; + tester.test(); + ASSERT_TRUE(tester.ref_is_unique()); +} + +TEST(DisplayList, DisplayListShaderRefHandling) { + class ShaderRefTester : public virtual AttributeRefTester { + public: + void setRefToPaint(SkPaint& paint) const override { + paint.setShader(shader); + } + void setRefToDisplayList(DisplayListBuilder& builder) const override { + builder.setShader(shader); + } + bool ref_is_unique() const override { return shader->unique(); } + + private: + sk_sp shader = SkShaders::Color(SK_ColorBLUE); + }; + + ShaderRefTester tester; + tester.test(); + ASSERT_TRUE(tester.ref_is_unique()); +} + +TEST(DisplayList, DisplayListPathEffectRefHandling) { + class PathEffectRefTester : public virtual AttributeRefTester { + public: + void setRefToPaint(SkPaint& paint) const override { + paint.setPathEffect(path_effect); + } + void setRefToDisplayList(DisplayListBuilder& builder) const override { + builder.setPathEffect(path_effect); + } + bool ref_is_unique() const override { return path_effect->unique(); } + + private: + sk_sp path_effect = + SkDashPathEffect::Make(TestDashes1, 2, 0.0); + }; + + PathEffectRefTester tester; + tester.test(); + ASSERT_TRUE(tester.ref_is_unique()); +} + +TEST(DisplayList, DisplayListFullPerspectiveTransformHandling) { + // SkM44 constructor takes row-major order + SkM44 sk_matrix = SkM44( + // clang-format off + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + // clang-format on + ); + + { // First test == + DisplayListBuilder builder; + // builder.transformFullPerspective takes row-major order + builder.transformFullPerspective( + // clang-format off + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, + 13, 14, 15, 16 + // clang-format on + ); + sk_sp display_list = builder.Build(); + sk_sp surface = SkSurface::MakeRasterN32Premul(10, 10); + SkCanvas* canvas = surface->getCanvas(); + display_list->RenderTo(canvas); + SkM44 dl_matrix = canvas->getLocalToDevice(); + ASSERT_EQ(sk_matrix, dl_matrix); + } + { // Next test != + DisplayListBuilder builder; + // builder.transformFullPerspective takes row-major order + builder.transformFullPerspective( + // clang-format off + 1, 5, 9, 13, + 2, 6, 7, 11, + 3, 7, 11, 15, + 4, 8, 12, 16 + // clang-format on + ); + sk_sp display_list = builder.Build(); + sk_sp surface = SkSurface::MakeRasterN32Premul(10, 10); + SkCanvas* canvas = surface->getCanvas(); + display_list->RenderTo(canvas); + SkM44 dl_matrix = canvas->getLocalToDevice(); + ASSERT_NE(sk_matrix, dl_matrix); + } +} + +TEST(DisplayList, SetMaskBlurSigmaZeroResetsMaskFilter) { + DisplayListBuilder builder; + builder.setMaskBlurFilter(SkBlurStyle::kNormal_SkBlurStyle, 2.0); + builder.drawRect({10, 10, 20, 20}); + builder.setMaskBlurFilter(SkBlurStyle::kNormal_SkBlurStyle, 0.0); + EXPECT_EQ(builder.getMaskFilter(), nullptr); + builder.drawRect({30, 30, 40, 40}); + sk_sp display_list = builder.Build(); + ASSERT_EQ(display_list->op_count(), 2); + ASSERT_EQ(display_list->bytes(), sizeof(DisplayList) + 8u + 24u + 8u + 24u); +} + +TEST(DisplayList, SetMaskFilterNullResetsMaskFilter) { + DisplayListBuilder builder; + builder.setMaskBlurFilter(SkBlurStyle::kNormal_SkBlurStyle, 2.0); + builder.drawRect({10, 10, 20, 20}); + builder.setMaskFilter(nullptr); + EXPECT_EQ(builder.getMaskFilter(), nullptr); + builder.drawRect({30, 30, 40, 40}); + sk_sp display_list = builder.Build(); + ASSERT_EQ(display_list->op_count(), 2); + ASSERT_EQ(display_list->bytes(), sizeof(DisplayList) + 8u + 24u + 8u + 24u); +} + } // namespace testing } // namespace flutter diff --git a/flow/display_list_utils.cc b/flow/display_list_utils.cc index b6d86fa53389..6cf86f16707e 100644 --- a/flow/display_list_utils.cc +++ b/flow/display_list_utils.cc @@ -234,8 +234,7 @@ void DisplayListBoundsCalculator::setStrokeJoin(SkPaint::Join join) { join_is_miter_ = (join == SkPaint::kMiter_Join); } void DisplayListBoundsCalculator::setStyle(SkPaint::Style style) { - style_flag_ = (style == SkPaint::kFill_Style) ? kIsFilledGeometry // - : kIsStrokedGeometry; + style_ = style; } void DisplayListBoundsCalculator::setStrokeWidth(SkScalar width) { half_stroke_width_ = std::max(width * 0.5f, kMinStrokeWidth); @@ -290,7 +289,9 @@ void DisplayListBoundsCalculator::saveLayer(const SkRect* bounds, // Accumulate the layer in its own coordinate system and then // filter and transform its bounds on restore. SkMatrixDispatchHelper::reset(); - ClipBoundsDispatchHelper::reset(bounds); + if (bounds) { + clipRect(*bounds, SkClipOp::kIntersect, false); + } } void DisplayListBoundsCalculator::restore() { if (layer_infos_.size() > 1) { @@ -310,7 +311,7 @@ void DisplayListBoundsCalculator::restore() { // modifications based on the attributes that were in place // when it was instantiated. Modifying it further base on the // current attributes would mix attribute states. - AccumulateRect(layer_bounds, kIsUnfiltered); + AccumulateRect(layer_bounds, kSaveLayerFlags); } if (layer_unbounded) { AccumulateUnbounded(); @@ -327,39 +328,35 @@ void DisplayListBoundsCalculator::drawColor(SkColor color, SkBlendMode mode) { void DisplayListBoundsCalculator::drawLine(const SkPoint& p0, const SkPoint& p1) { SkRect bounds = SkRect::MakeLTRB(p0.fX, p0.fY, p1.fX, p1.fY).makeSorted(); - int cap_flag = kIsStrokedGeometry; - if (bounds.width() > 0.0f && bounds.height() > 0.0f) { - cap_flag |= kGeometryMayHaveDiagonalEndCaps; - } - AccumulateRect(bounds, cap_flag); + DisplayListAttributeFlags flags = + (bounds.width() > 0.0f && bounds.height() > 0.0f) ? kDrawLineFlags + : kDrawHVLineFlags; + AccumulateRect(bounds, flags); } void DisplayListBoundsCalculator::drawRect(const SkRect& rect) { - AccumulateRect(rect, kIsDrawnGeometry); + AccumulateRect(rect, kDrawRectFlags); } void DisplayListBoundsCalculator::drawOval(const SkRect& bounds) { - AccumulateRect(bounds, kIsDrawnGeometry); + AccumulateRect(bounds, kDrawOvalFlags); } void DisplayListBoundsCalculator::drawCircle(const SkPoint& center, SkScalar radius) { AccumulateRect(SkRect::MakeLTRB(center.fX - radius, center.fY - radius, center.fX + radius, center.fY + radius), - kIsDrawnGeometry); + kDrawCircleFlags); } void DisplayListBoundsCalculator::drawRRect(const SkRRect& rrect) { - AccumulateRect(rrect.getBounds(), kIsDrawnGeometry); + AccumulateRect(rrect.getBounds(), kDrawRRectFlags); } void DisplayListBoundsCalculator::drawDRRect(const SkRRect& outer, const SkRRect& inner) { - AccumulateRect(outer.getBounds(), kIsDrawnGeometry); + AccumulateRect(outer.getBounds(), kDrawDRRectFlags); } void DisplayListBoundsCalculator::drawPath(const SkPath& path) { if (path.isInverseFillType()) { AccumulateUnbounded(); } else { - AccumulateRect(path.getBounds(), // - (kIsDrawnGeometry | // - kGeometryMayHaveDiagonalEndCaps | // - kGeometryMayHaveProblematicJoins)); + AccumulateRect(path.getBounds(), kDrawPathFlags); } } void DisplayListBoundsCalculator::drawArc(const SkRect& bounds, @@ -369,7 +366,10 @@ void DisplayListBoundsCalculator::drawArc(const SkRect& bounds, // 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, kIsDrawnGeometry | kGeometryMayHaveDiagonalEndCaps); + AccumulateRect(bounds, + useCenter // + ? kDrawArcWithCenterFlags + : kDrawArcNoCenterFlags); } void DisplayListBoundsCalculator::drawPoints(SkCanvas::PointMode mode, uint32_t count, @@ -379,17 +379,23 @@ void DisplayListBoundsCalculator::drawPoints(SkCanvas::PointMode mode, for (size_t i = 0; i < count; i++) { ptBounds.accumulate(pts[i]); } - int flags = kIsStrokedGeometry; - if (mode != SkCanvas::kPoints_PointMode) { - flags |= kGeometryMayHaveDiagonalEndCaps; - // Even Polygon mode just draws (count-1) separate lines, no joins + SkRect point_bounds = ptBounds.bounds(); + switch (mode) { + case SkCanvas::kPoints_PointMode: + AccumulateRect(point_bounds, kDrawPointsAsPointsFlags); + break; + case SkCanvas::kLines_PointMode: + AccumulateRect(point_bounds, kDrawPointsAsLinesFlags); + break; + case SkCanvas::kPolygon_PointMode: + AccumulateRect(point_bounds, kDrawPointsAsPolygonFlags); + break; } - AccumulateRect(ptBounds.bounds(), flags); } } void DisplayListBoundsCalculator::drawVertices(const sk_sp vertices, SkBlendMode mode) { - AccumulateRect(vertices->bounds(), kIsNonGeometric); + AccumulateRect(vertices->bounds(), kDrawVerticesFlags); } void DisplayListBoundsCalculator::drawImage(const sk_sp image, const SkPoint point, @@ -397,8 +403,9 @@ void DisplayListBoundsCalculator::drawImage(const sk_sp image, bool render_with_attributes) { SkRect bounds = SkRect::MakeXYWH(point.fX, point.fY, // image->width(), image->height()); - int flags = render_with_attributes ? kIsNonGeometric | kApplyMaskFilter - : kIsUnfiltered; + DisplayListAttributeFlags flags = render_with_attributes // + ? kDrawImageWithPaintFlags + : kDrawImageFlags; AccumulateRect(bounds, flags); } void DisplayListBoundsCalculator::drawImageRect( @@ -408,8 +415,9 @@ void DisplayListBoundsCalculator::drawImageRect( const SkSamplingOptions& sampling, bool render_with_attributes, SkCanvas::SrcRectConstraint constraint) { - int flags = render_with_attributes ? kIsNonGeometric | kApplyMaskFilter - : kIsUnfiltered; + DisplayListAttributeFlags flags = render_with_attributes + ? kDrawImageRectWithPaintFlags + : kDrawImageRectFlags; AccumulateRect(dst, flags); } void DisplayListBoundsCalculator::drawImageNine(const sk_sp image, @@ -417,7 +425,10 @@ void DisplayListBoundsCalculator::drawImageNine(const sk_sp image, const SkRect& dst, SkFilterMode filter, bool render_with_attributes) { - AccumulateRect(dst, render_with_attributes ? kIsNonGeometric : kIsUnfiltered); + DisplayListAttributeFlags flags = render_with_attributes + ? kDrawImageNineWithPaintFlags + : kDrawImageNineFlags; + AccumulateRect(dst, flags); } void DisplayListBoundsCalculator::drawImageLattice( const sk_sp image, @@ -425,8 +436,9 @@ void DisplayListBoundsCalculator::drawImageLattice( const SkRect& dst, SkFilterMode filter, bool render_with_attributes) { - int flags = render_with_attributes ? kIsNonGeometric | kApplyMaskFilter - : kIsUnfiltered; + DisplayListAttributeFlags flags = render_with_attributes + ? kDrawImageLatticeWithPaintFlags + : kDrawImageLatticeFlags; AccumulateRect(dst, flags); } void DisplayListBoundsCalculator::drawAtlas(const sk_sp atlas, @@ -448,7 +460,9 @@ void DisplayListBoundsCalculator::drawAtlas(const sk_sp atlas, } } if (atlasBounds.is_not_empty()) { - int flags = render_with_attributes ? kIsNonGeometric : kIsUnfiltered; + DisplayListAttributeFlags flags = render_with_attributes // + ? kDrawAtlasWithPaintFlags + : kDrawAtlasFlags; AccumulateRect(atlasBounds.bounds(), flags); } } @@ -462,17 +476,19 @@ void DisplayListBoundsCalculator::drawPicture(const sk_sp picture, if (pic_matrix) { pic_matrix->mapRect(&bounds); } - AccumulateRect(bounds, - render_with_attributes ? kIsNonGeometric : kIsUnfiltered); + DisplayListAttributeFlags flags = render_with_attributes // + ? kDrawPictureWithPaintFlags + : kDrawPictureFlags; + AccumulateRect(bounds, flags); } void DisplayListBoundsCalculator::drawDisplayList( const sk_sp display_list) { - AccumulateRect(display_list->bounds(), kIsUnfiltered); + AccumulateRect(display_list->bounds(), kDrawDisplayListFlags); } void DisplayListBoundsCalculator::drawTextBlob(const sk_sp blob, SkScalar x, SkScalar y) { - AccumulateRect(blob->bounds().makeOffset(x, y), kIsFilledGeometry); + AccumulateRect(blob->bounds().makeOffset(x, y), kDrawTextBlobFlags); } void DisplayListBoundsCalculator::drawShadow(const SkPath& path, const SkColor color, @@ -481,7 +497,7 @@ void DisplayListBoundsCalculator::drawShadow(const SkPath& path, SkScalar dpr) { SkRect shadow_bounds = PhysicalShapeLayer::ComputeShadowBounds(path, elevation, dpr, matrix()); - AccumulateRect(shadow_bounds, kIsUnfiltered); + AccumulateRect(shadow_bounds, kDrawShadowFlags); } bool DisplayListBoundsCalculator::ComputeFilteredBounds(SkRect& bounds, @@ -495,64 +511,41 @@ bool DisplayListBoundsCalculator::ComputeFilteredBounds(SkRect& bounds, return true; } -bool DisplayListBoundsCalculator::AdjustBoundsForPaint(SkRect& bounds, - int flags) { - if ((flags & kIsUnfiltered) != 0) { - FML_DCHECK(flags == kIsUnfiltered); +bool DisplayListBoundsCalculator::AdjustBoundsForPaint( + SkRect& bounds, + DisplayListAttributeFlags flags) { + if (flags.ignores_paint()) { return true; } - if ((flags & kIsAnyGeometryMask) != 0) { - if ((flags & kIsDrawnGeometry) != 0) { - FML_DCHECK((flags & (kIsFilledGeometry | kIsStrokedGeometry)) == 0); - flags |= style_flag_; - } - + if (flags.is_geometric()) { // Path effect occurs before stroking... + DisplayListSpecialGeometryFlags special_flags = + flags.WithPathEffect(path_effect_); if (path_effect_) { - SkPathEffect::DashInfo info; - if (path_effect_->asADash(&info) == SkPathEffect::kDash_DashType) { - // A dash effect has a very simple impact. It cannot introduce any - // miter joins that weren't already present in the original path - // and it does not grow the bounds of the path, but it can add - // end caps to areas that might not have had them before so all - // we need to do is to indicate the potential for diagonal - // end caps and move on. - flags |= kGeometryMayHaveDiagonalEndCaps; - } else { - SkPaint p; - p.setPathEffect(path_effect_); - if (!p.canComputeFastBounds()) { - return false; - } - bounds = p.computeFastBounds(bounds, &bounds); - flags |= (kGeometryMayHaveDiagonalEndCaps | - kGeometryMayHaveProblematicJoins); + SkPaint p; + p.setPathEffect(path_effect_); + if (!p.canComputeFastBounds()) { + return false; } + bounds = p.computeFastBounds(bounds, &bounds); } - if ((flags & kIsStrokedGeometry) != 0) { - FML_DCHECK((flags & kIsFilledGeometry) == 0); + if (flags.is_stroked(style_)) { // Determine the max multiplier to the stroke width first. SkScalar pad = 1.0f; - if (join_is_miter_ && (flags & kGeometryMayHaveProblematicJoins) != 0) { + if (join_is_miter_ && special_flags.may_have_acute_joins()) { pad = std::max(pad, miter_limit_); } - if (cap_is_square_ && (flags & kGeometryMayHaveDiagonalEndCaps) != 0) { + if (cap_is_square_ && special_flags.may_have_diagonal_caps()) { pad = std::max(pad, SK_ScalarSqrt2); } pad *= half_stroke_width_; bounds.outset(pad, pad); - } else { - FML_DCHECK((flags & kIsStrokedGeometry) == 0); } - flags |= kApplyMaskFilter; - } else { - FML_DCHECK((flags & (kGeometryMayHaveDiagonalEndCaps | - kGeometryMayHaveProblematicJoins)) == 0); } - if ((flags & kApplyMaskFilter) != 0) { + if (flags.applies_mask_filter()) { if (mask_filter_) { SkPaint p; p.setMaskFilter(mask_filter_); @@ -566,7 +559,11 @@ bool DisplayListBoundsCalculator::AdjustBoundsForPaint(SkRect& bounds, } } - return ComputeFilteredBounds(bounds, image_filter_.get()); + if (flags.applies_image_filter()) { + return ComputeFilteredBounds(bounds, image_filter_.get()); + } + + return true; } void DisplayListBoundsCalculator::AccumulateUnbounded() { @@ -576,7 +573,9 @@ void DisplayListBoundsCalculator::AccumulateUnbounded() { layer_infos_.back()->set_unbounded(); } } -void DisplayListBoundsCalculator::AccumulateRect(SkRect& rect, int flags) { +void DisplayListBoundsCalculator::AccumulateRect( + SkRect& rect, + DisplayListAttributeFlags flags) { if (AdjustBoundsForPaint(rect, flags)) { matrix().mapRect(&rect); if (!has_clip() || rect.intersect(clip_bounds())) { diff --git a/flow/display_list_utils.h b/flow/display_list_utils.h index 495a62f294fc..779c48fd5658 100644 --- a/flow/display_list_utils.h +++ b/flow/display_list_utils.h @@ -65,9 +65,9 @@ class IgnoreAttributeDispatchHelper : public virtual Dispatcher { // A utility class that will ignore all Dispatcher methods relating // to setting a clip. class IgnoreClipDispatchHelper : public virtual Dispatcher { - void clipRect(const SkRect& rect, SkClipOp clip_op, bool isAA) override {} - void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool isAA) override {} - void clipPath(const SkPath& path, SkClipOp clip_op, bool isAA) override {} + void clipRect(const SkRect& rect, SkClipOp clip_op, bool is_aa) override {} + void clipRRect(const SkRRect& rrect, SkClipOp clip_op, bool is_aa) override {} + void clipPath(const SkPath& path, SkClipOp clip_op, bool is_aa) override {} }; // A utility class that will ignore all Dispatcher methods relating @@ -267,7 +267,8 @@ class DisplayListBoundsCalculator final : public virtual Dispatcher, public virtual IgnoreAttributeDispatchHelper, public virtual SkMatrixDispatchHelper, - public virtual ClipBoundsDispatchHelper { + public virtual ClipBoundsDispatchHelper, + DisplayListOpFlags { public: // Construct a Calculator to determine the bounds of a list of // DisplayList dispatcher method calls. Since 2 of the method calls @@ -531,61 +532,6 @@ class DisplayListBoundsCalculator final std::vector> layer_infos_; - // A drawing operation that is not geometric in nature (but which - // may still apply a MaskFilter - see |kApplyMaskFilter| below). - static constexpr int kIsNonGeometric = 0x00; - - // A geometric operation that is defined as a fill operation - // regardless of what the current paint Style is set to. - // This flag will automatically assume |kApplyMaskFilter|. - static constexpr int kIsFilledGeometry = 0x01; - - // A geometric operation that is defined as a stroke operation - // regardless of what the current paint Style is set to. - // This flag will automatically assume |kApplyMaskFilter|. - static constexpr int kIsStrokedGeometry = 0x02; - - // A geometric operation that may be a stroke or fill operation - // depending on the current state of the paint Style attribute. - // This flag will automatically assume |kApplyMaskFilter|. - static constexpr int kIsDrawnGeometry = 0x04; - - static constexpr int kIsAnyGeometryMask = // - kIsFilledGeometry | // - kIsStrokedGeometry | // - kIsDrawnGeometry; - - // A geometric operation which has a path that might have - // end caps that are not rectilinear which means that square - // end caps might project further than half the stroke width - // from the geometry bounds. - // A rectilinear path such as |drawRect| will not have - // diagonal end caps. |drawLine| might have diagonal end - // caps depending on the angle of the line, and more likely - // |drawPath| will often have such end caps. - static constexpr int kGeometryMayHaveDiagonalEndCaps = 0x08; - - // A geometric operation which has joined vertices that are - // not guaranteed to be smooth (angles of incoming and outgoing) - // segments at some joins may not have the same angle) or - // rectilinear (squares have right angles at the corners, but - // those corners will never extend past the bounding box of - // the geometry pre-transform). - // |drawRect|, |drawOval| and |drawRRect| all have well - // behaved joins, but |drawPath| might have joins that cause - // mitered extensions outside the pre-transformed bounding box. - static constexpr int kGeometryMayHaveProblematicJoins = 0x10; - - // Some operations are inherently non-geometric and yet have the - // mask filter applied anyway. - // |drawImage| variants behave this way. - static constexpr int kApplyMaskFilter = 0x20; - - // In very rare circumstances the ImageFilter is ignored. - // This is one of the few flags that turns off a step in - // estimating the bounds, rather than turning on any steps. - static constexpr int kIsUnfiltered = 0x40; - static constexpr SkScalar kMinStrokeWidth = 0.01; skstd::optional blend_mode_ = SkBlendMode::kSrcOver; @@ -593,7 +539,7 @@ class DisplayListBoundsCalculator final SkScalar half_stroke_width_ = kMinStrokeWidth; SkScalar miter_limit_ = 4.0; - int style_flag_ = kIsFilledGeometry; + SkPaint::Style style_ = SkPaint::Style::kFill_Style; bool join_is_miter_ = true; bool cap_is_square_ = false; sk_sp image_filter_; @@ -604,14 +550,14 @@ class DisplayListBoundsCalculator final bool paint_nops_on_transparency(); static bool ComputeFilteredBounds(SkRect& rect, SkImageFilter* filter); - bool AdjustBoundsForPaint(SkRect& bounds, int flags); + bool AdjustBoundsForPaint(SkRect& bounds, DisplayListAttributeFlags flags); void AccumulateUnbounded(); - void AccumulateRect(const SkRect& rect, int flags) { + void AccumulateRect(const SkRect& rect, DisplayListAttributeFlags flags) { SkRect bounds = rect; AccumulateRect(bounds, flags); } - void AccumulateRect(SkRect& rect, int flags); + void AccumulateRect(SkRect& rect, DisplayListAttributeFlags flags); }; } // namespace flutter diff --git a/lib/ui/painting/canvas.cc b/lib/ui/painting/canvas.cc index a6075d054d68..6e31a060caab 100644 --- a/lib/ui/painting/canvas.cc +++ b/lib/ui/painting/canvas.cc @@ -82,6 +82,15 @@ fml::RefPtr Canvas::Create(PictureRecorder* recorder, ToDart("Canvas constructor called with non-genuine PictureRecorder.")); return nullptr; } + + // This call will implicitly initialize the |canvas_| field with an SkCanvas + // whether or not we are using display_list. Now that all of the code here + // in canvas.cc will direct calls to the DisplayListBuilder we could almost + // stop initializing that field for the display list case. Unfortunately, + // the text code in paragraph.cc still needs to present its output to an + // SkCanvas* which means without significant work to the internals of the + // paragraph code, we are going to continue to need the canvas adapter and + // field and getter. fml::RefPtr canvas = fml::MakeRefCounted( recorder->BeginRecording(SkRect::MakeLTRB(left, top, right, bottom))); recorder->set_canvas(canvas); @@ -94,19 +103,27 @@ Canvas::Canvas(SkCanvas* canvas) : canvas_(canvas) {} Canvas::~Canvas() {} void Canvas::save() { - if (!canvas_) { - return; + if (display_list_recorder_) { + builder()->save(); + } else if (canvas_) { + canvas_->save(); } - canvas_->save(); } void Canvas::saveLayerWithoutBounds(const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + bool restore_with_paint = + paint.sync_to(builder(), kSaveLayerWithPaintFlags); + FML_DCHECK(restore_with_paint); + TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)"); + builder()->saveLayer(nullptr, restore_with_paint); + } else if (canvas_) { + SkPaint sk_paint; + TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)"); + canvas_->saveLayer(nullptr, paint.paint(sk_paint)); } - TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)"); - canvas_->saveLayer(nullptr, paint.paint()); } void Canvas::saveLayer(double left, @@ -115,64 +132,88 @@ void Canvas::saveLayer(double left, double bottom, const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; - } - TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)"); + FML_DCHECK(paint.isNotNull()); SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom); - canvas_->saveLayer(&bounds, paint.paint()); + if (display_list_recorder_) { + bool restore_with_paint = + paint.sync_to(builder(), kSaveLayerWithPaintFlags); + FML_DCHECK(restore_with_paint); + TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)"); + builder()->saveLayer(&bounds, restore_with_paint); + } else if (canvas_) { + SkPaint sk_paint; + TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)"); + canvas_->saveLayer(&bounds, paint.paint(sk_paint)); + } } void Canvas::restore() { - if (!canvas_) { - return; + if (display_list_recorder_) { + builder()->restore(); + } else if (canvas_) { + canvas_->restore(); } - canvas_->restore(); } int Canvas::getSaveCount() { - if (!canvas_) { + if (display_list_recorder_) { + return builder()->getSaveCount(); + } else if (canvas_) { + return canvas_->getSaveCount(); + } else { return 0; } - return canvas_->getSaveCount(); } void Canvas::translate(double dx, double dy) { - if (!canvas_) { - return; + if (display_list_recorder_) { + builder()->translate(dx, dy); + } else if (canvas_) { + canvas_->translate(dx, dy); } - canvas_->translate(dx, dy); } void Canvas::scale(double sx, double sy) { - if (!canvas_) { - return; + if (display_list_recorder_) { + builder()->scale(sx, sy); + } else if (canvas_) { + canvas_->scale(sx, sy); } - canvas_->scale(sx, sy); } void Canvas::rotate(double radians) { - if (!canvas_) { - return; + if (display_list_recorder_) { + builder()->rotate(radians * 180.0 / M_PI); + } else if (canvas_) { + canvas_->rotate(radians * 180.0 / M_PI); } - canvas_->rotate(radians * 180.0 / M_PI); } void Canvas::skew(double sx, double sy) { - if (!canvas_) { - return; + if (display_list_recorder_) { + builder()->skew(sx, sy); + } else if (canvas_) { + canvas_->skew(sx, sy); } - canvas_->skew(sx, sy); } void Canvas::transform(const tonic::Float64List& matrix4) { - if (!canvas_) { - return; + // The Float array stored by Dart Matrix4 is in column-major order + // Both DisplayList and SkM44 constructor take row-major matrix order + if (display_list_recorder_) { + // clang-format off + builder()->transformFullPerspective( + matrix4[ 0], matrix4[ 4], matrix4[ 8], matrix4[12], + matrix4[ 1], matrix4[ 5], matrix4[ 9], matrix4[13], + matrix4[ 2], matrix4[ 6], matrix4[10], matrix4[14], + matrix4[ 3], matrix4[ 7], matrix4[11], matrix4[15]); + // clang-format on + } else if (canvas_) { + canvas_->concat(SkM44(matrix4[0], matrix4[4], matrix4[8], matrix4[12], + matrix4[1], matrix4[5], matrix4[9], matrix4[13], + matrix4[2], matrix4[6], matrix4[10], matrix4[14], + matrix4[3], matrix4[7], matrix4[11], matrix4[15])); } - canvas_->concat(SkM44(matrix4[0], matrix4[4], matrix4[8], matrix4[12], - matrix4[1], matrix4[5], matrix4[9], matrix4[13], - matrix4[2], matrix4[6], matrix4[10], matrix4[14], - matrix4[3], matrix4[7], matrix4[11], matrix4[15])); } void Canvas::clipRect(double left, @@ -181,37 +222,42 @@ void Canvas::clipRect(double left, double bottom, SkClipOp clipOp, bool doAntiAlias) { - if (!canvas_) { - return; + if (display_list_recorder_) { + builder()->clipRect(SkRect::MakeLTRB(left, top, right, bottom), clipOp, + doAntiAlias); + } else if (canvas_) { + canvas_->clipRect(SkRect::MakeLTRB(left, top, right, bottom), clipOp, + doAntiAlias); } - canvas_->clipRect(SkRect::MakeLTRB(left, top, right, bottom), clipOp, - doAntiAlias); } void Canvas::clipRRect(const RRect& rrect, bool doAntiAlias) { - if (!canvas_) { - return; + if (display_list_recorder_) { + builder()->clipRRect(rrect.sk_rrect, SkClipOp::kIntersect, doAntiAlias); + } else if (canvas_) { + canvas_->clipRRect(rrect.sk_rrect, doAntiAlias); } - canvas_->clipRRect(rrect.sk_rrect, doAntiAlias); } void Canvas::clipPath(const CanvasPath* path, bool doAntiAlias) { - if (!canvas_) { - return; - } if (!path) { Dart_ThrowException( ToDart("Canvas.clipPath called with non-genuine Path.")); return; } - canvas_->clipPath(path->path(), doAntiAlias); + if (display_list_recorder_) { + builder()->clipPath(path->path(), SkClipOp::kIntersect, doAntiAlias); + } else if (canvas_) { + canvas_->clipPath(path->path(), doAntiAlias); + } } void Canvas::drawColor(SkColor color, SkBlendMode blend_mode) { - if (!canvas_) { - return; + if (display_list_recorder_) { + builder()->drawColor(color, blend_mode); + } else if (canvas_) { + canvas_->drawColor(color, blend_mode); } - canvas_->drawColor(color, blend_mode); } void Canvas::drawLine(double x1, @@ -220,24 +266,38 @@ void Canvas::drawLine(double x1, double y2, const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + paint.sync_to(builder(), kDrawLineFlags); + builder()->drawLine(SkPoint::Make(x1, y1), SkPoint::Make(x2, y2)); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawLine(x1, y1, x2, y2, *paint.paint(sk_paint)); } - canvas_->drawLine(x1, y1, x2, y2, *paint.paint()); } void Canvas::drawPaint(const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; - } - const SkPaint* sk_paint = paint.paint(); - SkImageFilter* filter = sk_paint->getImageFilter(); - if (filter && !filter->asColorFilter(nullptr)) { - // drawPaint does an implicit saveLayer if an SkImageFilter is - // present that cannot be replaced by an SkColorFilter. - TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)"); + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + paint.sync_to(builder(), kDrawPaintFlags); + sk_sp filter = builder()->getImageFilter(); + if (filter && !filter->asColorFilter(nullptr)) { + // drawPaint does an implicit saveLayer if an SkImageFilter is + // present that cannot be replaced by an SkColorFilter. + TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)"); + } + builder()->drawPaint(); + } else if (canvas_) { + SkPaint sk_paint; + paint.paint(sk_paint); + SkImageFilter* filter = sk_paint.getImageFilter(); + if (filter && !filter->asColorFilter(nullptr)) { + // drawPaint does an implicit saveLayer if an SkImageFilter is + // present that cannot be replaced by an SkColorFilter. + TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)"); + } + canvas_->drawPaint(sk_paint); } - canvas_->drawPaint(*paint.paint()); } void Canvas::drawRect(double left, @@ -246,29 +306,42 @@ void Canvas::drawRect(double left, double bottom, const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + paint.sync_to(builder(), kDrawRectFlags); + builder()->drawRect(SkRect::MakeLTRB(left, top, right, bottom)); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawRect(SkRect::MakeLTRB(left, top, right, bottom), + *paint.paint(sk_paint)); } - canvas_->drawRect(SkRect::MakeLTRB(left, top, right, bottom), *paint.paint()); } void Canvas::drawRRect(const RRect& rrect, const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + paint.sync_to(builder(), kDrawRRectFlags); + builder()->drawRRect(rrect.sk_rrect); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawRRect(rrect.sk_rrect, *paint.paint(sk_paint)); } - canvas_->drawRRect(rrect.sk_rrect, *paint.paint()); } void Canvas::drawDRRect(const RRect& outer, const RRect& inner, const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + paint.sync_to(builder(), kDrawDRRectFlags); + builder()->drawDRRect(outer.sk_rrect, inner.sk_rrect); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawDRRect(outer.sk_rrect, inner.sk_rrect, *paint.paint(sk_paint)); } - canvas_->drawDRRect(outer.sk_rrect, inner.sk_rrect, *paint.paint()); } void Canvas::drawOval(double left, @@ -277,10 +350,15 @@ void Canvas::drawOval(double left, double bottom, const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + paint.sync_to(builder(), kDrawOvalFlags); + builder()->drawOval(SkRect::MakeLTRB(left, top, right, bottom)); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawOval(SkRect::MakeLTRB(left, top, right, bottom), + *paint.paint(sk_paint)); } - canvas_->drawOval(SkRect::MakeLTRB(left, top, right, bottom), *paint.paint()); } void Canvas::drawCircle(double x, @@ -288,10 +366,14 @@ void Canvas::drawCircle(double x, double radius, const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + paint.sync_to(builder(), kDrawCircleFlags); + builder()->drawCircle(SkPoint::Make(x, y), radius); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawCircle(x, y, radius, *paint.paint(sk_paint)); } - canvas_->drawCircle(x, y, radius, *paint.paint()); } void Canvas::drawArc(double left, @@ -303,26 +385,39 @@ void Canvas::drawArc(double left, bool useCenter, const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + paint.sync_to(builder(), + useCenter // + ? kDrawArcWithCenterFlags + : kDrawArcNoCenterFlags); + builder()->drawArc(SkRect::MakeLTRB(left, top, right, bottom), + startAngle * 180.0 / M_PI, sweepAngle * 180.0 / M_PI, + useCenter); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawArc(SkRect::MakeLTRB(left, top, right, bottom), + startAngle * 180.0 / M_PI, sweepAngle * 180.0 / M_PI, + useCenter, *paint.paint(sk_paint)); } - canvas_->drawArc(SkRect::MakeLTRB(left, top, right, bottom), - startAngle * 180.0 / M_PI, sweepAngle * 180.0 / M_PI, - useCenter, *paint.paint()); } void Canvas::drawPath(const CanvasPath* path, const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; - } + FML_DCHECK(paint.isNotNull()); if (!path) { Dart_ThrowException( ToDart("Canvas.drawPath called with non-genuine Path.")); return; } - canvas_->drawPath(path->path(), *paint.paint()); + if (display_list_recorder_) { + paint.sync_to(builder(), kDrawPathFlags); + builder()->drawPath(path->path()); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawPath(path->path(), *paint.paint(sk_paint)); + } } void Canvas::drawImage(const CanvasImage* image, @@ -331,16 +426,21 @@ void Canvas::drawImage(const CanvasImage* image, const Paint& paint, const PaintData& paint_data, int filterQualityIndex) { - if (!canvas_) { - return; - } + FML_DCHECK(paint.isNotNull()); if (!image) { Dart_ThrowException( ToDart("Canvas.drawImage called with non-genuine Image.")); return; } auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex); - canvas_->drawImage(image->image(), x, y, sampling, paint.paint()); + if (display_list_recorder_) { + bool with_attributes = paint.sync_to(builder(), kDrawImageWithPaintFlags); + builder()->drawImage(image->image(), SkPoint::Make(x, y), sampling, + with_attributes); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawImage(image->image(), x, y, sampling, paint.paint(sk_paint)); + } } void Canvas::drawImageRect(const CanvasImage* image, @@ -355,9 +455,7 @@ void Canvas::drawImageRect(const CanvasImage* image, const Paint& paint, const PaintData& paint_data, int filterQualityIndex) { - if (!canvas_) { - return; - } + FML_DCHECK(paint.isNotNull()); if (!image) { Dart_ThrowException( ToDart("Canvas.drawImageRect called with non-genuine Image.")); @@ -366,8 +464,18 @@ void Canvas::drawImageRect(const CanvasImage* image, SkRect src = SkRect::MakeLTRB(src_left, src_top, src_right, src_bottom); SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom); auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex); - canvas_->drawImageRect(image->image(), src, dst, sampling, paint.paint(), - SkCanvas::kFast_SrcRectConstraint); + if (display_list_recorder_) { + bool with_attributes = + paint.sync_to(builder(), kDrawImageRectWithPaintFlags); + builder()->drawImageRect(image->image(), src, dst, sampling, + with_attributes, + SkCanvas::kFast_SrcRectConstraint); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawImageRect(image->image(), src, dst, sampling, + paint.paint(sk_paint), + SkCanvas::kFast_SrcRectConstraint); + } } void Canvas::drawImageNine(const CanvasImage* image, @@ -382,9 +490,7 @@ void Canvas::drawImageNine(const CanvasImage* image, const Paint& paint, const PaintData& paint_data, int bitmapSamplingIndex) { - if (!canvas_) { - return; - } + FML_DCHECK(paint.isNotNull()); if (!image) { Dart_ThrowException( ToDart("Canvas.drawImageNine called with non-genuine Image.")); @@ -397,35 +503,33 @@ void Canvas::drawImageNine(const CanvasImage* image, SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom); auto filter = ImageFilter::FilterModeFromIndex(bitmapSamplingIndex); 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, true); - } else { + bool with_attributes = + paint.sync_to(builder(), kDrawImageNineWithPaintFlags); + builder()->drawImageNine(image->image(), icenter, dst, filter, + with_attributes); + } else if (canvas_) { + SkPaint sk_paint; canvas_->drawImageNine(image->image().get(), icenter, dst, filter, - paint.paint()); + paint.paint(sk_paint)); } } void Canvas::drawPicture(Picture* picture) { - if (!canvas_) { - return; - } if (!picture) { Dart_ThrowException( ToDart("Canvas.drawPicture called with non-genuine Picture.")); return; } if (picture->picture()) { - canvas_->drawPicture(picture->picture().get()); + if (display_list_recorder_) { + builder()->drawPicture(picture->picture(), nullptr, false); + } else if (canvas_) { + canvas_->drawPicture(picture->picture().get()); + } } else if (picture->display_list()) { if (display_list_recorder_) { builder()->drawDisplayList(picture->display_list()); - } else { + } else if (canvas_) { picture->display_list()->RenderTo(canvas_); } } else { @@ -437,32 +541,52 @@ void Canvas::drawPoints(const Paint& paint, const PaintData& paint_data, SkCanvas::PointMode point_mode, const tonic::Float32List& points) { - if (!canvas_) { - return; - } - static_assert(sizeof(SkPoint) == sizeof(float) * 2, "SkPoint doesn't use floats."); - canvas_->drawPoints(point_mode, - points.num_elements() / 2, // SkPoints have two floats. - reinterpret_cast(points.data()), - *paint.paint()); + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + switch (point_mode) { + case SkCanvas::kPoints_PointMode: + paint.sync_to(builder(), kDrawPointsAsPointsFlags); + break; + case SkCanvas::kLines_PointMode: + paint.sync_to(builder(), kDrawPointsAsLinesFlags); + break; + case SkCanvas::kPolygon_PointMode: + paint.sync_to(builder(), kDrawPointsAsPolygonFlags); + break; + } + builder()->drawPoints(point_mode, + points.num_elements() / 2, // SkPoints have 2 floats + reinterpret_cast(points.data())); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawPoints(point_mode, + points.num_elements() / 2, // SkPoints have 2 floats + reinterpret_cast(points.data()), + *paint.paint(sk_paint)); + } } void Canvas::drawVertices(const Vertices* vertices, SkBlendMode blend_mode, const Paint& paint, const PaintData& paint_data) { - if (!canvas_) { - return; - } if (!vertices) { Dart_ThrowException( ToDart("Canvas.drawVertices called with non-genuine Vertices.")); return; } - canvas_->drawVertices(vertices->vertices(), blend_mode, *paint.paint()); + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + paint.sync_to(builder(), kDrawVerticesFlags); + builder()->drawVertices(vertices->vertices(), blend_mode); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawVertices(vertices->vertices(), blend_mode, + *paint.paint(sk_paint)); + } } void Canvas::drawAtlas(const Paint& paint, @@ -474,9 +598,6 @@ void Canvas::drawAtlas(const Paint& paint, const tonic::Int32List& colors, SkBlendMode blend_mode, const tonic::Float32List& cull_rect) { - if (!canvas_) { - return; - } if (!atlas) { Dart_ThrowException( ToDart("Canvas.drawAtlas or Canvas.drawRawAtlas called with " @@ -493,13 +614,26 @@ void Canvas::drawAtlas(const Paint& paint, auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex); - canvas_->drawAtlas( - skImage.get(), reinterpret_cast(transforms.data()), - reinterpret_cast(rects.data()), - reinterpret_cast(colors.data()), - rects.num_elements() / 4, // SkRect have four floats. - blend_mode, sampling, reinterpret_cast(cull_rect.data()), - paint.paint()); + FML_DCHECK(paint.isNotNull()); + if (display_list_recorder_) { + bool with_attributes = paint.sync_to(builder(), kDrawAtlasWithPaintFlags); + builder()->drawAtlas( + skImage, reinterpret_cast(transforms.data()), + reinterpret_cast(rects.data()), + reinterpret_cast(colors.data()), + rects.num_elements() / 4, // SkRect have four floats. + blend_mode, sampling, reinterpret_cast(cull_rect.data()), + with_attributes); + } else if (canvas_) { + SkPaint sk_paint; + canvas_->drawAtlas( + skImage.get(), reinterpret_cast(transforms.data()), + reinterpret_cast(rects.data()), + reinterpret_cast(colors.data()), + rects.num_elements() / 4, // SkRect have four floats. + blend_mode, sampling, reinterpret_cast(cull_rect.data()), + paint.paint(sk_paint)); + } } void Canvas::drawShadow(const CanvasPath* path, @@ -526,7 +660,7 @@ void Canvas::drawShadow(const CanvasPath* path, // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125 builder()->drawShadow(path->path(), color, elevation, transparentOccluder, dpr); - } else { + } else if (canvas_) { flutter::PhysicalShapeLayer::DrawShadow( canvas_, path->path(), color, elevation, transparentOccluder, dpr); } @@ -534,6 +668,7 @@ void Canvas::drawShadow(const CanvasPath* path, void Canvas::Invalidate() { canvas_ = nullptr; + display_list_recorder_ = nullptr; if (dart_wrapper()) { ClearDartWrapper(); } diff --git a/lib/ui/painting/canvas.h b/lib/ui/painting/canvas.h index ca328d310dbb..8e8fd107f937 100644 --- a/lib/ui/painting/canvas.h +++ b/lib/ui/painting/canvas.h @@ -23,7 +23,7 @@ class DartLibraryNatives; namespace flutter { class CanvasImage; -class Canvas : public RefCountedDartWrappable { +class Canvas : public RefCountedDartWrappable, DisplayListOpFlags { DEFINE_WRAPPERTYPEINFO(); FML_FRIEND_MAKE_REF_COUNTED(Canvas); @@ -188,8 +188,8 @@ class Canvas : public RefCountedDartWrappable { // 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(); + DisplayListBuilder* builder() { + return display_list_recorder_->builder().get(); } }; diff --git a/lib/ui/painting/paint.cc b/lib/ui/painting/paint.cc index ee3c32241872..25e3913cfc32 100644 --- a/lib/ui/painting/paint.cc +++ b/lib/ui/painting/paint.cc @@ -65,28 +65,31 @@ constexpr float invert_colors[20] = { // Must be kept in sync with the MaskFilter private constants in painting.dart. enum MaskFilterType { Null, Blur }; -Paint::Paint(Dart_Handle paint_objects, Dart_Handle paint_data) { - is_null_ = Dart_IsNull(paint_data); - if (is_null_) { - return; +Paint::Paint(Dart_Handle paint_objects, Dart_Handle paint_data) + : paint_objects_(paint_objects), paint_data_(paint_data) {} + +const SkPaint* Paint::paint(SkPaint& paint) const { + if (isNull()) { + return nullptr; } + FML_DCHECK(paint == SkPaint()); - tonic::DartByteData byte_data(paint_data); + tonic::DartByteData byte_data(paint_data_); FML_CHECK(byte_data.length_in_bytes() == kDataByteCount); const uint32_t* uint_data = static_cast(byte_data.data()); const float* float_data = static_cast(byte_data.data()); Dart_Handle values[kObjectCount]; - if (!Dart_IsNull(paint_objects)) { - FML_DCHECK(Dart_IsList(paint_objects)); + if (!Dart_IsNull(paint_objects_)) { + FML_DCHECK(Dart_IsList(paint_objects_)); intptr_t length = 0; - Dart_ListLength(paint_objects, &length); + Dart_ListLength(paint_objects_, &length); FML_CHECK(length == kObjectCount); if (Dart_IsError( - Dart_ListGetRange(paint_objects, 0, kObjectCount, values))) { - return; + Dart_ListGetRange(paint_objects_, 0, kObjectCount, values))) { + return nullptr; } Dart_Handle shader = values[kShaderIndex]; @@ -94,75 +97,75 @@ Paint::Paint(Dart_Handle paint_objects, Dart_Handle paint_data) { Shader* decoded = tonic::DartConverter::FromDart(shader); auto sampling = ImageFilter::SamplingFromIndex(uint_data[kFilterQualityIndex]); - paint_.setShader(decoded->shader(sampling)); + paint.setShader(decoded->shader(sampling)); } Dart_Handle color_filter = values[kColorFilterIndex]; if (!Dart_IsNull(color_filter)) { ColorFilter* decoded_color_filter = tonic::DartConverter::FromDart(color_filter); - paint_.setColorFilter(decoded_color_filter->filter()); + paint.setColorFilter(decoded_color_filter->filter()); } Dart_Handle image_filter = values[kImageFilterIndex]; if (!Dart_IsNull(image_filter)) { ImageFilter* decoded = tonic::DartConverter::FromDart(image_filter); - paint_.setImageFilter(decoded->filter()); + paint.setImageFilter(decoded->filter()); } } - paint_.setAntiAlias(uint_data[kIsAntiAliasIndex] == 0); + paint.setAntiAlias(uint_data[kIsAntiAliasIndex] == 0); uint32_t encoded_color = uint_data[kColorIndex]; if (encoded_color) { SkColor color = encoded_color ^ kColorDefault; - paint_.setColor(color); + paint.setColor(color); } uint32_t encoded_blend_mode = uint_data[kBlendModeIndex]; if (encoded_blend_mode) { uint32_t blend_mode = encoded_blend_mode ^ kBlendModeDefault; - paint_.setBlendMode(static_cast(blend_mode)); + paint.setBlendMode(static_cast(blend_mode)); } uint32_t style = uint_data[kStyleIndex]; if (style) { - paint_.setStyle(static_cast(style)); + paint.setStyle(static_cast(style)); } float stroke_width = float_data[kStrokeWidthIndex]; if (stroke_width != 0.0) { - paint_.setStrokeWidth(stroke_width); + paint.setStrokeWidth(stroke_width); } uint32_t stroke_cap = uint_data[kStrokeCapIndex]; if (stroke_cap) { - paint_.setStrokeCap(static_cast(stroke_cap)); + paint.setStrokeCap(static_cast(stroke_cap)); } uint32_t stroke_join = uint_data[kStrokeJoinIndex]; if (stroke_join) { - paint_.setStrokeJoin(static_cast(stroke_join)); + paint.setStrokeJoin(static_cast(stroke_join)); } float stroke_miter_limit = float_data[kStrokeMiterLimitIndex]; if (stroke_miter_limit != 0.0) { - paint_.setStrokeMiter(stroke_miter_limit + kStrokeMiterLimitDefault); + paint.setStrokeMiter(stroke_miter_limit + kStrokeMiterLimitDefault); } if (uint_data[kInvertColorIndex]) { sk_sp invert_filter = ColorFilter::MakeColorMatrixFilter255(invert_colors); - sk_sp current_filter = paint_.refColorFilter(); + sk_sp current_filter = paint.refColorFilter(); if (current_filter) { invert_filter = invert_filter->makeComposed(current_filter); } - paint_.setColorFilter(invert_filter); + paint.setColorFilter(invert_filter); } if (uint_data[kDitherIndex]) { - paint_.setDither(true); + paint.setDither(true); } switch (uint_data[kMaskFilterIndex]) { @@ -172,9 +175,138 @@ Paint::Paint(Dart_Handle paint_objects, Dart_Handle paint_data) { SkBlurStyle blur_style = static_cast(uint_data[kMaskFilterBlurStyleIndex]); double sigma = float_data[kMaskFilterSigmaIndex]; - paint_.setMaskFilter(SkMaskFilter::MakeBlur(blur_style, sigma)); + paint.setMaskFilter(SkMaskFilter::MakeBlur(blur_style, sigma)); break; } + + return &paint; +} + +bool Paint::sync_to(DisplayListBuilder* builder, + const DisplayListAttributeFlags& flags) const { + if (isNull()) { + return false; + } + tonic::DartByteData byte_data(paint_data_); + FML_CHECK(byte_data.length_in_bytes() == kDataByteCount); + + const uint32_t* uint_data = static_cast(byte_data.data()); + const float* float_data = static_cast(byte_data.data()); + + Dart_Handle values[kObjectCount]; + if (Dart_IsNull(paint_objects_)) { + if (flags.applies_shader()) { + builder->setShader(nullptr); + } + if (flags.applies_color_filter()) { + builder->setColorFilter(nullptr); + } + if (flags.applies_image_filter()) { + builder->setImageFilter(nullptr); + } + } else { + FML_DCHECK(Dart_IsList(paint_objects_)); + intptr_t length = 0; + Dart_ListLength(paint_objects_, &length); + + FML_CHECK(length == kObjectCount); + if (Dart_IsError( + Dart_ListGetRange(paint_objects_, 0, kObjectCount, values))) { + return false; + } + + if (flags.applies_shader()) { + Dart_Handle shader = values[kShaderIndex]; + if (Dart_IsNull(shader)) { + builder->setShader(nullptr); + } else { + Shader* decoded = tonic::DartConverter::FromDart(shader); + auto sampling = + ImageFilter::SamplingFromIndex(uint_data[kFilterQualityIndex]); + builder->setShader(decoded->shader(sampling)); + } + } + + if (flags.applies_color_filter()) { + Dart_Handle color_filter = values[kColorFilterIndex]; + if (Dart_IsNull(color_filter)) { + builder->setColorFilter(nullptr); + } else { + ColorFilter* decoded_color_filter = + tonic::DartConverter::FromDart(color_filter); + builder->setColorFilter(decoded_color_filter->filter()); + } + } + + if (flags.applies_image_filter()) { + Dart_Handle image_filter = values[kImageFilterIndex]; + if (Dart_IsNull(image_filter)) { + builder->setImageFilter(nullptr); + } else { + ImageFilter* decoded = + tonic::DartConverter::FromDart(image_filter); + builder->setImageFilter(decoded->filter()); + } + } + } + + if (flags.applies_anti_alias()) { + builder->setAntiAlias(uint_data[kIsAntiAliasIndex] == 0); + } + + if (flags.applies_alpha_or_color()) { + uint32_t encoded_color = uint_data[kColorIndex]; + builder->setColor(encoded_color ^ kColorDefault); + } + + if (flags.applies_blend()) { + uint32_t encoded_blend_mode = uint_data[kBlendModeIndex]; + uint32_t blend_mode = encoded_blend_mode ^ kBlendModeDefault; + builder->setBlendMode(static_cast(blend_mode)); + } + + if (flags.applies_style()) { + uint32_t style = uint_data[kStyleIndex]; + builder->setStyle(static_cast(style)); + } + + if (flags.is_stroked(builder->getStyle())) { + float stroke_width = float_data[kStrokeWidthIndex]; + builder->setStrokeWidth(stroke_width); + + float stroke_miter_limit = float_data[kStrokeMiterLimitIndex]; + builder->setStrokeMiter(stroke_miter_limit + kStrokeMiterLimitDefault); + + uint32_t stroke_cap = uint_data[kStrokeCapIndex]; + builder->setStrokeCap(static_cast(stroke_cap)); + + uint32_t stroke_join = uint_data[kStrokeJoinIndex]; + builder->setStrokeJoin(static_cast(stroke_join)); + } + + if (flags.applies_color_filter()) { + builder->setInvertColors(uint_data[kInvertColorIndex] != 0); + } + + if (flags.applies_dither()) { + builder->setDither(uint_data[kDitherIndex] != 0); + } + + if (flags.applies_mask_filter()) { + switch (uint_data[kMaskFilterIndex]) { + case Null: + builder->setMaskFilter(nullptr); + break; + case Blur: + SkBlurStyle blur_style = + static_cast(uint_data[kMaskFilterBlurStyleIndex]); + double sigma = float_data[kMaskFilterSigmaIndex]; + builder->setMaskBlurFilter(blur_style, sigma); + break; + } + } + + return true; } } // namespace flutter diff --git a/lib/ui/painting/paint.h b/lib/ui/painting/paint.h index 2c6eef8455af..63943003ef28 100644 --- a/lib/ui/painting/paint.h +++ b/lib/ui/painting/paint.h @@ -8,6 +8,8 @@ #include "third_party/skia/include/core/SkPaint.h" #include "third_party/tonic/converter/dart_converter.h" +#include "flow/display_list.h" + namespace flutter { class Paint { @@ -15,13 +17,25 @@ class Paint { Paint() = default; Paint(Dart_Handle paint_objects, Dart_Handle paint_data); - const SkPaint* paint() const { return is_null_ ? nullptr : &paint_; } + const SkPaint* paint(SkPaint& paint) const; + + /// Synchronize the Dart properties to the display list according + /// to the attribute flags that indicate which properties are needed. + /// The return value indicates if the paint was non-null and can + /// either be DCHECKed or used to indicate to the DisplayList + /// draw operation whether or not to use the synchronized attributes + /// (mainly the drawImage and saveLayer methods). + bool sync_to(DisplayListBuilder* builder, + const DisplayListAttributeFlags& flags) const; + + bool isNull() const { return Dart_IsNull(paint_data_); } + bool isNotNull() const { return !Dart_IsNull(paint_data_); } private: friend struct tonic::DartConverter; - SkPaint paint_; - bool is_null_ = true; + Dart_Handle paint_objects_; + Dart_Handle paint_data_; }; // The PaintData argument is a placeholder to receive encoded data for Paint diff --git a/lib/ui/text/paragraph_builder.cc b/lib/ui/text/paragraph_builder.cc index 2ef379a2fe8c..793af6601c69 100644 --- a/lib/ui/text/paragraph_builder.cc +++ b/lib/ui/text/paragraph_builder.cc @@ -452,17 +452,19 @@ void ParagraphBuilder::pushStyle(tonic::Int32List& encoded, if (mask & tsBackgroundMask) { Paint background(background_objects, background_data); - if (background.paint()) { + if (background.isNotNull()) { + SkPaint sk_paint; style.has_background = true; - style.background = *background.paint(); + style.background = *background.paint(sk_paint); } } if (mask & tsForegroundMask) { Paint foreground(foreground_objects, foreground_data); - if (foreground.paint()) { + if (foreground.isNotNull()) { + SkPaint sk_paint; style.has_foreground = true; - style.foreground = *foreground.paint(); + style.foreground = *foreground.paint(sk_paint); } }