blob: 7da9ec2b55dcbcc79a9805401cfc272dd12c593a [file] [log] [blame]
// 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 "flutter/fml/logging.h"
#include "third_party/skia/include/core/SkImageFilter.h"
#include "third_party/skia/include/core/SkMaskFilter.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRSXform.h"
#include "third_party/skia/include/core/SkTextBlob.h"
namespace flutter {
const SkSamplingOptions DisplayList::NearestSampling =
SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone);
const SkSamplingOptions DisplayList::LinearSampling =
SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone);
const SkSamplingOptions DisplayList::MipmapSampling =
SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
const SkSamplingOptions DisplayList::CubicSampling =
SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f});
// Most Ops can be bulk compared using memcmp because they contain
// only numeric values or constructs that are constructed from numeric
// values.
//
// Some contain sk_sp<> references which can also be bulk compared
// to see if they are pointing to the same reference. (Note that
// two sk_sp<> that refer to the same object are themselves ==.)
//
// Only a DLOp that wants to do a deep compare needs to override the
// DLOp::equals() method and return a value of kEqual or kNotEqual.
enum class DisplayListCompare {
// The Op is deferring comparisons to a bulk memcmp performed lazily
// across all bulk-comparable ops.
kUseBulkCompare,
// The Op provided a specific equals method that spotted a difference
kNotEqual,
// The Op provided a specific equals method that saw no differences
kEqual,
};
#pragma pack(push, DLOp_Alignment, 8)
// Assuming a 64-bit platform (most of our platforms at this time?)
// the following comments are a "worst case" assessment of how well
// these structures pack into memory. They may be packed more tightly
// on some of the 32-bit platforms that we see in older phones.
//
// Struct allocation in the DL memory is aligned to a void* boundary
// which means that the minimum (aligned) struct size will be 8 bytes.
// The DLOp base uses 4 bytes so each Op-specific struct gets 4 bytes
// of data for "free" and works best when it packs well into an 8-byte
// aligned size.
struct DLOp {
DisplayListOpType type : 8;
uint32_t size : 24;
DisplayListCompare equals(const DLOp* other) const {
return DisplayListCompare::kUseBulkCompare;
}
};
// 4 byte header + 4 byte payload packs into minimum 8 bytes
#define DEFINE_SET_BOOL_OP(name) \
struct Set##name##Op final : DLOp { \
static const auto kType = DisplayListOpType::kSet##name; \
\
Set##name##Op(bool value) : value(value) {} \
\
const bool value; \
\
void dispatch(Dispatcher& dispatcher) const { \
dispatcher.set##name(value); \
} \
};
DEFINE_SET_BOOL_OP(AA)
DEFINE_SET_BOOL_OP(Dither)
DEFINE_SET_BOOL_OP(InvertColors)
#undef DEFINE_SET_BOOL_OP
// 4 byte header + 4 byte payload packs into minimum 8 bytes
#define DEFINE_SET_ENUM_OP(name) \
struct Set##name##s##Op final : DLOp { \
static const auto kType = DisplayListOpType::kSet##name##s; \
\
Set##name##s##Op(SkPaint::name value) : value(value) {} \
\
const SkPaint::name value; \
\
void dispatch(Dispatcher& dispatcher) const { \
dispatcher.set##name##s(value); \
} \
};
DEFINE_SET_ENUM_OP(Cap)
DEFINE_SET_ENUM_OP(Join)
#undef DEFINE_SET_ENUM_OP
// 4 byte header + 4 byte payload packs into minimum 8 bytes
struct SetDrawStyleOp final : DLOp {
static const auto kType = DisplayListOpType::kSetDrawStyle;
SetDrawStyleOp(SkPaint::Style style) : style(style) {}
const SkPaint::Style style;
void dispatch(Dispatcher& dispatcher) const {
dispatcher.setDrawStyle(style);
}
};
// 4 byte header + 4 byte payload packs into minimum 8 bytes
struct SetStrokeWidthOp final : DLOp {
static const auto kType = DisplayListOpType::kSetStrokeWidth;
SetStrokeWidthOp(SkScalar width) : width(width) {}
const SkScalar width;
void dispatch(Dispatcher& dispatcher) const {
dispatcher.setStrokeWidth(width);
}
};
// 4 byte header + 4 byte payload packs into minimum 8 bytes
struct SetMiterLimitOp final : DLOp {
static const auto kType = DisplayListOpType::kSetMiterLimit;
SetMiterLimitOp(SkScalar limit) : limit(limit) {}
const SkScalar limit;
void dispatch(Dispatcher& dispatcher) const {
dispatcher.setMiterLimit(limit);
}
};
// 4 byte header + 4 byte payload packs into minimum 8 bytes
struct SetColorOp final : DLOp {
static const auto kType = DisplayListOpType::kSetColor;
SetColorOp(SkColor color) : color(color) {}
const SkColor color;
void dispatch(Dispatcher& dispatcher) const { dispatcher.setColor(color); }
};
// 4 byte header + 4 byte payload packs into minimum 8 bytes
struct SetBlendModeOp final : DLOp {
static const auto kType = DisplayListOpType::kSetBlendMode;
SetBlendModeOp(SkBlendMode mode) : mode(mode) {}
const SkBlendMode mode;
void dispatch(Dispatcher& dispatcher) const { dispatcher.setBlendMode(mode); }
};
// Clear: 4 byte header + unused 4 byte payload uses 8 bytes
// (4 bytes unused)
// Set: 4 byte header + an sk_sp (ptr) uses 16 bytes due to the
// alignment of the ptr.
// (4 bytes unused)
#define DEFINE_SET_CLEAR_SKREF_OP(name, field) \
struct Clear##name##Op final : DLOp { \
static const auto kType = DisplayListOpType::kClear##name; \
\
Clear##name##Op() {} \
\
void dispatch(Dispatcher& dispatcher) const { \
dispatcher.set##name(nullptr); \
} \
}; \
struct Set##name##Op final : DLOp { \
static const auto kType = DisplayListOpType::kSet##name; \
\
Set##name##Op(sk_sp<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; \
\
SetMaskBlurFilter##name##Op(SkScalar sigma) : sigma(sigma) {} \
\
SkScalar sigma; \
\
void dispatch(Dispatcher& dispatcher) const { \
dispatcher.setMaskBlurFilter(style, sigma); \
} \
};
DEFINE_MASK_BLUR_FILTER_OP(Normal, kNormal_SkBlurStyle)
DEFINE_MASK_BLUR_FILTER_OP(Solid, kSolid_SkBlurStyle)
DEFINE_MASK_BLUR_FILTER_OP(Inner, kInner_SkBlurStyle)
DEFINE_MASK_BLUR_FILTER_OP(Outer, kOuter_SkBlurStyle)
#undef DEFINE_MASK_BLUR_FILTER_OP
// 4 byte header + no payload uses minimum 8 bytes (4 bytes unused)
struct SaveOp final : DLOp {
static const auto kType = DisplayListOpType::kSave;
SaveOp() {}
void dispatch(Dispatcher& dispatcher) const { dispatcher.save(); }
};
// 4 byte header + 4 byte payload packs into minimum 8 bytes
struct SaveLayerOp final : DLOp {
static const auto kType = DisplayListOpType::kSaveLayer;
SaveLayerOp(bool with_paint) : with_paint(with_paint) {}
bool with_paint;
void dispatch(Dispatcher& dispatcher) const {
dispatcher.saveLayer(nullptr, with_paint);
}
};
// 4 byte header + 20 byte payload packs evenly into 24 bytes
struct SaveLayerBoundsOp final : DLOp {
static const auto kType = DisplayListOpType::kSaveLayerBounds;
SaveLayerBoundsOp(SkRect rect, bool with_paint)
: with_paint(with_paint), rect(rect) {}
bool with_paint;
const SkRect rect;
void dispatch(Dispatcher& dispatcher) const {
dispatcher.saveLayer(&rect, with_paint);
}
};
// 4 byte header + no payload uses minimum 8 bytes (4 bytes unused)
struct RestoreOp final : DLOp {
static const auto kType = DisplayListOpType::kRestore;
RestoreOp() {}
void dispatch(Dispatcher& dispatcher) const { dispatcher.restore(); }
};
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
// (4 bytes unused)
struct TranslateOp final : DLOp {
static const auto kType = DisplayListOpType::kTranslate;
TranslateOp(SkScalar tx, SkScalar ty) : tx(tx), ty(ty) {}
const SkScalar tx;
const SkScalar ty;
void dispatch(Dispatcher& dispatcher) const { dispatcher.translate(tx, ty); }
};
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
// (4 bytes unused)
struct ScaleOp final : DLOp {
static const auto kType = DisplayListOpType::kScale;
ScaleOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {}
const SkScalar sx;
const SkScalar sy;
void dispatch(Dispatcher& dispatcher) const { dispatcher.scale(sx, sy); }
};
// 4 byte header + 4 byte payload packs into minimum 8 bytes
struct RotateOp final : DLOp {
static const auto kType = DisplayListOpType::kRotate;
RotateOp(SkScalar degrees) : degrees(degrees) {}
const SkScalar degrees;
void dispatch(Dispatcher& dispatcher) const { dispatcher.rotate(degrees); }
};
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
// (4 bytes unused)
struct SkewOp final : DLOp {
static const auto kType = DisplayListOpType::kSkew;
SkewOp(SkScalar sx, SkScalar sy) : sx(sx), sy(sy) {}
const SkScalar sx;
const SkScalar sy;
void dispatch(Dispatcher& dispatcher) const { dispatcher.skew(sx, sy); }
};
// 4 byte header + 24 byte payload uses 28 bytes but is rounded up to 32 bytes
// (4 bytes unused)
struct Transform2x3Op final : DLOp {
static const auto kType = DisplayListOpType::kTransform2x3;
Transform2x3Op(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt)
: mxx(mxx), mxy(mxy), mxt(mxt), myx(myx), myy(myy), myt(myt) {}
const SkScalar mxx, mxy, mxt;
const SkScalar myx, myy, myt;
void dispatch(Dispatcher& dispatcher) const {
dispatcher.transform2x3(mxx, mxy, mxt, myx, myy, myt);
}
};
// 4 byte header + 36 byte payload packs evenly into 40 bytes
struct Transform3x3Op final : DLOp {
static const auto kType = DisplayListOpType::kTransform3x3;
Transform3x3Op(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt,
SkScalar px,
SkScalar py,
SkScalar pt)
: mxx(mxx),
mxy(mxy),
mxt(mxt),
myx(myx),
myy(myy),
myt(myt),
px(px),
py(py),
pt(pt) {}
const SkScalar mxx, mxy, mxt;
const SkScalar myx, myy, myt;
const SkScalar px, py, pt;
void dispatch(Dispatcher& dispatcher) const {
dispatcher.transform3x3(mxx, mxy, mxt, myx, myy, myt, px, py, pt);
}
};
// 4 byte header + 4 byte common payload packs into minimum 8 bytes
// SkRect is 16 more bytes, which packs efficiently into 24 bytes total
// SkRRect is 52 more bytes, which rounds up to 56 bytes (4 bytes unused)
// which packs into 64 bytes total
// SkPath is 16 more bytes, which packs efficiently into 24 bytes total
//
// We could pack the clip_op and the bool both into the free 4 bytes after
// the header, but the Windows compiler keeps wanting to expand that
// packing into more bytes than needed (even when they are declared as
// packed bit fields!)
#define DEFINE_CLIP_SHAPE_OP(shapetype, clipop) \
struct Clip##clipop##shapetype##Op final : DLOp { \
static const auto kType = DisplayListOpType::kClip##clipop##shapetype; \
\
Clip##clipop##shapetype##Op(Sk##shapetype shape, bool is_aa) \
: is_aa(is_aa), shape(shape) {} \
\
const bool is_aa; \
const Sk##shapetype shape; \
\
void dispatch(Dispatcher& dispatcher) const { \
dispatcher.clip##shapetype(shape, is_aa, SkClipOp::k##clipop); \
} \
};
DEFINE_CLIP_SHAPE_OP(Rect, Intersect)
DEFINE_CLIP_SHAPE_OP(RRect, Intersect)
DEFINE_CLIP_SHAPE_OP(Rect, Difference)
DEFINE_CLIP_SHAPE_OP(RRect, Difference)
#undef DEFINE_CLIP_SHAPE_OP
#define DEFINE_CLIP_PATH_OP(clipop) \
struct Clip##clipop##PathOp final : DLOp { \
static const auto kType = DisplayListOpType::kClip##clipop##Path; \
\
Clip##clipop##PathOp(SkPath path, bool is_aa) \
: is_aa(is_aa), path(path) {} \
\
const bool is_aa; \
const SkPath path; \
\
void dispatch(Dispatcher& dispatcher) const { \
dispatcher.clipPath(path, is_aa, SkClipOp::k##clipop); \
} \
\
DisplayListCompare equals(const Clip##clipop##PathOp* other) const { \
return is_aa == other->is_aa && path == other->path \
? DisplayListCompare::kEqual \
: DisplayListCompare::kNotEqual; \
} \
};
DEFINE_CLIP_PATH_OP(Intersect)
DEFINE_CLIP_PATH_OP(Difference)
#undef DEFINE_CLIP_PATH_OP
// 4 byte header + no payload uses minimum 8 bytes (4 bytes unused)
struct DrawPaintOp final : DLOp {
static const auto kType = DisplayListOpType::kDrawPaint;
DrawPaintOp() {}
void dispatch(Dispatcher& dispatcher) const { dispatcher.drawPaint(); }
};
// 4 byte header + 8 byte payload uses 12 bytes but is rounded up to 16 bytes
// (4 bytes unused)
struct DrawColorOp final : DLOp {
static const auto kType = DisplayListOpType::kDrawColor;
DrawColorOp(SkColor color, SkBlendMode mode) : color(color), mode(mode) {}
const SkColor color;
const SkBlendMode mode;
void dispatch(Dispatcher& dispatcher) const {
dispatcher.drawColor(color, mode);
}
};
// The common data is a 4 byte header with an unused 4 bytes
// SkRect is 16 more bytes, using 20 bytes which rounds up to 24 bytes total
// (4 bytes unused)
// SkOval is same as SkRect
// SkRRect is 52 more bytes, which packs efficiently into 56 bytes total
#define DEFINE_DRAW_1ARG_OP(op_name, arg_type, arg_name) \
struct Draw##op_name##Op final : DLOp { \
static const auto kType = DisplayListOpType::kDraw##op_name; \
\
Draw##op_name##Op(arg_type arg_name) : arg_name(arg_name) {} \
\
const arg_type arg_name; \
\
void dispatch(Dispatcher& dispatcher) const { \
dispatcher.draw##op_name(arg_name); \
} \
};
DEFINE_DRAW_1ARG_OP(Rect, SkRect, rect)
DEFINE_DRAW_1ARG_OP(Oval, SkRect, oval)
DEFINE_DRAW_1ARG_OP(RRect, SkRRect, rrect)
#undef DEFINE_DRAW_1ARG_OP
// 4 byte header + 16 byte payload uses 20 bytes but is rounded up to 24 bytes
// (4 bytes unused)
struct DrawPathOp final : DLOp {
static const auto kType = DisplayListOpType::kDrawPath;
DrawPathOp(SkPath path) : path(path) {}
const SkPath path;
void dispatch(Dispatcher& dispatcher) const { dispatcher.drawPath(path); }
DisplayListCompare equals(const DrawPathOp* other) const {
return path == other->path ? DisplayListCompare::kEqual
: DisplayListCompare::kNotEqual;
}
};
// The common data is a 4 byte header with an unused 4 bytes
// 2 x SkPoint is 16 more bytes, using 20 bytes rounding up to 24 bytes total
// (4 bytes unused)
// SkPoint + SkScalar is 12 more bytes, packing efficiently into 16 bytes total
// 2 x SkRRect is 104 more bytes, using 108 and rounding up to 112 bytes total
// (4 bytes unused)
#define DEFINE_DRAW_2ARG_OP(op_name, type1, name1, type2, name2) \
struct Draw##op_name##Op final : DLOp { \
static const auto kType = DisplayListOpType::kDraw##op_name; \
\
Draw##op_name##Op(type1 name1, type2 name2) \
: name1(name1), name2(name2) {} \
\
const type1 name1; \
const type2 name2; \
\
void dispatch(Dispatcher& dispatcher) const { \
dispatcher.draw##op_name(name1, name2); \
} \
};
DEFINE_DRAW_2ARG_OP(Line, SkPoint, p0, SkPoint, p1)
DEFINE_DRAW_2ARG_OP(Circle, SkPoint, center, SkScalar, radius)
DEFINE_DRAW_2ARG_OP(DRRect, SkRRect, outer, SkRRect, inner)
#undef DEFINE_DRAW_2ARG_OP
// 4 byte header + 28 byte payload packs efficiently into 32 bytes
struct DrawArcOp final : DLOp {
static const auto kType = DisplayListOpType::kDrawArc;
DrawArcOp(SkRect bounds, SkScalar start, SkScalar sweep, bool center)
: bounds(bounds), start(start), sweep(sweep), center(center) {}
const SkRect bounds;
const SkScalar start;
const SkScalar sweep;
const bool center;
void dispatch(Dispatcher& dispatcher) const {
dispatcher.drawArc(bounds, start, sweep, center);
}
};
// 4 byte header + 4 byte fixed payload packs efficiently into 8 bytes
// But then there is a list of points following the structure which
// is guaranteed to be a multiple of 8 bytes (SkPoint is 8 bytes)
// so this op will always pack efficiently
// The point type is packed into 3 different OpTypes to avoid expanding
// the fixed payload beyond the 8 bytes
#define DEFINE_DRAW_POINTS_OP(name, mode) \
struct Draw##name##Op final : DLOp { \
static const auto kType = DisplayListOpType::kDraw##name; \
\
Draw##name##Op(uint32_t count) : count(count) {} \
\
const uint32_t count; \
\
void dispatch(Dispatcher& dispatcher) const { \
const SkPoint* pts = reinterpret_cast<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
struct DrawImageOp final : DLOp {
static const auto kType = DisplayListOpType::kDrawImage;
DrawImageOp(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);
}
};
// 4 byte header + 60 byte payload packs efficiently into 64 bytes
//
// The constraint could be stored in the struct, but it would not pack
// efficiently so 2 variants are defined instead.
#define DEFINE_DRAW_IMAGE_RECT_OP(name, constraint) \
struct Draw##name##Op final : DLOp { \
static const auto kType = DisplayListOpType::kDraw##name; \
\
Draw##name##Op(const sk_sp<SkImage> image, \
const SkRect& src, \
const SkRect& dst, \
const SkSamplingOptions& sampling) \
: src(src), dst(dst), sampling(sampling), image(std::move(image)) {} \
\
const SkRect src; \
const SkRect dst; \
const SkSamplingOptions sampling; \
const sk_sp<SkImage> image; \
\
void dispatch(Dispatcher& dispatcher) const { \
dispatcher.drawImageRect(image, src, dst, sampling, constraint); \
} \
};
DEFINE_DRAW_IMAGE_RECT_OP(ImageRectStrict, SkCanvas::kStrict_SrcRectConstraint)
DEFINE_DRAW_IMAGE_RECT_OP(ImageRectFast, SkCanvas::kFast_SrcRectConstraint)
#undef DEFINE_DRAW_IMAGE_RECT_OP
// 4 byte header + 44 byte payload packs efficiently into 48 bytes
struct DrawImageNineOp final : DLOp {
static const auto kType = DisplayListOpType::kDrawImageNine;
DrawImageNineOp(const sk_sp<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);
}
};
// 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);
}
};
#define DRAW_ATLAS_NO_COLORS_ARRAY(tex, count) nullptr
#define DRAW_ATLAS_HAS_COLORS_ARRAY(tex, count) \
reinterpret_cast<const SkColor*>(tex + count)
#define DRAW_ATLAS_NO_CULLING_ARGS \
const sk_sp<SkImage> atlas, int count, SkBlendMode mode, \
const SkSamplingOptions &sampling
#define DRAW_ATLAS_NO_CULLING_INIT \
count(count), mode(mode), sampling(sampling), atlas(std::move(atlas))
#define DRAW_ATLAS_NO_CULLING_FIELDS \
const int count; \
const SkBlendMode mode; \
const SkSamplingOptions sampling; \
const sk_sp<SkImage> atlas
#define DRAW_ATLAS_NO_CULLING_P_ARG nullptr
#define DRAW_ATLAS_HAS_CULLING_ARGS \
DRAW_ATLAS_NO_CULLING_ARGS, const SkRect& cull
#define DRAW_ATLAS_HAS_CULLING_INIT DRAW_ATLAS_NO_CULLING_INIT, cull(cull)
#define DRAW_ATLAS_HAS_CULLING_FIELDS \
DRAW_ATLAS_NO_CULLING_FIELDS; \
const SkRect cull
#define DRAW_ATLAS_HAS_CULLING_P_ARG &cull
// 4 byte header + 36 byte common payload packs efficiently into 40 bytes
// Culling version has an additional 16 bytes of payload for 56 bytes
// So all 4 versions of the base structure pack well.
// Each of these is then followed by a number of lists.
// SkRSXform list is a multiple of 16 bytes so it is always packed well
// SkRect list is also a multiple of 16 bytes so it also packs well
// SkColor list only packs well if the count is even, otherwise there
// can be 4 unusued bytes at the end.
#define DEFINE_DRAW_ATLAS_OP(name, colors, cull) \
struct Draw##name##Op final : DLOp { \
static const auto kType = DisplayListOpType::kDraw##name; \
\
Draw##name##Op(DRAW_ATLAS_##cull##_ARGS) : DRAW_ATLAS_##cull##_INIT {} \
\
DRAW_ATLAS_##cull##_FIELDS; \
\
void dispatch(Dispatcher& dispatcher) const { \
const SkRSXform* xform = reinterpret_cast<const SkRSXform*>(this + 1); \
const SkRect* tex = reinterpret_cast<const SkRect*>(xform + count); \
const SkColor* colors = DRAW_ATLAS_##colors##_ARRAY(tex, count); \
dispatcher.drawAtlas(atlas, xform, tex, colors, count, mode, sampling, \
DRAW_ATLAS_##cull##_P_ARG); \
} \
};
DEFINE_DRAW_ATLAS_OP(Atlas, NO_COLORS, NO_CULLING)
DEFINE_DRAW_ATLAS_OP(AtlasColored, HAS_COLORS, NO_CULLING)
DEFINE_DRAW_ATLAS_OP(AtlasCulled, NO_COLORS, HAS_CULLING)
DEFINE_DRAW_ATLAS_OP(AtlasColoredCulled, HAS_COLORS, HAS_CULLING)
#undef DEFINE_DRAW_ATLAS_OP
#undef DRAW_ATLAS_NO_COLORS_ARRAY
#undef DRAW_ATLAS_HAS_COLORS_ARRAY
#undef DRAW_ATLAS_NO_CULLING_ARGS
#undef DRAW_ATLAS_NO_CULLING_INIT
#undef DRAW_ATLAS_NO_CULLING_FIELDS
#undef DRAW_ATLAS_NO_CULLING_P_ARG
#undef DRAW_ATLAS_HAS_CULLING_ARGS
#undef DRAW_ATLAS_HAS_CULLING_INIT
#undef DRAW_ATLAS_HAS_CULLING_FIELDS
#undef DRAW_ATLAS_HAS_CULLING_P_ARG
// 4 byte header + 12 byte payload packs evenly into 16 bytes
struct DrawSkPictureOp final : DLOp {
static const auto kType = DisplayListOpType::kDrawSkPicture;
DrawSkPictureOp(sk_sp<SkPicture> picture, bool with_layer)
: with_layer(with_layer), picture(std::move(picture)) {}
const bool with_layer;
const sk_sp<SkPicture> picture;
void dispatch(Dispatcher& dispatcher) const {
dispatcher.drawPicture(picture, nullptr, with_layer);
}
};
// 4 byte header + 52 byte payload packs evenly into 56 bytes
struct DrawSkPictureMatrixOp final : DLOp {
static const auto kType = DisplayListOpType::kDrawSkPictureMatrix;
DrawSkPictureMatrixOp(sk_sp<SkPicture> picture,
const SkMatrix matrix,
bool with_layer)
: with_layer(with_layer), picture(std::move(picture)), matrix(matrix) {}
const bool with_layer;
const sk_sp<SkPicture> picture;
const SkMatrix matrix;
void dispatch(Dispatcher& dispatcher) const {
dispatcher.drawPicture(picture, &matrix, with_layer);
}
};
// 4 byte header + ptr aligned payload uses 12 bytes rounde up to 16
// (4 bytes unused)
struct DrawDisplayListOp final : DLOp {
static const auto kType = DisplayListOpType::kDrawDisplayList;
DrawDisplayListOp(const sk_sp<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, occludes) \
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, occludes, dpr); \
} \
};
DEFINE_DRAW_SHADOW_OP(Shadow, false)
DEFINE_DRAW_SHADOW_OP(ShadowOccludes, true)
#undef DEFINE_DRAW_SHADOW_OP
#pragma pack(pop, DLOp_Alignment)
void DisplayList::ComputeBounds() {
DisplayListBoundsCalculator calculator(bounds_cull_);
Dispatch(calculator);
bounds_ = calculator.getBounds();
}
void DisplayList::Dispatch(Dispatcher& dispatcher,
uint8_t* ptr,
uint8_t* end) const {
while (ptr < end) {
auto op = (const DLOp*)ptr;
ptr += op->size;
FML_DCHECK(ptr <= end);
switch (op->type) {
#define DL_OP_DISPATCH(name) \
case DisplayListOpType::k##name: \
static_cast<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 = (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_DISPATCH
default:
FML_DCHECK(false);
return;
}
}
}
static bool CompareOps(uint8_t* ptrA,
uint8_t* endA,
uint8_t* ptrB,
uint8_t* endB) {
// These conditions are checked by the caller...
FML_DCHECK((endA - ptrA) == (endB - ptrB));
FML_DCHECK(ptrA != ptrB);
uint8_t* bulkStartA = ptrA;
uint8_t* bulkStartB = ptrB;
while (ptrA < endA && ptrB < endB) {
auto opA = (const DLOp*)ptrA;
auto opB = (const DLOp*)ptrB;
if (opA->type != opB->type || opA->size != opB->size) {
return false;
}
ptrA += opA->size;
ptrB += opB->size;
FML_DCHECK(ptrA <= endA);
FML_DCHECK(ptrB <= endB);
DisplayListCompare result;
switch (opA->type) {
#define DL_OP_EQUALS(name) \
case DisplayListOpType::k##name: \
result = static_cast<const name##Op*>(opA)->equals( \
static_cast<const name##Op*>(opB)); \
break;
FOR_EACH_DISPLAY_LIST_OP(DL_OP_EQUALS)
#undef DL_OP_DISPATCH
default:
FML_DCHECK(false);
return false;
}
switch (result) {
case DisplayListCompare::kNotEqual:
return false;
case DisplayListCompare::kUseBulkCompare:
break;
case DisplayListCompare::kEqual:
// Check if we have a backlog of bytes to bulk compare and then
// reset the bulk compare pointers to the address following this op
auto bulkBytes = reinterpret_cast<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 (used_ != other.used_ || op_count_ != other.op_count_) {
return false;
}
if (ptr_ == other.ptr_) {
return true;
}
return CompareOps(ptr_, ptr_ + used_, other.ptr_, other.ptr_ + other.used_);
}
DisplayList::DisplayList(uint8_t* ptr,
size_t used,
int op_count,
const SkRect& cull)
: ptr_(ptr),
used_(used),
op_count_(op_count),
bounds_({0, 0, -1, -1}),
bounds_cull_(cull) {
static std::atomic<uint32_t> nextID{1};
do {
unique_id_ = nextID.fetch_add(+1, std::memory_order_relaxed);
} while (unique_id_ == 0);
}
DisplayList::~DisplayList() {
DisposeOps(ptr_, ptr_ + used_);
}
#define DL_BUILDER_PAGE 4096
// CopyV(dst, src,n, src,n, ...) copies any number of typed srcs into dst.
static void CopyV(void* dst) {}
template <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, Args&&... args) {
size_t size = SkAlignPtr(sizeof(T) + pod);
FML_DCHECK(size < (1 << 24));
if (used_ + size > allocated_) {
static_assert(SkIsPow2(DL_BUILDER_PAGE),
"This math needs updating for non-pow2.");
// Next greater multiple of DL_BUILDER_PAGE.
allocated_ = (used_ + size + DL_BUILDER_PAGE) & ~(DL_BUILDER_PAGE - 1);
storage_.realloc(allocated_);
FML_DCHECK(storage_.get());
memset(storage_.get() + used_, 0, allocated_ - used_);
}
FML_DCHECK(used_ + size <= allocated_);
auto op = (T*)(storage_.get() + used_);
used_ += size;
new (op) T{std::forward<Args>(args)...};
op->type = T::kType;
op->size = size;
op_count_++;
return op + 1;
}
sk_sp<DisplayList> DisplayListBuilder::Build() {
while (save_level_ > 0) {
restore();
}
size_t used = used_;
int count = op_count_;
used_ = allocated_ = op_count_ = 0;
storage_.realloc(used);
return sk_sp<DisplayList>(
new DisplayList(storage_.release(), used, count, cull_));
}
DisplayListBuilder::DisplayListBuilder(const SkRect& cull) : cull_(cull) {}
DisplayListBuilder::~DisplayListBuilder() {
uint8_t* ptr = storage_.get();
if (ptr) {
DisposeOps(ptr, ptr + used_);
}
}
void DisplayListBuilder::setAA(bool aa) {
Push<SetAAOp>(0, aa);
}
void DisplayListBuilder::setDither(bool dither) {
Push<SetDitherOp>(0, dither);
}
void DisplayListBuilder::setInvertColors(bool invert) {
Push<SetInvertColorsOp>(0, invert);
}
void DisplayListBuilder::setCaps(SkPaint::Cap cap) {
Push<SetCapsOp>(0, cap);
}
void DisplayListBuilder::setJoins(SkPaint::Join join) {
Push<SetJoinsOp>(0, join);
}
void DisplayListBuilder::setDrawStyle(SkPaint::Style style) {
Push<SetDrawStyleOp>(0, style);
}
void DisplayListBuilder::setStrokeWidth(SkScalar width) {
Push<SetStrokeWidthOp>(0, width);
}
void DisplayListBuilder::setMiterLimit(SkScalar limit) {
Push<SetMiterLimitOp>(0, limit);
}
void DisplayListBuilder::setColor(SkColor color) {
Push<SetColorOp>(0, color);
}
void DisplayListBuilder::setBlendMode(SkBlendMode mode) {
Push<SetBlendModeOp>(0, mode);
}
void DisplayListBuilder::setBlender(sk_sp<SkBlender> blender) {
blender //
? Push<SetBlenderOp>(0, std::move(blender))
: Push<ClearBlenderOp>(0);
}
void DisplayListBuilder::setShader(sk_sp<SkShader> shader) {
shader //
? Push<SetShaderOp>(0, std::move(shader))
: Push<ClearShaderOp>(0);
}
void DisplayListBuilder::setImageFilter(sk_sp<SkImageFilter> filter) {
filter //
? Push<SetImageFilterOp>(0, std::move(filter))
: Push<ClearImageFilterOp>(0);
}
void DisplayListBuilder::setColorFilter(sk_sp<SkColorFilter> filter) {
filter //
? Push<SetColorFilterOp>(0, std::move(filter))
: Push<ClearColorFilterOp>(0);
}
void DisplayListBuilder::setPathEffect(sk_sp<SkPathEffect> effect) {
effect //
? Push<SetPathEffectOp>(0, std::move(effect))
: Push<ClearPathEffectOp>(0);
}
void DisplayListBuilder::setMaskFilter(sk_sp<SkMaskFilter> filter) {
Push<SetMaskFilterOp>(0, std::move(filter));
}
void DisplayListBuilder::setMaskBlurFilter(SkBlurStyle style, SkScalar sigma) {
switch (style) {
case kNormal_SkBlurStyle:
Push<SetMaskBlurFilterNormalOp>(0, sigma);
break;
case kSolid_SkBlurStyle:
Push<SetMaskBlurFilterSolidOp>(0, sigma);
break;
case kOuter_SkBlurStyle:
Push<SetMaskBlurFilterOuterOp>(0, sigma);
break;
case kInner_SkBlurStyle:
Push<SetMaskBlurFilterInnerOp>(0, sigma);
break;
}
}
void DisplayListBuilder::save() {
save_level_++;
Push<SaveOp>(0);
}
void DisplayListBuilder::restore() {
if (save_level_ > 0) {
Push<RestoreOp>(0);
save_level_--;
}
}
void DisplayListBuilder::saveLayer(const SkRect* bounds, bool with_paint) {
save_level_++;
bounds //
? Push<SaveLayerBoundsOp>(0, *bounds, with_paint)
: Push<SaveLayerOp>(0, with_paint);
}
void DisplayListBuilder::translate(SkScalar tx, SkScalar ty) {
Push<TranslateOp>(0, tx, ty);
}
void DisplayListBuilder::scale(SkScalar sx, SkScalar sy) {
Push<ScaleOp>(0, sx, sy);
}
void DisplayListBuilder::rotate(SkScalar degrees) {
Push<RotateOp>(0, degrees);
}
void DisplayListBuilder::skew(SkScalar sx, SkScalar sy) {
Push<SkewOp>(0, sx, sy);
}
void DisplayListBuilder::transform2x3(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt) {
Push<Transform2x3Op>(0, mxx, mxy, mxt, myx, myy, myt);
}
void DisplayListBuilder::transform3x3(SkScalar mxx,
SkScalar mxy,
SkScalar mxt,
SkScalar myx,
SkScalar myy,
SkScalar myt,
SkScalar px,
SkScalar py,
SkScalar pt) {
Push<Transform3x3Op>(0, mxx, mxy, mxt, myx, myy, myt, px, py, pt);
}
void DisplayListBuilder::clipRect(const SkRect& rect,
bool is_aa,
SkClipOp clip_op) {
clip_op == SkClipOp::kIntersect //
? Push<ClipIntersectRectOp>(0, rect, is_aa)
: Push<ClipDifferenceRectOp>(0, rect, is_aa);
}
void DisplayListBuilder::clipRRect(const SkRRect& rrect,
bool is_aa,
SkClipOp clip_op) {
if (rrect.isRect()) {
clipRect(rrect.rect(), is_aa, clip_op);
} else {
clip_op == SkClipOp::kIntersect //
? Push<ClipIntersectRRectOp>(0, rrect, is_aa)
: Push<ClipDifferenceRRectOp>(0, rrect, is_aa);
}
}
void DisplayListBuilder::clipPath(const SkPath& path,
bool is_aa,
SkClipOp clip_op) {
if (!path.isInverseFillType()) {
SkRect rect;
if (path.isRect(&rect)) {
this->clipRect(rect, is_aa, clip_op);
return;
}
SkRRect rrect;
if (path.isOval(&rect)) {
rrect.setOval(rect);
this->clipRRect(rrect, is_aa, clip_op);
return;
}
if (path.isRRect(&rrect)) {
this->clipRRect(rrect, is_aa, clip_op);
return;
}
}
clip_op == SkClipOp::kIntersect //
? Push<ClipIntersectPathOp>(0, path, is_aa)
: Push<ClipDifferencePathOp>(0, path, is_aa);
}
void DisplayListBuilder::drawPaint() {
Push<DrawPaintOp>(0);
}
void DisplayListBuilder::drawColor(SkColor color, SkBlendMode mode) {
Push<DrawColorOp>(0, color, mode);
}
void DisplayListBuilder::drawLine(const SkPoint& p0, const SkPoint& p1) {
Push<DrawLineOp>(0, p0, p1);
}
void DisplayListBuilder::drawRect(const SkRect& rect) {
Push<DrawRectOp>(0, rect);
}
void DisplayListBuilder::drawOval(const SkRect& bounds) {
Push<DrawOvalOp>(0, bounds);
}
void DisplayListBuilder::drawCircle(const SkPoint& center, SkScalar radius) {
Push<DrawCircleOp>(0, 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, rrect);
}
}
void DisplayListBuilder::drawDRRect(const SkRRect& outer,
const SkRRect& inner) {
Push<DrawDRRectOp>(0, outer, inner);
}
void DisplayListBuilder::drawPath(const SkPath& path) {
Push<DrawPathOp>(0, path);
}
void DisplayListBuilder::drawArc(const SkRect& bounds,
SkScalar start,
SkScalar sweep,
bool useCenter) {
Push<DrawArcOp>(0, bounds, start, sweep, useCenter);
}
void DisplayListBuilder::drawPoints(SkCanvas::PointMode mode,
uint32_t count,
const SkPoint pts[]) {
void* data_ptr;
FML_DCHECK(count < MaxDrawPointsCount);
int bytes = count * sizeof(SkPoint);
switch (mode) {
case SkCanvas::PointMode::kPoints_PointMode:
data_ptr = Push<DrawPointsOp>(bytes, count);
break;
case SkCanvas::PointMode::kLines_PointMode:
data_ptr = Push<DrawLinesOp>(bytes, count);
break;
case SkCanvas::PointMode::kPolygon_PointMode:
data_ptr = Push<DrawPolygonOp>(bytes, 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, std::move(vertices), mode);
}
void DisplayListBuilder::drawImage(const sk_sp<SkImage> image,
const SkPoint point,
const SkSamplingOptions& sampling) {
Push<DrawImageOp>(0, std::move(image), point, sampling);
}
void DisplayListBuilder::drawImageRect(const sk_sp<SkImage> image,
const SkRect& src,
const SkRect& dst,
const SkSamplingOptions& sampling,
SkCanvas::SrcRectConstraint constraint) {
constraint == SkCanvas::kFast_SrcRectConstraint //
? Push<DrawImageRectFastOp>(0, std::move(image), src, dst, sampling)
: Push<DrawImageRectStrictOp>(0, std::move(image), src, dst, sampling);
}
void DisplayListBuilder::drawImageNine(const sk_sp<SkImage> image,
const SkIRect& center,
const SkRect& dst,
SkFilterMode filter) {
Push<DrawImageNineOp>(0, std::move(image), center, dst, filter);
}
void DisplayListBuilder::drawImageLattice(const sk_sp<SkImage> image,
const SkCanvas::Lattice& lattice,
const SkRect& dst,
SkFilterMode filter,
bool with_paint) {
int xDivCount = lattice.fXCount;
int yDivCount = lattice.fYCount;
FML_DCHECK((lattice.fRectTypes == nullptr) || (lattice.fColors != nullptr));
int cellCount = lattice.fRectTypes && lattice.fColors
? (xDivCount + 1) * (yDivCount + 1)
: 0;
size_t bytes =
(xDivCount + yDivCount) * sizeof(int) +
cellCount * (sizeof(SkColor) + sizeof(SkCanvas::Lattice::RectType));
SkIRect src = lattice.fBounds ? *lattice.fBounds : image->bounds();
void* pod = this->Push<DrawImageLatticeOp>(bytes, std::move(image), xDivCount,
yDivCount, cellCount, src, dst,
filter, with_paint);
CopyV(pod, lattice.fXDivs, xDivCount, lattice.fYDivs, yDivCount,
lattice.fColors, cellCount, lattice.fRectTypes, cellCount);
}
void DisplayListBuilder::drawAtlas(const sk_sp<SkImage> atlas,
const SkRSXform xform[],
const SkRect tex[],
const SkColor colors[],
int count,
SkBlendMode mode,
const SkSamplingOptions& sampling,
const SkRect* cullRect) {
int bytes = count * (sizeof(SkRSXform) + sizeof(SkRect));
void* data_ptr;
if (colors) {
bytes += count * sizeof(SkColor);
if (cullRect) {
data_ptr = Push<DrawAtlasColoredCulledOp>(bytes, std::move(atlas), count,
mode, sampling, *cullRect);
} else {
data_ptr = Push<DrawAtlasColoredOp>(bytes, std::move(atlas), count, mode,
sampling);
}
CopyV(data_ptr, xform, count, tex, count, colors, count);
} else {
if (cullRect) {
data_ptr = Push<DrawAtlasCulledOp>(bytes, std::move(atlas), count, mode,
sampling, *cullRect);
} else {
data_ptr =
Push<DrawAtlasOp>(bytes, std::move(atlas), count, mode, sampling);
}
CopyV(data_ptr, xform, count, tex, count);
}
}
void DisplayListBuilder::drawPicture(const sk_sp<SkPicture> picture,
const SkMatrix* matrix,
bool with_layer) {
matrix //
? Push<DrawSkPictureMatrixOp>(0, std::move(picture), *matrix, with_layer)
: Push<DrawSkPictureOp>(0, std::move(picture), with_layer);
}
void DisplayListBuilder::drawDisplayList(
const sk_sp<DisplayList> display_list) {
Push<DrawDisplayListOp>(0, std::move(display_list));
}
void DisplayListBuilder::drawTextBlob(const sk_sp<SkTextBlob> blob,
SkScalar x,
SkScalar y) {
Push<DrawTextBlobOp>(0, std::move(blob), x, y);
}
void DisplayListBuilder::drawShadow(const SkPath& path,
const SkColor color,
const SkScalar elevation,
bool occludes,
SkScalar dpr) {
occludes //
? Push<DrawShadowOccludesOp>(0, path, color, elevation, dpr)
: Push<DrawShadowOp>(0, path, color, elevation, dpr);
}
} // namespace flutter