| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <type_traits> |
| |
| #include "flutter/flow/display_list.h" |
| #include "flutter/flow/display_list_canvas.h" |
| #include "flutter/flow/display_list_utils.h" |
| |
| #include "third_party/skia/include/core/SkPath.h" |
| #include "third_party/skia/include/core/SkRSXform.h" |
| #include "third_party/skia/include/core/SkTextBlob.h" |
| |
| namespace flutter { |
| |
| const SkSamplingOptions DisplayList::NearestSampling = |
| SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone); |
| const SkSamplingOptions DisplayList::LinearSampling = |
| SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone); |
| const SkSamplingOptions DisplayList::MipmapSampling = |
| SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear); |
| const SkSamplingOptions DisplayList::CubicSampling = |
| SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f}); |
| |
| // Most Ops can be bulk compared using memcmp because they contain |
| // only numeric values or constructs that are constructed from numeric |
| // values. |
| // |
| // Some contain sk_sp<> references which can also be bulk compared |
| // to see if they are pointing to the same reference. (Note that |
| // two sk_sp<> that refer to the same object are themselves ==.) |
| // |
| // Only a DLOp that wants to do a deep compare needs to override the |
| // DLOp::equals() method and return a value of kEqual or kNotEqual. |
| enum class DisplayListCompare { |
| // The Op is deferring comparisons to a bulk memcmp performed lazily |
| // across all bulk-comparable ops. |
| kUseBulkCompare, |
| |
| // The Op provided a specific equals method that spotted a difference |
| kNotEqual, |
| |
| // The Op provided a specific equals method that saw no differences |
| kEqual, |
| }; |
| |
| #pragma pack(push, DLOp_Alignment, 8) |
| |
| // Assuming a 64-bit platform (most of our platforms at this time?) |
| // the following comments are a "worst case" assessment of how well |
| // these structures pack into memory. They may be packed more tightly |
| // on some of the 32-bit platforms that we see in older phones. |
| // |
| // Struct allocation in the DL memory is aligned to a void* boundary |
| // which means that the minimum (aligned) struct size will be 8 bytes. |
| // The DLOp base uses 4 bytes so each Op-specific struct gets 4 bytes |
| // of data for "free" and works best when it packs well into an 8-byte |
| // aligned size. |
| struct DLOp { |
| DisplayListOpType type : 8; |
| uint32_t size : 24; |
| |
| DisplayListCompare equals(const DLOp* other) const { |
| return DisplayListCompare::kUseBulkCompare; |
| } |
| }; |
| |
| // 4 byte header + 4 byte payload packs into minimum 8 bytes |
| #define DEFINE_SET_BOOL_OP(name) \ |
| struct Set##name##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::kSet##name; \ |
| \ |
| explicit Set##name##Op(bool value) : value(value) {} \ |
| \ |
| const bool value; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.set##name(value); \ |
| } \ |
| }; |
| DEFINE_SET_BOOL_OP(AntiAlias) |
| DEFINE_SET_BOOL_OP(Dither) |
| DEFINE_SET_BOOL_OP(InvertColors) |
| #undef DEFINE_SET_BOOL_OP |
| |
| // 4 byte header + 4 byte payload packs into minimum 8 bytes |
| #define DEFINE_SET_ENUM_OP(name) \ |
| struct SetStroke##name##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::kSetStroke##name; \ |
| \ |
| explicit SetStroke##name##Op(SkPaint::name value) : value(value) {} \ |
| \ |
| const SkPaint::name value; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.setStroke##name(value); \ |
| } \ |
| }; |
| DEFINE_SET_ENUM_OP(Cap) |
| DEFINE_SET_ENUM_OP(Join) |
| #undef DEFINE_SET_ENUM_OP |
| |
| // 4 byte header + 4 byte payload packs into minimum 8 bytes |
| struct SetStyleOp final : DLOp { |
| static const auto kType = DisplayListOpType::kSetStyle; |
| |
| explicit SetStyleOp(SkPaint::Style style) : style(style) {} |
| |
| const SkPaint::Style style; |
| |
| void dispatch(Dispatcher& dispatcher) const { dispatcher.setStyle(style); } |
| }; |
| // 4 byte header + 4 byte payload packs into minimum 8 bytes |
| struct SetStrokeWidthOp final : DLOp { |
| static const auto kType = DisplayListOpType::kSetStrokeWidth; |
| |
| explicit SetStrokeWidthOp(SkScalar width) : width(width) {} |
| |
| const SkScalar width; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.setStrokeWidth(width); |
| } |
| }; |
| // 4 byte header + 4 byte payload packs into minimum 8 bytes |
| struct SetStrokeMiterOp final : DLOp { |
| static const auto kType = DisplayListOpType::kSetStrokeMiter; |
| |
| explicit SetStrokeMiterOp(SkScalar limit) : limit(limit) {} |
| |
| const SkScalar limit; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.setStrokeMiter(limit); |
| } |
| }; |
| |
| // 4 byte header + 4 byte payload packs into minimum 8 bytes |
| struct SetColorOp final : DLOp { |
| static const auto kType = DisplayListOpType::kSetColor; |
| |
| explicit SetColorOp(SkColor color) : color(color) {} |
| |
| const SkColor color; |
| |
| void dispatch(Dispatcher& dispatcher) const { dispatcher.setColor(color); } |
| }; |
| // 4 byte header + 4 byte payload packs into minimum 8 bytes |
| struct SetBlendModeOp final : DLOp { |
| static const auto kType = DisplayListOpType::kSetBlendMode; |
| |
| explicit SetBlendModeOp(SkBlendMode mode) : mode(mode) {} |
| |
| const SkBlendMode mode; |
| |
| void dispatch(Dispatcher& dispatcher) const { dispatcher.setBlendMode(mode); } |
| }; |
| |
| // Clear: 4 byte header + unused 4 byte payload uses 8 bytes |
| // (4 bytes unused) |
| // Set: 4 byte header + an sk_sp (ptr) uses 16 bytes due to the |
| // alignment of the ptr. |
| // (4 bytes unused) |
| #define DEFINE_SET_CLEAR_SKREF_OP(name, field) \ |
| struct Clear##name##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::kClear##name; \ |
| \ |
| Clear##name##Op() {} \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.set##name(nullptr); \ |
| } \ |
| }; \ |
| struct Set##name##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::kSet##name; \ |
| \ |
| explicit Set##name##Op(sk_sp<Sk##name> field) : field(std::move(field)) {} \ |
| \ |
| sk_sp<Sk##name> field; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.set##name(field); \ |
| } \ |
| }; |
| DEFINE_SET_CLEAR_SKREF_OP(Blender, blender) |
| DEFINE_SET_CLEAR_SKREF_OP(Shader, shader) |
| DEFINE_SET_CLEAR_SKREF_OP(ImageFilter, filter) |
| DEFINE_SET_CLEAR_SKREF_OP(ColorFilter, filter) |
| DEFINE_SET_CLEAR_SKREF_OP(MaskFilter, filter) |
| DEFINE_SET_CLEAR_SKREF_OP(PathEffect, effect) |
| #undef DEFINE_SET_CLEAR_SKREF_OP |
| |
| // 4 byte header + 4 byte payload packs into minimum 8 bytes |
| // Note that the "blur style" is packed into the OpType to prevent |
| // needing an additional 8 bytes for a 4-value enum. |
| #define DEFINE_MASK_BLUR_FILTER_OP(name, style) \ |
| struct SetMaskBlurFilter##name##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::kSetMaskBlurFilter##name; \ |
| \ |
| explicit SetMaskBlurFilter##name##Op(SkScalar sigma) : sigma(sigma) {} \ |
| \ |
| SkScalar sigma; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.setMaskBlurFilter(style, sigma); \ |
| } \ |
| }; |
| DEFINE_MASK_BLUR_FILTER_OP(Normal, kNormal_SkBlurStyle) |
| DEFINE_MASK_BLUR_FILTER_OP(Solid, kSolid_SkBlurStyle) |
| DEFINE_MASK_BLUR_FILTER_OP(Inner, kInner_SkBlurStyle) |
| DEFINE_MASK_BLUR_FILTER_OP(Outer, kOuter_SkBlurStyle) |
| #undef DEFINE_MASK_BLUR_FILTER_OP |
| |
| // 4 byte header + no payload uses minimum 8 bytes (4 bytes unused) |
| struct SaveOp final : DLOp { |
| static const auto kType = DisplayListOpType::kSave; |
| |
| SaveOp() {} |
| |
| void dispatch(Dispatcher& dispatcher) const { dispatcher.save(); } |
| }; |
| // 4 byte header + 4 byte payload packs into minimum 8 bytes |
| struct SaveLayerOp final : DLOp { |
| static const auto kType = DisplayListOpType::kSaveLayer; |
| |
| explicit SaveLayerOp(bool with_paint) : with_paint(with_paint) {} |
| |
| bool with_paint; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.saveLayer(nullptr, with_paint); |
| } |
| }; |
| // 4 byte header + 20 byte payload packs evenly into 24 bytes |
| struct SaveLayerBoundsOp final : DLOp { |
| static const auto kType = DisplayListOpType::kSaveLayerBounds; |
| |
| SaveLayerBoundsOp(SkRect rect, bool with_paint) |
| : with_paint(with_paint), rect(rect) {} |
| |
| bool with_paint; |
| const SkRect rect; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.saveLayer(&rect, with_paint); |
| } |
| }; |
| // 4 byte header + no payload uses minimum 8 bytes (4 bytes unused) |
| struct RestoreOp final : DLOp { |
| static const auto kType = DisplayListOpType::kRestore; |
| |
| RestoreOp() {} |
| |
| void dispatch(Dispatcher& dispatcher) const { dispatcher.restore(); } |
| }; |
| |
| // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes |
| // (4 bytes unused) |
| struct TranslateOp final : DLOp { |
| static const auto kType = DisplayListOpType::kTranslate; |
| |
| TranslateOp(SkScalar tx, SkScalar ty) : tx(tx), ty(ty) {} |
| |
| const SkScalar tx; |
| const SkScalar ty; |
| |
| void dispatch(Dispatcher& dispatcher) const { dispatcher.translate(tx, ty); } |
| }; |
| // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes |
| // (4 bytes unused) |
| struct ScaleOp final : DLOp { |
| static const auto kType = DisplayListOpType::kScale; |
| |
| ScaleOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {} |
| |
| const SkScalar sx; |
| const SkScalar sy; |
| |
| void dispatch(Dispatcher& dispatcher) const { dispatcher.scale(sx, sy); } |
| }; |
| // 4 byte header + 4 byte payload packs into minimum 8 bytes |
| struct RotateOp final : DLOp { |
| static const auto kType = DisplayListOpType::kRotate; |
| |
| explicit RotateOp(SkScalar degrees) : degrees(degrees) {} |
| |
| const SkScalar degrees; |
| |
| void dispatch(Dispatcher& dispatcher) const { dispatcher.rotate(degrees); } |
| }; |
| // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes |
| // (4 bytes unused) |
| struct SkewOp final : DLOp { |
| static const auto kType = DisplayListOpType::kSkew; |
| |
| SkewOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {} |
| |
| const SkScalar sx; |
| const SkScalar sy; |
| |
| void dispatch(Dispatcher& dispatcher) const { dispatcher.skew(sx, sy); } |
| }; |
| // 4 byte header + 24 byte payload uses 28 bytes but is rounded up to 32 bytes |
| // (4 bytes unused) |
| struct Transform2DAffineOp final : DLOp { |
| static const auto kType = DisplayListOpType::kTransform2DAffine; |
| |
| // clang-format off |
| Transform2DAffineOp(SkScalar mxx, SkScalar mxy, SkScalar mxt, |
| SkScalar myx, SkScalar myy, SkScalar myt) |
| : mxx(mxx), mxy(mxy), mxt(mxt), myx(myx), myy(myy), myt(myt) {} |
| // clang-format on |
| |
| const SkScalar mxx, mxy, mxt; |
| const SkScalar myx, myy, myt; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.transform2DAffine(mxx, mxy, mxt, // |
| myx, myy, myt); |
| } |
| }; |
| // 4 byte header + 64 byte payload uses 68 bytes which is rounded up to 72 bytes |
| // (4 bytes unused) |
| struct TransformFullPerspectiveOp final : DLOp { |
| static const auto kType = DisplayListOpType::kTransformFullPerspective; |
| |
| // clang-format off |
| TransformFullPerspectiveOp( |
| SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, |
| SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, |
| SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, |
| SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) |
| : mxx(mxx), mxy(mxy), mxz(mxz), mxt(mxt), |
| myx(myx), myy(myy), myz(myz), myt(myt), |
| mzx(mzx), mzy(mzy), mzz(mzz), mzt(mzt), |
| mwx(mwx), mwy(mwy), mwz(mwz), mwt(mwt) {} |
| // clang-format on |
| |
| const SkScalar mxx, mxy, mxz, mxt; |
| const SkScalar myx, myy, myz, myt; |
| const SkScalar mzx, mzy, mzz, mzt; |
| const SkScalar mwx, mwy, mwz, mwt; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.transformFullPerspective(mxx, mxy, mxz, mxt, // |
| myx, myy, myz, myt, // |
| mzx, mzy, mzz, mzt, // |
| mwx, mwy, mwz, mwt); |
| } |
| }; |
| |
| // 4 byte header + 4 byte common payload packs into minimum 8 bytes |
| // SkRect is 16 more bytes, which packs efficiently into 24 bytes total |
| // SkRRect is 52 more bytes, which rounds up to 56 bytes (4 bytes unused) |
| // which packs into 64 bytes total |
| // SkPath is 16 more bytes, which packs efficiently into 24 bytes total |
| // |
| // We could pack the clip_op and the bool both into the free 4 bytes after |
| // the header, but the Windows compiler keeps wanting to expand that |
| // packing into more bytes than needed (even when they are declared as |
| // packed bit fields!) |
| #define DEFINE_CLIP_SHAPE_OP(shapetype, clipop) \ |
| struct Clip##clipop##shapetype##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::kClip##clipop##shapetype; \ |
| \ |
| Clip##clipop##shapetype##Op(Sk##shapetype shape, bool is_aa) \ |
| : is_aa(is_aa), shape(shape) {} \ |
| \ |
| const bool is_aa; \ |
| const Sk##shapetype shape; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.clip##shapetype(shape, SkClipOp::k##clipop, is_aa); \ |
| } \ |
| }; |
| DEFINE_CLIP_SHAPE_OP(Rect, Intersect) |
| DEFINE_CLIP_SHAPE_OP(RRect, Intersect) |
| DEFINE_CLIP_SHAPE_OP(Rect, Difference) |
| DEFINE_CLIP_SHAPE_OP(RRect, Difference) |
| #undef DEFINE_CLIP_SHAPE_OP |
| |
| #define DEFINE_CLIP_PATH_OP(clipop) \ |
| struct Clip##clipop##PathOp final : DLOp { \ |
| static const auto kType = DisplayListOpType::kClip##clipop##Path; \ |
| \ |
| Clip##clipop##PathOp(SkPath path, bool is_aa) \ |
| : is_aa(is_aa), path(path) {} \ |
| \ |
| const bool is_aa; \ |
| const SkPath path; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.clipPath(path, SkClipOp::k##clipop, is_aa); \ |
| } \ |
| \ |
| DisplayListCompare equals(const Clip##clipop##PathOp* other) const { \ |
| return is_aa == other->is_aa && path == other->path \ |
| ? DisplayListCompare::kEqual \ |
| : DisplayListCompare::kNotEqual; \ |
| } \ |
| }; |
| DEFINE_CLIP_PATH_OP(Intersect) |
| DEFINE_CLIP_PATH_OP(Difference) |
| #undef DEFINE_CLIP_PATH_OP |
| |
| // 4 byte header + no payload uses minimum 8 bytes (4 bytes unused) |
| struct DrawPaintOp final : DLOp { |
| static const auto kType = DisplayListOpType::kDrawPaint; |
| |
| DrawPaintOp() {} |
| |
| void dispatch(Dispatcher& dispatcher) const { dispatcher.drawPaint(); } |
| }; |
| // 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes |
| // (4 bytes unused) |
| struct DrawColorOp final : DLOp { |
| static const auto kType = DisplayListOpType::kDrawColor; |
| |
| DrawColorOp(SkColor color, SkBlendMode mode) : color(color), mode(mode) {} |
| |
| const SkColor color; |
| const SkBlendMode mode; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.drawColor(color, mode); |
| } |
| }; |
| |
| // The common data is a 4 byte header with an unused 4 bytes |
| // SkRect is 16 more bytes, using 20 bytes which rounds up to 24 bytes total |
| // (4 bytes unused) |
| // SkOval is same as SkRect |
| // SkRRect is 52 more bytes, which packs efficiently into 56 bytes total |
| #define DEFINE_DRAW_1ARG_OP(op_name, arg_type, arg_name) \ |
| struct Draw##op_name##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::kDraw##op_name; \ |
| \ |
| explicit Draw##op_name##Op(arg_type arg_name) : arg_name(arg_name) {} \ |
| \ |
| const arg_type arg_name; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.draw##op_name(arg_name); \ |
| } \ |
| }; |
| DEFINE_DRAW_1ARG_OP(Rect, SkRect, rect) |
| DEFINE_DRAW_1ARG_OP(Oval, SkRect, oval) |
| DEFINE_DRAW_1ARG_OP(RRect, SkRRect, rrect) |
| #undef DEFINE_DRAW_1ARG_OP |
| |
| // 4 byte header + 16 byte payload uses 20 bytes but is rounded up to 24 bytes |
| // (4 bytes unused) |
| struct DrawPathOp final : DLOp { |
| static const auto kType = DisplayListOpType::kDrawPath; |
| |
| explicit DrawPathOp(SkPath path) : path(path) {} |
| |
| const SkPath path; |
| |
| void dispatch(Dispatcher& dispatcher) const { dispatcher.drawPath(path); } |
| |
| DisplayListCompare equals(const DrawPathOp* other) const { |
| return path == other->path ? DisplayListCompare::kEqual |
| : DisplayListCompare::kNotEqual; |
| } |
| }; |
| |
| // The common data is a 4 byte header with an unused 4 bytes |
| // 2 x SkPoint is 16 more bytes, using 20 bytes rounding up to 24 bytes total |
| // (4 bytes unused) |
| // SkPoint + SkScalar is 12 more bytes, packing efficiently into 16 bytes total |
| // 2 x SkRRect is 104 more bytes, using 108 and rounding up to 112 bytes total |
| // (4 bytes unused) |
| #define DEFINE_DRAW_2ARG_OP(op_name, type1, name1, type2, name2) \ |
| struct Draw##op_name##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::kDraw##op_name; \ |
| \ |
| Draw##op_name##Op(type1 name1, type2 name2) \ |
| : name1(name1), name2(name2) {} \ |
| \ |
| const type1 name1; \ |
| const type2 name2; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.draw##op_name(name1, name2); \ |
| } \ |
| }; |
| DEFINE_DRAW_2ARG_OP(Line, SkPoint, p0, SkPoint, p1) |
| DEFINE_DRAW_2ARG_OP(Circle, SkPoint, center, SkScalar, radius) |
| DEFINE_DRAW_2ARG_OP(DRRect, SkRRect, outer, SkRRect, inner) |
| #undef DEFINE_DRAW_2ARG_OP |
| |
| // 4 byte header + 28 byte payload packs efficiently into 32 bytes |
| struct DrawArcOp final : DLOp { |
| static const auto kType = DisplayListOpType::kDrawArc; |
| |
| DrawArcOp(SkRect bounds, SkScalar start, SkScalar sweep, bool center) |
| : bounds(bounds), start(start), sweep(sweep), center(center) {} |
| |
| const SkRect bounds; |
| const SkScalar start; |
| const SkScalar sweep; |
| const bool center; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.drawArc(bounds, start, sweep, center); |
| } |
| }; |
| |
| // 4 byte header + 4 byte fixed payload packs efficiently into 8 bytes |
| // But then there is a list of points following the structure which |
| // is guaranteed to be a multiple of 8 bytes (SkPoint is 8 bytes) |
| // so this op will always pack efficiently |
| // The point type is packed into 3 different OpTypes to avoid expanding |
| // the fixed payload beyond the 8 bytes |
| #define DEFINE_DRAW_POINTS_OP(name, mode) \ |
| struct Draw##name##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::kDraw##name; \ |
| \ |
| explicit Draw##name##Op(uint32_t count) : count(count) {} \ |
| \ |
| const uint32_t count; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| const SkPoint* pts = reinterpret_cast<const SkPoint*>(this + 1); \ |
| dispatcher.drawPoints(SkCanvas::PointMode::mode, count, pts); \ |
| } \ |
| }; |
| DEFINE_DRAW_POINTS_OP(Points, kPoints_PointMode); |
| DEFINE_DRAW_POINTS_OP(Lines, kLines_PointMode); |
| DEFINE_DRAW_POINTS_OP(Polygon, kPolygon_PointMode); |
| #undef DEFINE_DRAW_POINTS_OP |
| |
| // 4 byte header + 12 byte payload packs efficiently into 16 bytes |
| struct DrawVerticesOp final : DLOp { |
| static const auto kType = DisplayListOpType::kDrawVertices; |
| |
| DrawVerticesOp(sk_sp<SkVertices> vertices, SkBlendMode mode) |
| : mode(mode), vertices(std::move(vertices)) {} |
| |
| const SkBlendMode mode; |
| const sk_sp<SkVertices> vertices; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.drawVertices(vertices, mode); |
| } |
| }; |
| |
| // 4 byte header + 36 byte payload packs efficiently into 40 bytes |
| #define DEFINE_DRAW_IMAGE_OP(name, with_attributes) \ |
| struct name##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::k##name; \ |
| \ |
| name##Op(const sk_sp<SkImage> image, \ |
| const SkPoint& point, \ |
| const SkSamplingOptions& sampling) \ |
| : point(point), sampling(sampling), image(std::move(image)) {} \ |
| \ |
| const SkPoint point; \ |
| const SkSamplingOptions sampling; \ |
| const sk_sp<SkImage> image; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.drawImage(image, point, sampling, with_attributes); \ |
| } \ |
| }; |
| DEFINE_DRAW_IMAGE_OP(DrawImage, false) |
| DEFINE_DRAW_IMAGE_OP(DrawImageWithAttr, true) |
| #undef DEFINE_DRAW_IMAGE_OP |
| |
| // 4 byte header + 68 byte payload packs efficiently into 72 bytes |
| struct DrawImageRectOp final : DLOp { |
| static const auto kType = DisplayListOpType::kDrawImageRect; |
| |
| DrawImageRectOp(const sk_sp<SkImage> image, |
| const SkRect& src, |
| const SkRect& dst, |
| const SkSamplingOptions& sampling, |
| bool render_with_attributes, |
| SkCanvas::SrcRectConstraint constraint) |
| : src(src), |
| dst(dst), |
| sampling(sampling), |
| render_with_attributes(render_with_attributes), |
| constraint(constraint), |
| image(std::move(image)) {} |
| |
| const SkRect src; |
| const SkRect dst; |
| const SkSamplingOptions sampling; |
| const bool render_with_attributes; |
| const SkCanvas::SrcRectConstraint constraint; |
| const sk_sp<SkImage> image; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.drawImageRect(image, src, dst, sampling, render_with_attributes, |
| constraint); |
| } |
| }; |
| |
| // 4 byte header + 44 byte payload packs efficiently into 48 bytes |
| #define DEFINE_DRAW_IMAGE_NINE_OP(name, render_with_attributes) \ |
| struct name##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::k##name; \ |
| \ |
| name##Op(const sk_sp<SkImage> image, \ |
| const SkIRect& center, \ |
| const SkRect& dst, \ |
| SkFilterMode filter) \ |
| : center(center), dst(dst), filter(filter), image(std::move(image)) {} \ |
| \ |
| const SkIRect center; \ |
| const SkRect dst; \ |
| const SkFilterMode filter; \ |
| const sk_sp<SkImage> image; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.drawImageNine(image, center, dst, filter, \ |
| render_with_attributes); \ |
| } \ |
| }; |
| DEFINE_DRAW_IMAGE_NINE_OP(DrawImageNine, false) |
| DEFINE_DRAW_IMAGE_NINE_OP(DrawImageNineWithAttr, true) |
| #undef DEFINE_DRAW_IMAGE_NINE_OP |
| |
| // 4 byte header + 60 byte payload packs evenly into 64 bytes |
| struct DrawImageLatticeOp final : DLOp { |
| static const auto kType = DisplayListOpType::kDrawImageLattice; |
| |
| DrawImageLatticeOp(const sk_sp<SkImage> image, |
| int x_count, |
| int y_count, |
| int cell_count, |
| const SkIRect& src, |
| const SkRect& dst, |
| SkFilterMode filter, |
| bool with_paint) |
| : with_paint(with_paint), |
| x_count(x_count), |
| y_count(y_count), |
| cell_count(cell_count), |
| filter(filter), |
| src(src), |
| dst(dst), |
| image(std::move(image)) {} |
| |
| const bool with_paint; |
| const int x_count; |
| const int y_count; |
| const int cell_count; |
| const SkFilterMode filter; |
| const SkIRect src; |
| const SkRect dst; |
| const sk_sp<SkImage> image; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| const int* xDivs = reinterpret_cast<const int*>(this + 1); |
| const int* yDivs = reinterpret_cast<const int*>(xDivs + x_count); |
| const SkColor* colors = |
| (cell_count == 0) ? nullptr |
| : reinterpret_cast<const SkColor*>(yDivs + y_count); |
| const SkCanvas::Lattice::RectType* types = |
| (cell_count == 0) |
| ? nullptr |
| : reinterpret_cast<const SkCanvas::Lattice::RectType*>(colors + |
| cell_count); |
| dispatcher.drawImageLattice( |
| image, {xDivs, yDivs, types, x_count, y_count, &src, colors}, dst, |
| filter, with_paint); |
| } |
| }; |
| |
| // 4 byte header + 36 byte common payload packs efficiently into 40 bytes |
| // Each of these is then followed by a number of lists. |
| // SkRSXform list is a multiple of 16 bytes so it is always packed well |
| // SkRect list is also a multiple of 16 bytes so it also packs well |
| // SkColor list only packs well if the count is even, otherwise there |
| // can be 4 unusued bytes at the end. |
| struct DrawAtlasBaseOp : DLOp { |
| DrawAtlasBaseOp(const sk_sp<SkImage> atlas, |
| int count, |
| SkBlendMode mode, |
| const SkSamplingOptions& sampling, |
| bool has_colors, |
| bool render_with_attributes) |
| : count(count), |
| mode_index(static_cast<uint16_t>(mode)), |
| has_colors(has_colors), |
| render_with_attributes(render_with_attributes), |
| sampling(sampling), |
| atlas(std::move(atlas)) {} |
| |
| const int count; |
| const uint16_t mode_index; |
| const uint8_t has_colors; |
| const uint8_t render_with_attributes; |
| const SkSamplingOptions sampling; |
| const sk_sp<SkImage> atlas; |
| }; |
| |
| // Packs as efficiently into 40 bytes as per DrawAtlasBaseOp |
| // with array data following the struct also as per DrawAtlasBaseOp |
| struct DrawAtlasOp final : DrawAtlasBaseOp { |
| static const auto kType = DisplayListOpType::kDrawAtlas; |
| |
| DrawAtlasOp(const sk_sp<SkImage> atlas, |
| int count, |
| SkBlendMode mode, |
| const SkSamplingOptions& sampling, |
| bool has_colors, |
| bool render_with_attributes) |
| : DrawAtlasBaseOp(atlas, |
| count, |
| mode, |
| sampling, |
| has_colors, |
| render_with_attributes) {} |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| const SkRSXform* xform = reinterpret_cast<const SkRSXform*>(this + 1); |
| const SkRect* tex = reinterpret_cast<const SkRect*>(xform + count); |
| const SkColor* colors = |
| has_colors ? reinterpret_cast<const SkColor*>(tex + count) : nullptr; |
| const SkBlendMode mode = static_cast<SkBlendMode>(mode_index); |
| dispatcher.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, |
| nullptr, render_with_attributes); |
| } |
| }; |
| |
| // Packs efficiently into the same 40 bytes as DrawAtlasBaseOp plus |
| // an additional 16 bytes for the cull rect resulting in a total |
| // of 56 bytes for the Culled drawAtlas. |
| // Also with array data following the struct as per DrawAtlasBaseOp |
| struct DrawAtlasCulledOp final : DrawAtlasBaseOp { |
| static const auto kType = DisplayListOpType::kDrawAtlasCulled; |
| |
| DrawAtlasCulledOp(const sk_sp<SkImage> atlas, |
| int count, |
| SkBlendMode mode, |
| const SkSamplingOptions& sampling, |
| bool has_colors, |
| const SkRect& cull_rect, |
| bool render_with_attributes) |
| : DrawAtlasBaseOp(atlas, |
| count, |
| mode, |
| sampling, |
| has_colors, |
| render_with_attributes), |
| cull_rect(cull_rect) {} |
| |
| const SkRect cull_rect; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| const SkRSXform* xform = reinterpret_cast<const SkRSXform*>(this + 1); |
| const SkRect* tex = reinterpret_cast<const SkRect*>(xform + count); |
| const SkColor* colors = |
| has_colors ? reinterpret_cast<const SkColor*>(tex + count) : nullptr; |
| const SkBlendMode mode = static_cast<SkBlendMode>(mode_index); |
| dispatcher.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, |
| &cull_rect, render_with_attributes); |
| } |
| }; |
| |
| // 4 byte header + 12 byte payload packs evenly into 16 bytes |
| struct DrawSkPictureOp final : DLOp { |
| static const auto kType = DisplayListOpType::kDrawSkPicture; |
| |
| DrawSkPictureOp(sk_sp<SkPicture> picture, bool render_with_attributes) |
| : render_with_attributes(render_with_attributes), |
| picture(std::move(picture)) {} |
| |
| const bool render_with_attributes; |
| const sk_sp<SkPicture> picture; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.drawPicture(picture, nullptr, render_with_attributes); |
| } |
| }; |
| |
| // 4 byte header + 52 byte payload packs evenly into 56 bytes |
| struct DrawSkPictureMatrixOp final : DLOp { |
| static const auto kType = DisplayListOpType::kDrawSkPictureMatrix; |
| |
| DrawSkPictureMatrixOp(sk_sp<SkPicture> picture, |
| const SkMatrix matrix, |
| bool render_with_attributes) |
| : render_with_attributes(render_with_attributes), |
| picture(std::move(picture)), |
| matrix(matrix) {} |
| |
| const bool render_with_attributes; |
| const sk_sp<SkPicture> picture; |
| const SkMatrix matrix; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.drawPicture(picture, &matrix, render_with_attributes); |
| } |
| }; |
| |
| // 4 byte header + ptr aligned payload uses 12 bytes rounde up to 16 |
| // (4 bytes unused) |
| struct DrawDisplayListOp final : DLOp { |
| static const auto kType = DisplayListOpType::kDrawDisplayList; |
| |
| explicit DrawDisplayListOp(const sk_sp<DisplayList> display_list) |
| : display_list(std::move(display_list)) {} |
| |
| sk_sp<DisplayList> display_list; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.drawDisplayList(display_list); |
| } |
| }; |
| |
| // 4 byte header + 8 payload bytes + an aligned pointer take 24 bytes |
| // (4 unused to align the pointer) |
| struct DrawTextBlobOp final : DLOp { |
| static const auto kType = DisplayListOpType::kDrawTextBlob; |
| |
| DrawTextBlobOp(const sk_sp<SkTextBlob> blob, SkScalar x, SkScalar y) |
| : x(x), y(y), blob(std::move(blob)) {} |
| |
| const SkScalar x; |
| const SkScalar y; |
| const sk_sp<SkTextBlob> blob; |
| |
| void dispatch(Dispatcher& dispatcher) const { |
| dispatcher.drawTextBlob(blob, x, y); |
| } |
| }; |
| |
| // 4 byte header + 28 byte payload packs evenly into 32 bytes |
| #define DEFINE_DRAW_SHADOW_OP(name, transparent_occluder) \ |
| struct Draw##name##Op final : DLOp { \ |
| static const auto kType = DisplayListOpType::kDraw##name; \ |
| \ |
| Draw##name##Op(const SkPath& path, \ |
| SkColor color, \ |
| SkScalar elevation, \ |
| SkScalar dpr) \ |
| : color(color), elevation(elevation), dpr(dpr), path(path) {} \ |
| \ |
| const SkColor color; \ |
| const SkScalar elevation; \ |
| const SkScalar dpr; \ |
| const SkPath path; \ |
| \ |
| void dispatch(Dispatcher& dispatcher) const { \ |
| dispatcher.drawShadow(path, color, elevation, transparent_occluder, \ |
| dpr); \ |
| } \ |
| }; |
| DEFINE_DRAW_SHADOW_OP(Shadow, false) |
| DEFINE_DRAW_SHADOW_OP(ShadowTransparentOccluder, true) |
| #undef DEFINE_DRAW_SHADOW_OP |
| |
| #pragma pack(pop, DLOp_Alignment) |
| |
| void DisplayList::ComputeBounds() { |
| DisplayListBoundsCalculator calculator(&bounds_cull_); |
| Dispatch(calculator); |
| bounds_ = calculator.bounds(); |
| } |
| |
| void DisplayList::Dispatch(Dispatcher& dispatcher, |
| uint8_t* ptr, |
| uint8_t* end) const { |
| while (ptr < end) { |
| auto op = reinterpret_cast<const DLOp*>(ptr); |
| ptr += op->size; |
| FML_DCHECK(ptr <= end); |
| switch (op->type) { |
| #define DL_OP_DISPATCH(name) \ |
| case DisplayListOpType::k##name: \ |
| static_cast<const name##Op*>(op)->dispatch(dispatcher); \ |
| break; |
| |
| FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPATCH) |
| |
| #undef DL_OP_DISPATCH |
| |
| default: |
| FML_DCHECK(false); |
| return; |
| } |
| } |
| } |
| |
| static void DisposeOps(uint8_t* ptr, uint8_t* end) { |
| while (ptr < end) { |
| auto op = reinterpret_cast<const DLOp*>(ptr); |
| ptr += op->size; |
| FML_DCHECK(ptr <= end); |
| switch (op->type) { |
| #define DL_OP_DISPOSE(name) \ |
| case DisplayListOpType::k##name: \ |
| if (!std::is_trivially_destructible_v<name##Op>) { \ |
| static_cast<const name##Op*>(op)->~name##Op(); \ |
| } \ |
| break; |
| |
| FOR_EACH_DISPLAY_LIST_OP(DL_OP_DISPOSE) |
| |
| #undef DL_OP_DISPOSE |
| |
| default: |
| FML_DCHECK(false); |
| return; |
| } |
| } |
| } |
| |
| static bool CompareOps(uint8_t* ptrA, |
| uint8_t* endA, |
| uint8_t* ptrB, |
| uint8_t* endB) { |
| // These conditions are checked by the caller... |
| FML_DCHECK((endA - ptrA) == (endB - ptrB)); |
| FML_DCHECK(ptrA != ptrB); |
| uint8_t* bulkStartA = ptrA; |
| uint8_t* bulkStartB = ptrB; |
| while (ptrA < endA && ptrB < endB) { |
| auto opA = reinterpret_cast<const DLOp*>(ptrA); |
| auto opB = reinterpret_cast<const DLOp*>(ptrB); |
| if (opA->type != opB->type || opA->size != opB->size) { |
| return false; |
| } |
| ptrA += opA->size; |
| ptrB += opB->size; |
| FML_DCHECK(ptrA <= endA); |
| FML_DCHECK(ptrB <= endB); |
| DisplayListCompare result; |
| switch (opA->type) { |
| #define DL_OP_EQUALS(name) \ |
| case DisplayListOpType::k##name: \ |
| result = static_cast<const name##Op*>(opA)->equals( \ |
| static_cast<const name##Op*>(opB)); \ |
| break; |
| |
| FOR_EACH_DISPLAY_LIST_OP(DL_OP_EQUALS) |
| |
| #undef DL_OP_EQUALS |
| |
| default: |
| FML_DCHECK(false); |
| return false; |
| } |
| switch (result) { |
| case DisplayListCompare::kNotEqual: |
| return false; |
| case DisplayListCompare::kUseBulkCompare: |
| break; |
| case DisplayListCompare::kEqual: |
| // Check if we have a backlog of bytes to bulk compare and then |
| // reset the bulk compare pointers to the address following this op |
| auto bulkBytes = reinterpret_cast<const uint8_t*>(opA) - bulkStartA; |
| if (bulkBytes > 0) { |
| if (memcmp(bulkStartA, bulkStartB, bulkBytes) != 0) { |
| return false; |
| } |
| } |
| bulkStartA = ptrA; |
| bulkStartB = ptrB; |
| break; |
| } |
| } |
| if (ptrA != endA || ptrB != endB) { |
| return false; |
| } |
| if (bulkStartA < ptrA) { |
| // Perform a final bulk compare if we have remaining bytes waiting |
| if (memcmp(bulkStartA, bulkStartB, ptrA - bulkStartA) != 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void DisplayList::RenderTo(SkCanvas* canvas) const { |
| DisplayListCanvasDispatcher dispatcher(canvas); |
| Dispatch(dispatcher); |
| } |
| |
| bool DisplayList::Equals(const DisplayList& other) const { |
| if (byte_count_ != other.byte_count_ || op_count_ != other.op_count_) { |
| return false; |
| } |
| uint8_t* ptr = storage_.get(); |
| uint8_t* o_ptr = other.storage_.get(); |
| if (ptr == o_ptr) { |
| return true; |
| } |
| return CompareOps(ptr, ptr + byte_count_, o_ptr, o_ptr + other.byte_count_); |
| } |
| |
| DisplayList::DisplayList(uint8_t* ptr, |
| size_t byte_count, |
| int op_count, |
| size_t nested_byte_count, |
| int nested_op_count, |
| const SkRect& cull_rect) |
| : storage_(ptr), |
| byte_count_(byte_count), |
| op_count_(op_count), |
| nested_byte_count_(nested_byte_count), |
| nested_op_count_(nested_op_count), |
| bounds_({0, 0, -1, -1}), |
| bounds_cull_(cull_rect) { |
| static std::atomic<uint32_t> nextID{1}; |
| do { |
| unique_id_ = nextID.fetch_add(+1, std::memory_order_relaxed); |
| } while (unique_id_ == 0); |
| } |
| |
| DisplayList::~DisplayList() { |
| uint8_t* ptr = storage_.get(); |
| DisposeOps(ptr, ptr + byte_count_); |
| } |
| |
| #define DL_BUILDER_PAGE 4096 |
| |
| // CopyV(dst, src,n, src,n, ...) copies any number of typed srcs into dst. |
| static void CopyV(void* dst) {} |
| |
| template <typename S, typename... Rest> |
| static void CopyV(void* dst, const S* src, int n, Rest&&... rest) { |
| FML_DCHECK(((uintptr_t)dst & (alignof(S) - 1)) == 0) |
| << "Expected " << dst << " to be aligned for at least " << alignof(S) |
| << " bytes."; |
| sk_careful_memcpy(dst, src, n * sizeof(S)); |
| CopyV(SkTAddOffset<void>(dst, n * sizeof(S)), std::forward<Rest>(rest)...); |
| } |
| |
| template <typename T, typename... Args> |
| void* DisplayListBuilder::Push(size_t pod, int op_inc, Args&&... args) { |
| size_t size = SkAlignPtr(sizeof(T) + pod); |
| FML_DCHECK(size < (1 << 24)); |
| if (used_ + size > allocated_) { |
| static_assert(SkIsPow2(DL_BUILDER_PAGE), |
| "This math needs updating for non-pow2."); |
| // Next greater multiple of DL_BUILDER_PAGE. |
| allocated_ = (used_ + size + DL_BUILDER_PAGE) & ~(DL_BUILDER_PAGE - 1); |
| storage_.realloc(allocated_); |
| FML_DCHECK(storage_.get()); |
| memset(storage_.get() + used_, 0, allocated_ - used_); |
| } |
| FML_DCHECK(used_ + size <= allocated_); |
| auto op = reinterpret_cast<T*>(storage_.get() + used_); |
| used_ += size; |
| new (op) T{std::forward<Args>(args)...}; |
| op->type = T::kType; |
| op->size = size; |
| op_count_ += op_inc; |
| return op + 1; |
| } |
| |
| sk_sp<DisplayList> DisplayListBuilder::Build() { |
| while (save_level_ > 0) { |
| restore(); |
| } |
| size_t bytes = used_; |
| int count = op_count_; |
| size_t nested_bytes = nested_bytes_; |
| int nested_count = nested_op_count_; |
| used_ = allocated_ = op_count_ = 0; |
| nested_bytes_ = nested_op_count_ = 0; |
| storage_.realloc(bytes); |
| return sk_sp<DisplayList>(new DisplayList(storage_.release(), bytes, count, |
| nested_bytes, nested_count, |
| cull_rect_)); |
| } |
| |
| DisplayListBuilder::DisplayListBuilder(const SkRect& cull_rect) |
| : cull_rect_(cull_rect) {} |
| |
| DisplayListBuilder::~DisplayListBuilder() { |
| uint8_t* ptr = storage_.get(); |
| if (ptr) { |
| DisposeOps(ptr, ptr + used_); |
| } |
| } |
| |
| void DisplayListBuilder::onSetAntiAlias(bool aa) { |
| Push<SetAntiAliasOp>(0, 0, current_anti_alias_ = aa); |
| } |
| void DisplayListBuilder::onSetDither(bool dither) { |
| Push<SetDitherOp>(0, 0, current_dither_ = dither); |
| } |
| void DisplayListBuilder::onSetInvertColors(bool invert) { |
| Push<SetInvertColorsOp>(0, 0, current_invert_colors_ = invert); |
| } |
| void DisplayListBuilder::onSetStrokeCap(SkPaint::Cap cap) { |
| Push<SetStrokeCapOp>(0, 0, current_stroke_cap_ = cap); |
| } |
| void DisplayListBuilder::onSetStrokeJoin(SkPaint::Join join) { |
| Push<SetStrokeJoinOp>(0, 0, current_stroke_join_ = join); |
| } |
| void DisplayListBuilder::onSetStyle(SkPaint::Style style) { |
| Push<SetStyleOp>(0, 0, current_style_ = style); |
| } |
| void DisplayListBuilder::onSetStrokeWidth(SkScalar width) { |
| Push<SetStrokeWidthOp>(0, 0, current_stroke_width_ = width); |
| } |
| void DisplayListBuilder::onSetStrokeMiter(SkScalar limit) { |
| Push<SetStrokeMiterOp>(0, 0, current_stroke_miter_ = limit); |
| } |
| void DisplayListBuilder::onSetColor(SkColor color) { |
| Push<SetColorOp>(0, 0, current_color_ = color); |
| } |
| void DisplayListBuilder::onSetBlendMode(SkBlendMode mode) { |
| current_blender_ = nullptr; |
| Push<SetBlendModeOp>(0, 0, current_blend_mode_ = mode); |
| } |
| void DisplayListBuilder::onSetBlender(sk_sp<SkBlender> 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<SetBlenderOp>(0, 0, std::move(blender)) |
| : Push<ClearBlenderOp>(0, 0); |
| } |
| } |
| void DisplayListBuilder::onSetShader(sk_sp<SkShader> shader) { |
| (current_shader_ = shader) // |
| ? Push<SetShaderOp>(0, 0, std::move(shader)) |
| : Push<ClearShaderOp>(0, 0); |
| } |
| void DisplayListBuilder::onSetImageFilter(sk_sp<SkImageFilter> filter) { |
| (current_image_filter_ = filter) // |
| ? Push<SetImageFilterOp>(0, 0, std::move(filter)) |
| : Push<ClearImageFilterOp>(0, 0); |
| } |
| void DisplayListBuilder::onSetColorFilter(sk_sp<SkColorFilter> filter) { |
| (current_color_filter_ = filter) // |
| ? Push<SetColorFilterOp>(0, 0, std::move(filter)) |
| : Push<ClearColorFilterOp>(0, 0); |
| } |
| void DisplayListBuilder::onSetPathEffect(sk_sp<SkPathEffect> effect) { |
| (current_path_effect_ = effect) // |
| ? Push<SetPathEffectOp>(0, 0, std::move(effect)) |
| : Push<ClearPathEffectOp>(0, 0); |
| } |
| void DisplayListBuilder::onSetMaskFilter(sk_sp<SkMaskFilter> filter) { |
| current_mask_sigma_ = kInvalidSigma; |
| (current_mask_filter_ = filter) // |
| ? Push<SetMaskFilterOp>(0, 0, std::move(filter)) |
| : Push<ClearMaskFilterOp>(0, 0); |
| } |
| 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<SetMaskBlurFilterNormalOp>(0, 0, sigma); |
| break; |
| case kSolid_SkBlurStyle: |
| Push<SetMaskBlurFilterSolidOp>(0, 0, sigma); |
| break; |
| case kOuter_SkBlurStyle: |
| Push<SetMaskBlurFilterOuterOp>(0, 0, sigma); |
| break; |
| case kInner_SkBlurStyle: |
| Push<SetMaskBlurFilterInnerOp>(0, 0, sigma); |
| break; |
| } |
| } |
| |
| 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<SkBlendMode> 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<SaveOp>(0, 1); |
| } |
| void DisplayListBuilder::restore() { |
| if (save_level_ > 0) { |
| Push<RestoreOp>(0, 1); |
| save_level_--; |
| } |
| } |
| void DisplayListBuilder::saveLayer(const SkRect* bounds, |
| bool restore_with_paint) { |
| save_level_++; |
| bounds // |
| ? Push<SaveLayerBoundsOp>(0, 1, *bounds, restore_with_paint) |
| : Push<SaveLayerOp>(0, 1, restore_with_paint); |
| } |
| |
| void DisplayListBuilder::translate(SkScalar tx, SkScalar ty) { |
| if (SkScalarIsFinite(tx) && SkScalarIsFinite(ty) && |
| (tx != 0.0 || ty != 0.0)) { |
| Push<TranslateOp>(0, 1, tx, ty); |
| } |
| } |
| void DisplayListBuilder::scale(SkScalar sx, SkScalar sy) { |
| if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) && |
| (sx != 1.0 || sy != 1.0)) { |
| Push<ScaleOp>(0, 1, sx, sy); |
| } |
| } |
| void DisplayListBuilder::rotate(SkScalar degrees) { |
| if (SkScalarMod(degrees, 360.0) != 0.0) { |
| Push<RotateOp>(0, 1, degrees); |
| } |
| } |
| void DisplayListBuilder::skew(SkScalar sx, SkScalar sy) { |
| if (SkScalarIsFinite(sx) && SkScalarIsFinite(sy) && |
| (sx != 0.0 || sy != 0.0)) { |
| Push<SkewOp>(0, 1, sx, sy); |
| } |
| } |
| |
| // clang-format off |
| |
| // 2x3 2D affine subset of a 4x4 transform in row major order |
| void DisplayListBuilder::transform2DAffine( |
| SkScalar mxx, SkScalar mxy, SkScalar mxt, |
| SkScalar myx, SkScalar myy, SkScalar myt) { |
| if (SkScalarsAreFinite(mxx, myx) && |
| SkScalarsAreFinite(mxy, myy) && |
| SkScalarsAreFinite(mxt, myt) && |
| !(mxx == 1 && mxy == 0 && mxt == 0 && |
| myx == 0 && myy == 1 && myt == 0)) { |
| Push<Transform2DAffineOp>(0, 1, |
| mxx, mxy, mxt, |
| myx, myy, myt); |
| } |
| } |
| // full 4x4 transform in row major order |
| void DisplayListBuilder::transformFullPerspective( |
| SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt, |
| SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt, |
| SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt, |
| SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) { |
| if ( mxz == 0 && |
| myz == 0 && |
| mzx == 0 && mzy == 0 && mzz == 1 && mzt == 0 && |
| mwx == 0 && mwy == 0 && mwz == 0 && mwt == 1) { |
| transform2DAffine(mxx, mxy, mxt, |
| myx, myy, myt); |
| } 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<TransformFullPerspectiveOp>(0, 1, |
| mxx, mxy, mxz, mxt, |
| myx, myy, myz, myt, |
| mzx, mzy, mzz, mzt, |
| mwx, mwy, mwz, mwt); |
| } |
| } |
| |
| // clang-format on |
| |
| void DisplayListBuilder::clipRect(const SkRect& rect, |
| SkClipOp clip_op, |
| bool is_aa) { |
| clip_op == SkClipOp::kIntersect // |
| ? Push<ClipIntersectRectOp>(0, 1, rect, is_aa) |
| : Push<ClipDifferenceRectOp>(0, 1, rect, is_aa); |
| } |
| void DisplayListBuilder::clipRRect(const SkRRect& rrect, |
| SkClipOp clip_op, |
| bool is_aa) { |
| if (rrect.isRect()) { |
| clipRect(rrect.rect(), clip_op, is_aa); |
| } else { |
| clip_op == SkClipOp::kIntersect // |
| ? Push<ClipIntersectRRectOp>(0, 1, rrect, is_aa) |
| : Push<ClipDifferenceRRectOp>(0, 1, rrect, is_aa); |
| } |
| } |
| void DisplayListBuilder::clipPath(const SkPath& path, |
| SkClipOp clip_op, |
| bool is_aa) { |
| if (!path.isInverseFillType()) { |
| SkRect rect; |
| if (path.isRect(&rect)) { |
| this->clipRect(rect, clip_op, is_aa); |
| return; |
| } |
| SkRRect rrect; |
| if (path.isOval(&rect)) { |
| rrect.setOval(rect); |
| this->clipRRect(rrect, clip_op, is_aa); |
| return; |
| } |
| if (path.isRRect(&rrect)) { |
| this->clipRRect(rrect, clip_op, is_aa); |
| return; |
| } |
| } |
| clip_op == SkClipOp::kIntersect // |
| ? Push<ClipIntersectPathOp>(0, 1, path, is_aa) |
| : Push<ClipDifferencePathOp>(0, 1, path, is_aa); |
| } |
| |
| void DisplayListBuilder::drawPaint() { |
| Push<DrawPaintOp>(0, 1); |
| } |
| void DisplayListBuilder::drawColor(SkColor color, SkBlendMode mode) { |
| Push<DrawColorOp>(0, 1, color, mode); |
| } |
| void DisplayListBuilder::drawLine(const SkPoint& p0, const SkPoint& p1) { |
| Push<DrawLineOp>(0, 1, p0, p1); |
| } |
| void DisplayListBuilder::drawRect(const SkRect& rect) { |
| Push<DrawRectOp>(0, 1, rect); |
| } |
| void DisplayListBuilder::drawOval(const SkRect& bounds) { |
| Push<DrawOvalOp>(0, 1, bounds); |
| } |
| void DisplayListBuilder::drawCircle(const SkPoint& center, SkScalar radius) { |
| Push<DrawCircleOp>(0, 1, center, radius); |
| } |
| void DisplayListBuilder::drawRRect(const SkRRect& rrect) { |
| if (rrect.isRect()) { |
| drawRect(rrect.rect()); |
| } else if (rrect.isOval()) { |
| drawOval(rrect.rect()); |
| } else { |
| Push<DrawRRectOp>(0, 1, rrect); |
| } |
| } |
| void DisplayListBuilder::drawDRRect(const SkRRect& outer, |
| const SkRRect& inner) { |
| Push<DrawDRRectOp>(0, 1, outer, inner); |
| } |
| void DisplayListBuilder::drawPath(const SkPath& path) { |
| Push<DrawPathOp>(0, 1, path); |
| } |
| |
| void DisplayListBuilder::drawArc(const SkRect& bounds, |
| SkScalar start, |
| SkScalar sweep, |
| bool useCenter) { |
| Push<DrawArcOp>(0, 1, bounds, start, sweep, useCenter); |
| } |
| void DisplayListBuilder::drawPoints(SkCanvas::PointMode mode, |
| uint32_t count, |
| const SkPoint pts[]) { |
| void* data_ptr; |
| FML_DCHECK(count < kMaxDrawPointsCount); |
| int bytes = count * sizeof(SkPoint); |
| switch (mode) { |
| case SkCanvas::PointMode::kPoints_PointMode: |
| data_ptr = Push<DrawPointsOp>(bytes, 1, count); |
| break; |
| case SkCanvas::PointMode::kLines_PointMode: |
| data_ptr = Push<DrawLinesOp>(bytes, 1, count); |
| break; |
| case SkCanvas::PointMode::kPolygon_PointMode: |
| data_ptr = Push<DrawPolygonOp>(bytes, 1, count); |
| break; |
| default: |
| FML_DCHECK(false); |
| return; |
| } |
| CopyV(data_ptr, pts, count); |
| } |
| void DisplayListBuilder::drawVertices(const sk_sp<SkVertices> vertices, |
| SkBlendMode mode) { |
| Push<DrawVerticesOp>(0, 1, std::move(vertices), mode); |
| } |
| |
| void DisplayListBuilder::drawImage(const sk_sp<SkImage> image, |
| const SkPoint point, |
| const SkSamplingOptions& sampling, |
| bool render_with_attributes) { |
| render_with_attributes |
| ? Push<DrawImageWithAttrOp>(0, 1, std::move(image), point, sampling) |
| : Push<DrawImageOp>(0, 1, std::move(image), point, sampling); |
| } |
| void DisplayListBuilder::drawImageRect(const sk_sp<SkImage> image, |
| const SkRect& src, |
| const SkRect& dst, |
| const SkSamplingOptions& sampling, |
| bool render_with_attributes, |
| SkCanvas::SrcRectConstraint constraint) { |
| Push<DrawImageRectOp>(0, 1, std::move(image), src, dst, sampling, |
| render_with_attributes, constraint); |
| } |
| void DisplayListBuilder::drawImageNine(const sk_sp<SkImage> image, |
| const SkIRect& center, |
| const SkRect& dst, |
| SkFilterMode filter, |
| bool render_with_attributes) { |
| render_with_attributes |
| ? Push<DrawImageNineWithAttrOp>(0, 1, std::move(image), center, dst, |
| filter) |
| : Push<DrawImageNineOp>(0, 1, std::move(image), center, dst, filter); |
| } |
| void DisplayListBuilder::drawImageLattice(const sk_sp<SkImage> image, |
| const SkCanvas::Lattice& lattice, |
| const SkRect& dst, |
| SkFilterMode filter, |
| bool render_with_attributes) { |
| int xDivCount = lattice.fXCount; |
| int yDivCount = lattice.fYCount; |
| FML_DCHECK((lattice.fRectTypes == nullptr) || (lattice.fColors != nullptr)); |
| int cellCount = lattice.fRectTypes && lattice.fColors |
| ? (xDivCount + 1) * (yDivCount + 1) |
| : 0; |
| size_t bytes = |
| (xDivCount + yDivCount) * sizeof(int) + |
| cellCount * (sizeof(SkColor) + sizeof(SkCanvas::Lattice::RectType)); |
| SkIRect src = lattice.fBounds ? *lattice.fBounds : image->bounds(); |
| void* pod = this->Push<DrawImageLatticeOp>( |
| 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); |
| } |
| void DisplayListBuilder::drawAtlas(const sk_sp<SkImage> atlas, |
| const SkRSXform xform[], |
| const SkRect tex[], |
| const SkColor colors[], |
| int count, |
| SkBlendMode mode, |
| const SkSamplingOptions& sampling, |
| const SkRect* cull_rect, |
| bool render_with_attributes) { |
| int bytes = count * (sizeof(SkRSXform) + sizeof(SkRect)); |
| void* data_ptr; |
| if (colors != nullptr) { |
| bytes += count * sizeof(SkColor); |
| if (cull_rect != nullptr) { |
| data_ptr = Push<DrawAtlasCulledOp>(bytes, 1, std::move(atlas), count, |
| mode, sampling, true, *cull_rect, |
| render_with_attributes); |
| } else { |
| data_ptr = Push<DrawAtlasOp>(bytes, 1, std::move(atlas), count, mode, |
| sampling, true, render_with_attributes); |
| } |
| CopyV(data_ptr, xform, count, tex, count, colors, count); |
| } else { |
| if (cull_rect != nullptr) { |
| data_ptr = Push<DrawAtlasCulledOp>(bytes, 1, std::move(atlas), count, |
| mode, sampling, false, *cull_rect, |
| render_with_attributes); |
| } else { |
| data_ptr = Push<DrawAtlasOp>(bytes, 1, std::move(atlas), count, mode, |
| sampling, false, render_with_attributes); |
| } |
| CopyV(data_ptr, xform, count, tex, count); |
| } |
| } |
| |
| void DisplayListBuilder::drawPicture(const sk_sp<SkPicture> picture, |
| const SkMatrix* matrix, |
| bool render_with_attributes) { |
| matrix // |
| ? Push<DrawSkPictureMatrixOp>(0, 1, std::move(picture), *matrix, |
| render_with_attributes) |
| : Push<DrawSkPictureOp>(0, 1, std::move(picture), render_with_attributes); |
| // The non-nested op count accumulated in the |Push| method will include |
| // this call to |drawPicture| for non-nested op count metrics. |
| // But, for nested op count metrics we want the |drawPicture| call itself |
| // to be transparent. So we subtract 1 from our accumulated nested count to |
| // balance out against the 1 that was accumulated into the regular count. |
| // This behavior is identical to the way SkPicture computes nested op counts. |
| nested_op_count_ += picture->approximateOpCount(true) - 1; |
| nested_bytes_ += picture->approximateBytesUsed(); |
| } |
| void DisplayListBuilder::drawDisplayList( |
| const sk_sp<DisplayList> display_list) { |
| Push<DrawDisplayListOp>(0, 1, std::move(display_list)); |
| // The non-nested op count accumulated in the |Push| method will include |
| // this call to |drawDisplayList| for non-nested op count metrics. |
| // But, for nested op count metrics we want the |drawDisplayList| call itself |
| // to be transparent. So we subtract 1 from our accumulated nested count to |
| // balance out against the 1 that was accumulated into the regular count. |
| // This behavior is identical to the way SkPicture computes nested op counts. |
| nested_op_count_ += display_list->op_count(true) - 1; |
| nested_bytes_ += display_list->bytes(true); |
| } |
| void DisplayListBuilder::drawTextBlob(const sk_sp<SkTextBlob> blob, |
| SkScalar x, |
| SkScalar y) { |
| Push<DrawTextBlobOp>(0, 1, std::move(blob), x, y); |
| } |
| void DisplayListBuilder::drawShadow(const SkPath& path, |
| const SkColor color, |
| const SkScalar elevation, |
| bool transparent_occluder, |
| SkScalar dpr) { |
| transparent_occluder // |
| ? Push<DrawShadowTransparentOccluderOp>(0, 1, path, color, elevation, dpr) |
| : Push<DrawShadowOp>(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 |