Opacity peephole optimization (flutter/engine#29775)

This commit is contained in:
Jim Graham 2021-12-14 16:19:09 -08:00 committed by GitHub
parent d4bcc2e2bc
commit 46a8def326
42 changed files with 854 additions and 173 deletions

View File

@ -26,7 +26,8 @@ class Texture {
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) = 0;
const SkSamplingOptions& sampling,
const SkPaint* paint = nullptr) = 0;
// Called from raster thread.
virtual void OnGrContextCreated() = 0;

View File

@ -978,8 +978,8 @@ static bool CompareOps(uint8_t* ptrA,
return true;
}
void DisplayList::RenderTo(SkCanvas* canvas) const {
DisplayListCanvasDispatcher dispatcher(canvas);
void DisplayList::RenderTo(SkCanvas* canvas, SkScalar opacity) const {
DisplayListCanvasDispatcher dispatcher(canvas, opacity);
Dispatch(dispatcher);
}
@ -995,19 +995,31 @@ bool DisplayList::Equals(const DisplayList& other) const {
return CompareOps(ptr, ptr + byte_count_, o_ptr, o_ptr + other.byte_count_);
}
DisplayList::DisplayList()
: byte_count_(0),
op_count_(0),
nested_byte_count_(0),
nested_op_count_(0),
unique_id_(0),
bounds_({0, 0, 0, 0}),
bounds_cull_({0, 0, 0, 0}),
can_apply_group_opacity_(true) {}
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)
const SkRect& cull_rect,
bool can_apply_group_opacity)
: 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) {
bounds_cull_(cull_rect),
can_apply_group_opacity_(can_apply_group_opacity) {
static std::atomic<uint32_t> nextID{1};
do {
unique_id_ = nextID.fetch_add(+1, std::memory_order_relaxed);
@ -1057,7 +1069,7 @@ void* DisplayListBuilder::Push(size_t pod, int op_inc, Args&&... args) {
}
sk_sp<DisplayList> DisplayListBuilder::Build() {
while (save_level_ > 0) {
while (layer_stack_.size() > 1) {
restore();
}
size_t bytes = used_;
@ -1067,13 +1079,17 @@ sk_sp<DisplayList> DisplayListBuilder::Build() {
used_ = allocated_ = op_count_ = 0;
nested_bytes_ = nested_op_count_ = 0;
storage_.realloc(bytes);
bool compatible = layer_stack_.back().is_group_opacity_compatible();
return sk_sp<DisplayList>(new DisplayList(storage_.release(), bytes, count,
nested_bytes, nested_count,
cull_rect_));
cull_rect_, compatible));
}
DisplayListBuilder::DisplayListBuilder(const SkRect& cull_rect)
: cull_rect_(cull_rect) {}
: cull_rect_(cull_rect) {
layer_stack_.emplace_back();
current_layer_ = &layer_stack_.back();
}
DisplayListBuilder::~DisplayListBuilder() {
uint8_t* ptr = storage_.get();
@ -1090,6 +1106,7 @@ void DisplayListBuilder::onSetDither(bool dither) {
}
void DisplayListBuilder::onSetInvertColors(bool invert) {
Push<SetInvertColorsOp>(0, 0, current_invert_colors_ = invert);
UpdateCurrentOpacityCompatibility();
}
void DisplayListBuilder::onSetStrokeCap(SkPaint::Cap cap) {
Push<SetStrokeCapOp>(0, 0, current_stroke_cap_ = cap);
@ -1112,6 +1129,7 @@ void DisplayListBuilder::onSetColor(SkColor color) {
void DisplayListBuilder::onSetBlendMode(SkBlendMode mode) {
current_blender_ = nullptr;
Push<SetBlendModeOp>(0, 0, current_blend_mode_ = mode);
UpdateCurrentOpacityCompatibility();
}
void DisplayListBuilder::onSetBlender(sk_sp<SkBlender> blender) {
// setBlender(nullptr) should be redirected to setBlendMode(SrcOver)
@ -1126,6 +1144,7 @@ void DisplayListBuilder::onSetBlender(sk_sp<SkBlender> blender) {
(current_blender_ = blender) //
? Push<SetBlenderOp>(0, 0, std::move(blender))
: Push<ClearBlenderOp>(0, 0);
UpdateCurrentOpacityCompatibility();
}
}
void DisplayListBuilder::onSetShader(sk_sp<SkShader> shader) {
@ -1142,6 +1161,7 @@ void DisplayListBuilder::onSetColorFilter(sk_sp<SkColorFilter> filter) {
(current_color_filter_ = filter) //
? Push<SetColorFilterOp>(0, 0, std::move(filter))
: Push<ClearColorFilterOp>(0, 0);
UpdateCurrentOpacityCompatibility();
}
void DisplayListBuilder::onSetPathEffect(sk_sp<SkPathEffect> effect) {
(current_path_effect_ = effect) //
@ -1228,21 +1248,37 @@ void DisplayListBuilder::setAttributesFromPaint(
}
void DisplayListBuilder::save() {
save_level_++;
Push<SaveOp>(0, 1);
layer_stack_.emplace_back();
current_layer_ = &layer_stack_.back();
}
void DisplayListBuilder::restore() {
if (save_level_ > 0) {
if (layer_stack_.size() > 1) {
// Grab the current layer info before we push the restore
// on the stack.
LayerInfo layer_info = layer_stack_.back();
layer_stack_.pop_back();
current_layer_ = &layer_stack_.back();
Push<RestoreOp>(0, 1);
save_level_--;
if (!layer_info.has_layer) {
// For regular save() ops there was no protecting layer so we have to
// accumulate the values into the enclosing layer.
if (layer_info.cannot_inherit_opacity) {
current_layer_->mark_incompatible();
} else if (layer_info.has_compatible_op) {
current_layer_->add_compatible_op();
}
}
}
}
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);
CheckLayerOpacityCompatibility(restore_with_paint);
layer_stack_.emplace_back(true);
current_layer_ = &layer_stack_.back();
}
void DisplayListBuilder::translate(SkScalar tx, SkScalar ty) {
@ -1356,21 +1392,27 @@ void DisplayListBuilder::clipPath(const SkPath& path,
void DisplayListBuilder::drawPaint() {
Push<DrawPaintOp>(0, 1);
CheckLayerOpacityCompatibility();
}
void DisplayListBuilder::drawColor(SkColor color, SkBlendMode mode) {
Push<DrawColorOp>(0, 1, color, mode);
CheckLayerOpacityCompatibility(mode);
}
void DisplayListBuilder::drawLine(const SkPoint& p0, const SkPoint& p1) {
Push<DrawLineOp>(0, 1, p0, p1);
CheckLayerOpacityCompatibility();
}
void DisplayListBuilder::drawRect(const SkRect& rect) {
Push<DrawRectOp>(0, 1, rect);
CheckLayerOpacityCompatibility();
}
void DisplayListBuilder::drawOval(const SkRect& bounds) {
Push<DrawOvalOp>(0, 1, bounds);
CheckLayerOpacityCompatibility();
}
void DisplayListBuilder::drawCircle(const SkPoint& center, SkScalar radius) {
Push<DrawCircleOp>(0, 1, center, radius);
CheckLayerOpacityCompatibility();
}
void DisplayListBuilder::drawRRect(const SkRRect& rrect) {
if (rrect.isRect()) {
@ -1379,14 +1421,17 @@ void DisplayListBuilder::drawRRect(const SkRRect& rrect) {
drawOval(rrect.rect());
} else {
Push<DrawRRectOp>(0, 1, rrect);
CheckLayerOpacityCompatibility();
}
}
void DisplayListBuilder::drawDRRect(const SkRRect& outer,
const SkRRect& inner) {
Push<DrawDRRectOp>(0, 1, outer, inner);
CheckLayerOpacityCompatibility();
}
void DisplayListBuilder::drawPath(const SkPath& path) {
Push<DrawPathOp>(0, 1, path);
CheckLayerOpacityHairlineCompatibility();
}
void DisplayListBuilder::drawArc(const SkRect& bounds,
@ -1394,6 +1439,11 @@ void DisplayListBuilder::drawArc(const SkRect& bounds,
SkScalar sweep,
bool useCenter) {
Push<DrawArcOp>(0, 1, bounds, start, sweep, useCenter);
if (useCenter) {
CheckLayerOpacityHairlineCompatibility();
} else {
CheckLayerOpacityCompatibility();
}
}
void DisplayListBuilder::drawPoints(SkCanvas::PointMode mode,
uint32_t count,
@ -1416,10 +1466,19 @@ void DisplayListBuilder::drawPoints(SkCanvas::PointMode mode,
return;
}
CopyV(data_ptr, pts, count);
// drawPoints treats every point or line (or segment of a polygon)
// as a completely separate operation meaning we cannot ensure
// distribution of group opacity without analyzing the mode and the
// bounds of every sub-primitive.
// See: https://fiddle.skia.org/c/228459001d2de8db117ce25ef5cedb0c
UpdateLayerOpacityCompatibility(false);
}
void DisplayListBuilder::drawVertices(const sk_sp<SkVertices> vertices,
SkBlendMode mode) {
Push<DrawVerticesOp>(0, 1, std::move(vertices), mode);
// DrawVertices applies its colors to the paint so we have no way
// of controlling opacity using the current paint attributes.
UpdateLayerOpacityCompatibility(false);
}
void DisplayListBuilder::drawImage(const sk_sp<SkImage> image,
@ -1429,6 +1488,7 @@ void DisplayListBuilder::drawImage(const sk_sp<SkImage> image,
render_with_attributes
? Push<DrawImageWithAttrOp>(0, 1, std::move(image), point, sampling)
: Push<DrawImageOp>(0, 1, std::move(image), point, sampling);
CheckLayerOpacityCompatibility(render_with_attributes);
}
void DisplayListBuilder::drawImageRect(const sk_sp<SkImage> image,
const SkRect& src,
@ -1438,6 +1498,7 @@ void DisplayListBuilder::drawImageRect(const sk_sp<SkImage> image,
SkCanvas::SrcRectConstraint constraint) {
Push<DrawImageRectOp>(0, 1, std::move(image), src, dst, sampling,
render_with_attributes, constraint);
CheckLayerOpacityCompatibility(render_with_attributes);
}
void DisplayListBuilder::drawImageNine(const sk_sp<SkImage> image,
const SkIRect& center,
@ -1448,6 +1509,7 @@ void DisplayListBuilder::drawImageNine(const sk_sp<SkImage> image,
? Push<DrawImageNineWithAttrOp>(0, 1, std::move(image), center, dst,
filter)
: Push<DrawImageNineOp>(0, 1, std::move(image), center, dst, filter);
CheckLayerOpacityCompatibility(render_with_attributes);
}
void DisplayListBuilder::drawImageLattice(const sk_sp<SkImage> image,
const SkCanvas::Lattice& lattice,
@ -1469,6 +1531,7 @@ void DisplayListBuilder::drawImageLattice(const sk_sp<SkImage> image,
filter, render_with_attributes);
CopyV(pod, lattice.fXDivs, xDivCount, lattice.fYDivs, yDivCount,
lattice.fColors, cellCount, lattice.fRectTypes, cellCount);
CheckLayerOpacityCompatibility(render_with_attributes);
}
void DisplayListBuilder::drawAtlas(const sk_sp<SkImage> atlas,
const SkRSXform xform[],
@ -1503,6 +1566,10 @@ void DisplayListBuilder::drawAtlas(const sk_sp<SkImage> atlas,
}
CopyV(data_ptr, xform, count, tex, count);
}
// drawAtlas treats each image as a separate operation so we cannot rely
// on it to distribute the opacity without overlap without checking all
// of the transforms and texture rectangles.
UpdateLayerOpacityCompatibility(false);
}
void DisplayListBuilder::drawPicture(const sk_sp<SkPicture> picture,
@ -1520,6 +1587,7 @@ void DisplayListBuilder::drawPicture(const sk_sp<SkPicture> picture,
// This behavior is identical to the way SkPicture computes nested op counts.
nested_op_count_ += picture->approximateOpCount(true) - 1;
nested_bytes_ += picture->approximateBytesUsed();
CheckLayerOpacityCompatibility(render_with_attributes);
}
void DisplayListBuilder::drawDisplayList(
const sk_sp<DisplayList> display_list) {
@ -1532,11 +1600,13 @@ void DisplayListBuilder::drawDisplayList(
// 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);
UpdateLayerOpacityCompatibility(display_list->can_apply_group_opacity());
}
void DisplayListBuilder::drawTextBlob(const sk_sp<SkTextBlob> blob,
SkScalar x,
SkScalar y) {
Push<DrawTextBlobOp>(0, 1, std::move(blob), x, y);
CheckLayerOpacityCompatibility();
}
void DisplayListBuilder::drawShadow(const SkPath& path,
const SkColor color,
@ -1546,6 +1616,7 @@ void DisplayListBuilder::drawShadow(const SkPath& path,
transparent_occluder //
? Push<DrawShadowTransparentOccluderOp>(0, 1, path, color, elevation, dpr)
: Push<DrawShadowOp>(0, 1, path, color, elevation, dpr);
UpdateLayerOpacityCompatibility(false);
}
// clang-format off

View File

@ -171,15 +171,7 @@ class DisplayList : public SkRefCnt {
static const SkSamplingOptions MipmapSampling;
static const SkSamplingOptions CubicSampling;
DisplayList()
: byte_count_(0),
op_count_(0),
nested_byte_count_(0),
nested_op_count_(0),
unique_id_(0),
bounds_({0, 0, 0, 0}),
bounds_cull_({0, 0, 0, 0}) {}
DisplayList();
~DisplayList();
void Dispatch(Dispatcher& ctx) const {
@ -187,7 +179,7 @@ class DisplayList : public SkRefCnt {
Dispatch(ctx, ptr, ptr + byte_count_);
}
void RenderTo(SkCanvas* canvas) const;
void RenderTo(SkCanvas* canvas, SkScalar opacity = SK_Scalar1) const;
// SkPicture always includes nested bytes, but nested ops are
// only included if requested. The defaults used here for these
@ -212,13 +204,16 @@ class DisplayList : public SkRefCnt {
bool Equals(const DisplayList& other) const;
bool can_apply_group_opacity() { return can_apply_group_opacity_; }
private:
DisplayList(uint8_t* ptr,
size_t byte_count,
int op_count,
size_t nested_byte_count,
int nested_op_count,
const SkRect& cull_rect);
const SkRect& cull_rect,
bool can_apply_group_opacity);
std::unique_ptr<uint8_t, SkFunctionWrapper<void(void*), sk_free>> storage_;
size_t byte_count_;
@ -233,6 +228,8 @@ class DisplayList : public SkRefCnt {
// Only used for drawPaint() and drawColor()
SkRect bounds_cull_;
bool can_apply_group_opacity_;
void ComputeBounds();
void Dispatch(Dispatcher& ctx, uint8_t* ptr, uint8_t* end) const;
@ -548,6 +545,13 @@ class DisplayListFlags {
static constexpr int kUsesMaskFilter_ = 1 << 18;
static constexpr int kUsesImageFilter_ = 1 << 19;
// Some ops have an optional paint argument. If the version
// stored in the DisplayList ignores the paint, but there
// is an option to render the same op with a paint then
// both of the following flags are set to indicate that
// a default paint object can be constructed when rendering
// the op to carry information imposed from outside the
// DisplayList (for example, the opacity override).
static constexpr int kIgnoresPaint_ = 1 << 30;
// clang-format on
@ -746,7 +750,9 @@ class DisplayListOpFlags : DisplayListFlags {
// If there is some code that already renders to an SkCanvas object,
// those rendering commands can be captured into a DisplayList using
// the DisplayListCanvasRecorder class.
class DisplayListBuilder final : public virtual Dispatcher, public SkRefCnt {
class DisplayListBuilder final : public virtual Dispatcher,
public SkRefCnt,
DisplayListOpFlags {
public:
explicit DisplayListBuilder(const SkRect& cull_rect = kMaxCullRect_);
~DisplayListBuilder();
@ -880,7 +886,7 @@ class DisplayListBuilder final : public virtual Dispatcher, public SkRefCnt {
void save() override;
void saveLayer(const SkRect* bounds, bool restore_with_paint) override;
void restore() override;
int getSaveCount() { return save_level_ + 1; }
int getSaveCount() { return layer_stack_.size(); }
void translate(SkScalar tx, SkScalar ty) override;
void scale(SkScalar sx, SkScalar sy) override;
@ -977,7 +983,6 @@ class DisplayListBuilder final : public virtual Dispatcher, public SkRefCnt {
size_t used_ = 0;
size_t allocated_ = 0;
int op_count_ = 0;
int save_level_ = 0;
// bytes and ops from |drawPicture| and |drawDisplayList|
size_t nested_bytes_ = 0;
@ -996,6 +1001,94 @@ class DisplayListBuilder final : public virtual Dispatcher, public SkRefCnt {
return SkScalarIsFinite(sigma) && sigma > 0.0;
}
struct LayerInfo {
LayerInfo(bool has_layer = false)
: has_layer(has_layer),
cannot_inherit_opacity(false),
has_compatible_op(false) {}
bool has_layer;
bool cannot_inherit_opacity;
bool has_compatible_op;
bool is_group_opacity_compatible() const { return !cannot_inherit_opacity; }
void mark_incompatible() { cannot_inherit_opacity = true; }
// For now this only allows a single compatible op to mark the
// layer as being compatible with group opacity. If we start
// computing bounds of ops in the Builder methods then we
// can upgrade this to checking for overlapping ops.
// See https://github.com/flutter/flutter/issues/93899
void add_compatible_op() {
if (!cannot_inherit_opacity) {
if (has_compatible_op) {
cannot_inherit_opacity = true;
} else {
has_compatible_op = true;
}
}
}
};
std::vector<LayerInfo> layer_stack_;
LayerInfo* current_layer_;
// This flag indicates whether or not the current rendering attributes
// are compatible with rendering ops applying an inherited opacity.
bool current_opacity_compatibility_ = true;
// Returns the compatibility of a given blend mode for applying an
// inherited opacity value to modulate the visibility of the op.
// For now we only accept SrcOver blend modes but this could be expanded
// in the future to include other (rarely used) modes that also modulate
// the opacity of a rendering operation at the cost of a switch statement
// or lookup table.
static bool IsOpacityCompatible(SkBlendMode mode) {
return (mode == SkBlendMode::kSrcOver);
}
void UpdateCurrentOpacityCompatibility() {
current_opacity_compatibility_ = //
current_color_filter_ == nullptr && //
!current_invert_colors_ && //
current_blender_ == nullptr && //
IsOpacityCompatible(current_blend_mode_);
}
// Update the opacity compatibility flags of the current layer for an op
// that has determined its compatibility as indicated by |compatible|.
void UpdateLayerOpacityCompatibility(bool compatible) {
if (compatible) {
current_layer_->add_compatible_op();
} else {
current_layer_->mark_incompatible();
}
}
// Check for opacity compatibility for an op that may or may not use the
// current rendering attributes as indicated by |uses_blend_attribute|.
// If the flag is false then the rendering op will be able to substitute
// a default Paint object with the opacity applied using the default SrcOver
// blend mode which is always compatible with applying an inherited opacity.
void CheckLayerOpacityCompatibility(bool uses_blend_attribute = true) {
UpdateLayerOpacityCompatibility(!uses_blend_attribute ||
current_opacity_compatibility_);
}
void CheckLayerOpacityHairlineCompatibility() {
UpdateLayerOpacityCompatibility(
current_opacity_compatibility_ &&
(current_style_ == SkPaint::kFill_Style || current_stroke_width_ > 0));
}
// Check for opacity compatibility for an op that ignores the current
// attributes and uses the indicated blend |mode| to render to the layer.
// This is only used by |drawColor| currently.
void CheckLayerOpacityCompatibility(SkBlendMode mode) {
UpdateLayerOpacityCompatibility(IsOpacityCompatible(mode));
}
void onSetAntiAlias(bool aa);
void onSetDither(bool dither);
void onSetInvertColors(bool invert);

View File

@ -11,16 +11,32 @@
namespace flutter {
const SkPaint* DisplayListCanvasDispatcher::safe_paint(bool use_attributes) {
if (use_attributes) {
// The accumulated SkPaint object will already have incorporated
// any attribute overrides.
return &paint();
} else if (has_opacity()) {
temp_paint_.setAlphaf(opacity());
return &temp_paint_;
} else {
return nullptr;
}
}
void DisplayListCanvasDispatcher::save() {
canvas_->save();
save_opacity(false);
}
void DisplayListCanvasDispatcher::restore() {
canvas_->restore();
restore_opacity();
}
void DisplayListCanvasDispatcher::saveLayer(const SkRect* bounds,
bool restore_with_paint) {
TRACE_EVENT0("flutter", "Canvas::saveLayer");
canvas_->saveLayer(bounds, restore_with_paint ? &paint() : nullptr);
canvas_->saveLayer(bounds, safe_paint(restore_with_paint));
save_opacity(true);
}
void DisplayListCanvasDispatcher::translate(SkScalar tx, SkScalar ty) {
@ -87,7 +103,11 @@ void DisplayListCanvasDispatcher::drawPaint() {
canvas_->drawPaint(sk_paint);
}
void DisplayListCanvasDispatcher::drawColor(SkColor color, SkBlendMode mode) {
canvas_->drawColor(color, mode);
// SkCanvas::drawColor(SkColor) does the following conversion anyway
// We do it here manually to increase precision on applying opacity
SkColor4f color4f = SkColor4f::FromColor(color);
color4f.fA *= opacity();
canvas_->drawColor(color4f, mode);
}
void DisplayListCanvasDispatcher::drawLine(const SkPoint& p0,
const SkPoint& p1) {
@ -133,7 +153,7 @@ void DisplayListCanvasDispatcher::drawImage(const sk_sp<SkImage> image,
const SkSamplingOptions& sampling,
bool render_with_attributes) {
canvas_->drawImage(image, point.fX, point.fY, sampling,
render_with_attributes ? &paint() : nullptr);
safe_paint(render_with_attributes));
}
void DisplayListCanvasDispatcher::drawImageRect(
const sk_sp<SkImage> image,
@ -143,8 +163,7 @@ void DisplayListCanvasDispatcher::drawImageRect(
bool render_with_attributes,
SkCanvas::SrcRectConstraint constraint) {
canvas_->drawImageRect(image, src, dst, sampling,
render_with_attributes ? &paint() : nullptr,
constraint);
safe_paint(render_with_attributes), constraint);
}
void DisplayListCanvasDispatcher::drawImageNine(const sk_sp<SkImage> image,
const SkIRect& center,
@ -152,7 +171,7 @@ void DisplayListCanvasDispatcher::drawImageNine(const sk_sp<SkImage> image,
SkFilterMode filter,
bool render_with_attributes) {
canvas_->drawImageNine(image.get(), center, dst, filter,
render_with_attributes ? &paint() : nullptr);
safe_paint(render_with_attributes));
}
void DisplayListCanvasDispatcher::drawImageLattice(
const sk_sp<SkImage> image,
@ -161,7 +180,7 @@ void DisplayListCanvasDispatcher::drawImageLattice(
SkFilterMode filter,
bool render_with_attributes) {
canvas_->drawImageLattice(image.get(), lattice, dst, filter,
render_with_attributes ? &paint() : nullptr);
safe_paint(render_with_attributes));
}
void DisplayListCanvasDispatcher::drawAtlas(const sk_sp<SkImage> atlas,
const SkRSXform xform[],
@ -173,15 +192,16 @@ void DisplayListCanvasDispatcher::drawAtlas(const sk_sp<SkImage> atlas,
const SkRect* cullRect,
bool render_with_attributes) {
canvas_->drawAtlas(atlas.get(), xform, tex, colors, count, mode, sampling,
cullRect, render_with_attributes ? &paint() : nullptr);
cullRect, safe_paint(render_with_attributes));
}
void DisplayListCanvasDispatcher::drawPicture(const sk_sp<SkPicture> picture,
const SkMatrix* matrix,
bool render_with_attributes) {
if (render_with_attributes) {
const SkPaint* paint = safe_paint(render_with_attributes);
if (paint) {
// drawPicture does an implicit saveLayer if an SkPaint is supplied.
TRACE_EVENT0("flutter", "Canvas::saveLayer");
canvas_->drawPicture(picture, matrix, &paint());
canvas_->drawPicture(picture, matrix, paint);
} else {
canvas_->drawPicture(picture, matrix, nullptr);
}
@ -189,10 +209,7 @@ void DisplayListCanvasDispatcher::drawPicture(const sk_sp<SkPicture> picture,
void DisplayListCanvasDispatcher::drawDisplayList(
const sk_sp<DisplayList> display_list) {
int save_count = canvas_->save();
{
DisplayListCanvasDispatcher dispatcher(canvas_);
display_list->Dispatch(dispatcher);
}
display_list->RenderTo(canvas_, opacity());
canvas_->restoreToCount(save_count);
}
void DisplayListCanvasDispatcher::drawTextBlob(const sk_sp<SkTextBlob> blob,

View File

@ -27,7 +27,11 @@ namespace flutter {
class DisplayListCanvasDispatcher : public virtual Dispatcher,
public SkPaintDispatchHelper {
public:
explicit DisplayListCanvasDispatcher(SkCanvas* canvas) : canvas_(canvas) {}
explicit DisplayListCanvasDispatcher(SkCanvas* canvas,
SkScalar opacity = SK_Scalar1)
: SkPaintDispatchHelper(opacity), canvas_(canvas) {}
const SkPaint* safe_paint(bool use_attributes);
void save() override;
void restore() override;
@ -115,6 +119,7 @@ class DisplayListCanvasDispatcher : public virtual Dispatcher,
private:
SkCanvas* canvas_;
SkPaint temp_paint_;
};
// Receives all methods on SkCanvas and sends them to a DisplayListBuilder

View File

@ -244,13 +244,16 @@ static void EmptyDlRenderer(DisplayListBuilder&) {}
class RenderSurface {
public:
explicit RenderSurface(sk_sp<SkSurface> surface) : surface_(surface) {}
explicit RenderSurface(sk_sp<SkSurface> surface) : surface_(surface) {
EXPECT_EQ(canvas()->save(), 1);
}
~RenderSurface() { sk_free(addr_); }
SkCanvas* canvas() { return surface_->getCanvas(); }
const SkPixmap* pixmap() {
if (!pixmap_.addr()) {
canvas()->restoreToCount(1);
SkImageInfo info = surface_->imageInfo();
if (info.colorType() != kN32_SkColorType ||
!surface_->peekPixels(&pixmap_)) {
@ -280,13 +283,14 @@ class RenderEnvironment {
return RenderEnvironment(SkImageInfo::MakeN32Premul(1, 1));
}
RenderSurface MakeSurface(const SkColor bg = SK_ColorTRANSPARENT,
int width = TestWidth,
int height = TestHeight) const {
std::unique_ptr<RenderSurface> MakeSurface(
const SkColor bg = SK_ColorTRANSPARENT,
int width = TestWidth,
int height = TestHeight) const {
sk_sp<SkSurface> surface =
SkSurface::MakeRaster(info_.makeWH(width, height));
surface->getCanvas()->clear(bg);
return RenderSurface(surface);
return std::make_unique<RenderSurface>(surface);
}
void init_ref(CvRenderer& cv_renderer, SkColor bg = SK_ColorTRANSPARENT) {
@ -301,25 +305,27 @@ class RenderEnvironment {
ref_matrix_ = ref_canvas()->getTotalMatrix();
ref_clip_ = ref_canvas()->getDeviceClipBounds();
cv_renderer(ref_canvas(), ref_paint_);
ref_pixmap_ = ref_surface_.pixmap();
ref_pixmap_ = ref_surface_->pixmap();
}
SkCanvas* ref_canvas() { return ref_surface_.canvas(); }
const SkImageInfo& info() const { return info_; }
SkCanvas* ref_canvas() { return ref_surface_->canvas(); }
const SkPaint& ref_paint() const { return ref_paint_; }
const SkMatrix& ref_matrix() const { return ref_matrix_; }
const SkIRect& ref_clip_bounds() const { return ref_clip_; }
const SkPixmap* ref_pixmap() const { return ref_pixmap_; }
private:
explicit RenderEnvironment(const SkImageInfo& info)
: info_(info), ref_surface_(MakeSurface()) {}
explicit RenderEnvironment(const SkImageInfo& info) : info_(info) {
ref_surface_ = MakeSurface();
}
const SkImageInfo info_;
SkPaint ref_paint_;
SkMatrix ref_matrix_;
SkIRect ref_clip_;
RenderSurface ref_surface_;
std::unique_ptr<RenderSurface> ref_surface_;
const SkPixmap* ref_pixmap_ = nullptr;
};
@ -1280,7 +1286,7 @@ class CanvasCompareTester {
}
static void RenderWithStrokes(const TestParameters& testP,
const RenderEnvironment env,
const RenderEnvironment& env,
const BoundsTolerance& tolerance_in) {
// The test cases were generated with geometry that will try to fill
// out the various miter limits used for testing, but they can be off
@ -1512,8 +1518,16 @@ class CanvasCompareTester {
[=](SkCanvas* c, SkPaint&) { c->skew(0.05, 0.05); },
[=](DisplayListBuilder& b) { b.skew(0.05, 0.05); }));
{
SkMatrix tx = SkMatrix::MakeAll(1.10, 0.10, 5, //
0.05, 1.05, 10, //
// This rather odd transform can cause slight differences in
// computing in-bounds samples depending on which base rendering
// routine Skia uses. Making sure our matrix values are powers
// of 2 reduces, but does not eliminate, these slight differences
// in calculation when we are comparing rendering with an alpha
// to rendering opaque colors in the group opacity tests, for
// example.
SkScalar tweak = 1.0 / 16.0;
SkMatrix tx = SkMatrix::MakeAll(1.0 + tweak, tweak, 5, //
tweak, 1.0 + tweak, 10, //
0, 0, 1);
RenderWith(testP, env, skewed_tolerance,
CaseParameters(
@ -1660,8 +1674,8 @@ class CanvasCompareTester {
// DisplayList mechanisms are not involved in this operation
const std::string info = caseP.info();
const SkColor bg = caseP.bg();
RenderSurface sk_surface = env.MakeSurface(bg);
SkCanvas* sk_canvas = sk_surface.canvas();
std::unique_ptr<RenderSurface> sk_surface = env.MakeSurface(bg);
SkCanvas* sk_canvas = sk_surface->canvas();
SkPaint sk_paint;
caseP.cv_setup()(sk_canvas, sk_paint);
SkMatrix sk_matrix = sk_canvas->getTotalMatrix();
@ -1672,7 +1686,7 @@ class CanvasCompareTester {
caseP.cv_restore()(sk_canvas, sk_paint);
const sk_sp<SkPicture> sk_picture = getSkPicture(testP, caseP);
SkRect sk_bounds = sk_picture->cullRect();
const SkPixmap* sk_pixels = sk_surface.pixmap();
const SkPixmap* sk_pixels = sk_surface->pixmap();
ASSERT_EQ(sk_pixels->width(), TestWidth) << info;
ASSERT_EQ(sk_pixels->height(), TestHeight) << info;
ASSERT_EQ(sk_pixels->info().bytesPerPixel(), 4) << info;
@ -1692,7 +1706,6 @@ class CanvasCompareTester {
// This sequence plays the provided equivalently constructed
// DisplayList onto the SkCanvas of the surface
// DisplayList => direct rendering
RenderSurface dl_surface = env.MakeSurface(bg);
DisplayListBuilder builder(TestBounds);
caseP.render_to(builder, testP);
sk_sp<DisplayList> display_list = builder.Build();
@ -1729,10 +1742,16 @@ class CanvasCompareTester {
<< info;
}
display_list->RenderTo(dl_surface.canvas());
compareToReference(dl_surface.pixmap(), sk_pixels,
std::unique_ptr<RenderSurface> dl_surface = env.MakeSurface(bg);
display_list->RenderTo(dl_surface->canvas());
compareToReference(dl_surface->pixmap(), sk_pixels,
info + " (DisplayList built directly -> surface)",
&dl_bounds, &tolerance, bg);
if (display_list->can_apply_group_opacity()) {
checkGroupOpacity(env, display_list, dl_surface->pixmap(),
info + " with Group Opacity", bg);
}
}
// This test cannot work if the rendering is using shadows until
@ -1741,11 +1760,11 @@ class CanvasCompareTester {
// This sequence renders SkCanvas calls to a DisplayList and then
// plays them back on SkCanvas to SkSurface
// SkCanvas calls => DisplayList => rendering
RenderSurface cv_dl_surface = env.MakeSurface(bg);
std::unique_ptr<RenderSurface> cv_dl_surface = env.MakeSurface(bg);
DisplayListCanvasRecorder dl_recorder(TestBounds);
caseP.render_to(&dl_recorder, testP);
dl_recorder.builder()->Build()->RenderTo(cv_dl_surface.canvas());
compareToReference(cv_dl_surface.pixmap(), sk_pixels,
dl_recorder.builder()->Build()->RenderTo(cv_dl_surface->canvas());
compareToReference(cv_dl_surface->pixmap(), sk_pixels,
info + " (Skia calls -> DisplayList -> surface)",
nullptr, nullptr, bg);
}
@ -1766,12 +1785,12 @@ class CanvasCompareTester {
caseP.render_to(ref_canvas, testP);
sk_sp<SkPicture> ref_x2_picture =
sk_x2_recorder.finishRecordingAsPicture();
RenderSurface ref_x2_surface =
std::unique_ptr<RenderSurface> ref_x2_surface =
env.MakeSurface(bg, TestWidth2, TestHeight2);
SkCanvas* ref_x2_canvas = ref_x2_surface.canvas();
SkCanvas* ref_x2_canvas = ref_x2_surface->canvas();
ref_x2_canvas->scale(TestScale, TestScale);
ref_x2_picture->playback(ref_x2_canvas);
const SkPixmap* ref_x2_pixels = ref_x2_surface.pixmap();
const SkPixmap* ref_x2_pixels = ref_x2_surface->pixmap();
ASSERT_EQ(ref_x2_pixels->width(), TestWidth2) << info;
ASSERT_EQ(ref_x2_pixels->height(), TestHeight2) << info;
ASSERT_EQ(ref_x2_pixels->info().bytesPerPixel(), 4) << info;
@ -1779,19 +1798,75 @@ class CanvasCompareTester {
DisplayListBuilder builder_x2(TestBounds);
caseP.render_to(builder_x2, testP);
sk_sp<DisplayList> display_list_x2 = builder_x2.Build();
RenderSurface test_x2_surface =
std::unique_ptr<RenderSurface> test_x2_surface =
env.MakeSurface(bg, TestWidth2, TestHeight2);
SkCanvas* test_x2_canvas = test_x2_surface.canvas();
SkCanvas* test_x2_canvas = test_x2_surface->canvas();
test_x2_canvas->scale(TestScale, TestScale);
display_list_x2->RenderTo(test_x2_canvas);
compareToReference(test_x2_surface.pixmap(), ref_x2_pixels,
compareToReference(test_x2_surface->pixmap(), ref_x2_pixels,
info + " (Both rendered scaled 2x)", nullptr, nullptr,
bg, TestWidth2, TestHeight2, false);
}
}
static void checkGroupOpacity(const RenderEnvironment& env,
sk_sp<DisplayList> display_list,
const SkPixmap* ref_pixmap,
const std::string info,
SkColor bg) {
SkScalar opacity = 128.0 / 255.0;
std::unique_ptr<RenderSurface> group_opacity_surface = env.MakeSurface(bg);
SkCanvas* group_opacity_canvas = group_opacity_surface->canvas();
display_list->RenderTo(group_opacity_canvas, opacity);
const SkPixmap* group_opacity_pixmap = group_opacity_surface->pixmap();
ASSERT_EQ(group_opacity_pixmap->width(), TestWidth) << info;
ASSERT_EQ(group_opacity_pixmap->height(), TestHeight) << info;
ASSERT_EQ(group_opacity_pixmap->info().bytesPerPixel(), 4) << info;
ASSERT_EQ(ref_pixmap->width(), TestWidth) << info;
ASSERT_EQ(ref_pixmap->height(), TestHeight) << info;
ASSERT_EQ(ref_pixmap->info().bytesPerPixel(), 4) << info;
int pixels_touched = 0;
int pixels_different = 0;
// We need to allow some slight differences per component due to the
// fact that rearranging discrete calculations can compound round off
// errors. Off-by-2 is enough for 8 bit components, but for the 565
// tests we allow at least 9 which is the maximum distance between
// samples when converted to 8 bits. (You might think it would be a
// max step of 8 converting 5 bits to 8 bits, but it is really
// converting 31 steps to 255 steps with an average step size of
// 8.23 - 24 of the steps are by 8, but 7 of them are by 9.)
int fudge = env.info().bytesPerPixel() < 4 ? 9 : 2;
for (int y = 0; y < TestHeight; y++) {
const uint32_t* ref_row = ref_pixmap->addr32(0, y);
const uint32_t* test_row = group_opacity_pixmap->addr32(0, y);
for (int x = 0; x < TestWidth; x++) {
uint32_t ref_pixel = ref_row[x];
uint32_t test_pixel = test_row[x];
if (ref_pixel != bg || test_pixel != bg) {
pixels_touched++;
for (int i = 0; i < 32; i += 8) {
int ref_comp = (ref_pixel >> i) & 0xff;
int bg_comp = (bg >> i) & 0xff;
SkScalar faded_comp = bg_comp + (ref_comp - bg_comp) * opacity;
int test_comp = (test_pixel >> i) & 0xff;
if (std::abs(faded_comp - test_comp) > fudge) {
pixels_different++;
break;
}
}
}
}
}
ASSERT_GT(pixels_touched, 20) << info;
ASSERT_LE(pixels_different, 1) << info;
}
static void checkPixels(const SkPixmap* ref_pixels,
SkRect ref_bounds,
const SkRect ref_bounds,
const std::string info,
const SkColor bg) {
SkPMColor untouched = SkPreMultiplyColor(bg);
@ -2844,7 +2919,7 @@ TEST_F(DisplayListCanvas, DrawPicture) {
TEST_F(DisplayListCanvas, DrawPictureWithMatrix) {
sk_sp<SkPicture> picture = makeTestPicture();
SkMatrix matrix = SkMatrix::Scale(0.95, 0.95);
SkMatrix matrix = SkMatrix::Scale(0.9, 0.9);
CanvasCompareTester::RenderAll( //
TestParameters(
[=](SkCanvas* canvas, const SkPaint& paint) { //

View File

@ -232,6 +232,7 @@ struct DisplayListInvocation {
size_t sk_byte_count_;
DlInvoker invoker;
bool supports_group_opacity_ = false;
bool sk_version_matches() {
return (op_count_ == sk_op_count_ && byte_count_ == sk_byte_count_);
@ -246,6 +247,8 @@ struct DisplayListInvocation {
bool is_empty() { return byte_count_ == 0; }
bool supports_group_opacity() { return supports_group_opacity_; }
int op_count() { return op_count_; }
// byte count for the individual ops, no DisplayList overhead
size_t raw_byte_count() { return byte_count_; }
@ -1397,5 +1400,183 @@ TEST(DisplayList, SetMaskFilterNullResetsMaskFilter) {
ASSERT_EQ(display_list->bytes(), sizeof(DisplayList) + 8u + 24u + 8u + 24u);
}
TEST(DisplayList, SingleOpsMightSupportGroupOpacityWithOrWithoutBlendMode) {
auto run_tests = [](std::string name,
void build(DisplayListBuilder & builder),
bool expect_for_op, bool expect_with_kSrc) {
{
// First test is the draw op, by itself
// (usually supports group opacity)
DisplayListBuilder builder;
build(builder);
auto display_list = builder.Build();
EXPECT_EQ(display_list->can_apply_group_opacity(), expect_for_op)
<< "{" << std::endl
<< " " << name << std::endl
<< "}";
}
{
// Second test i the draw op with kSrc,
// (usually fails group opacity)
DisplayListBuilder builder;
builder.setBlendMode(SkBlendMode::kSrc);
build(builder);
auto display_list = builder.Build();
EXPECT_EQ(display_list->can_apply_group_opacity(), expect_with_kSrc)
<< "{" << std::endl
<< " builder.setBlendMode(kSrc);" << std::endl
<< " " << name << std::endl
<< "}";
}
};
#define RUN_TESTS(body) \
run_tests( \
#body, [](DisplayListBuilder& builder) { body }, true, false)
#define RUN_TESTS2(body, expect) \
run_tests( \
#body, [](DisplayListBuilder& builder) { body }, expect, expect)
RUN_TESTS(builder.drawPaint(););
RUN_TESTS2(builder.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);, true);
RUN_TESTS2(builder.drawColor(SK_ColorRED, SkBlendMode::kSrc);, false);
RUN_TESTS(builder.drawLine({0, 0}, {10, 10}););
RUN_TESTS(builder.drawRect({0, 0, 10, 10}););
RUN_TESTS(builder.drawOval({0, 0, 10, 10}););
RUN_TESTS(builder.drawCircle({10, 10}, 5););
RUN_TESTS(builder.drawRRect(SkRRect::MakeRectXY({0, 0, 10, 10}, 2, 2)););
RUN_TESTS(builder.drawDRRect(SkRRect::MakeRectXY({0, 0, 10, 10}, 2, 2),
SkRRect::MakeRectXY({2, 2, 8, 8}, 2, 2)););
RUN_TESTS(builder.drawPath(
SkPath().addOval({0, 0, 10, 10}).addOval({5, 5, 15, 15})););
RUN_TESTS(builder.drawArc({0, 0, 10, 10}, 0, M_PI, true););
RUN_TESTS2(builder.drawPoints(SkCanvas::kPoints_PointMode, TestPointCount,
TestPoints);
, false);
RUN_TESTS2(builder.drawVertices(TestVertices1, SkBlendMode::kSrc);, false);
RUN_TESTS(builder.drawImage(TestImage1, {0, 0}, DisplayList::LinearSampling,
true););
RUN_TESTS2(
builder.drawImage(TestImage1, {0, 0}, DisplayList::LinearSampling, false);
, true);
RUN_TESTS(builder.drawImageRect(TestImage1, {10, 10, 20, 20}, {0, 0, 10, 10},
DisplayList::NearestSampling, true););
RUN_TESTS2(builder.drawImageRect(TestImage1, {10, 10, 20, 20}, {0, 0, 10, 10},
DisplayList::NearestSampling, false);
, true);
RUN_TESTS(builder.drawImageNine(TestImage2, {20, 20, 30, 30}, {0, 0, 20, 20},
SkFilterMode::kLinear, true););
RUN_TESTS2(builder.drawImageNine(TestImage2, {20, 20, 30, 30}, {0, 0, 20, 20},
SkFilterMode::kLinear, false);
, true);
RUN_TESTS(builder.drawImageLattice(
TestImage1,
{TestDivs1, TestDivs1, nullptr, 3, 3, &TestLatticeSrcRect, nullptr},
{10, 10, 40, 40}, SkFilterMode::kNearest, true););
RUN_TESTS2(builder.drawImageLattice(
TestImage1,
{TestDivs1, TestDivs1, nullptr, 3, 3, &TestLatticeSrcRect, nullptr},
{10, 10, 40, 40}, SkFilterMode::kNearest, false);
, true);
static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
RUN_TESTS2(builder.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
SkBlendMode::kSrcIn,
DisplayList::NearestSampling, nullptr, true);
, false);
RUN_TESTS2(builder.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
SkBlendMode::kSrcIn,
DisplayList::NearestSampling, nullptr, false);
, false);
RUN_TESTS(builder.drawPicture(TestPicture1, nullptr, true););
RUN_TESTS2(builder.drawPicture(TestPicture1, nullptr, false);, true);
EXPECT_TRUE(TestDisplayList1->can_apply_group_opacity());
RUN_TESTS2(builder.drawDisplayList(TestDisplayList1);, true);
{
static DisplayListBuilder builder;
builder.drawRect({0, 0, 10, 10});
builder.drawRect({5, 5, 15, 15});
static auto display_list = builder.Build();
RUN_TESTS2(builder.drawDisplayList(display_list);, false);
}
RUN_TESTS(builder.drawTextBlob(TestBlob1, 0, 0););
RUN_TESTS2(builder.drawShadow(TestPath1, SK_ColorBLACK, 1.0, false, 1.0);
, false);
#undef RUN_TESTS2
#undef RUN_TESTS
}
TEST(DisplayList, OverlappingOpsDoNotSupportGroupOpacity) {
DisplayListBuilder builder;
for (int i = 0; i < 10; i++) {
builder.drawRect(SkRect::MakeXYWH(i * 10, 0, 30, 30));
}
auto display_list = builder.Build();
EXPECT_FALSE(display_list->can_apply_group_opacity());
}
TEST(DisplayList, SaveLayerFalseSupportsGroupOpacityWithOverlappingChidren) {
DisplayListBuilder builder;
builder.saveLayer(nullptr, false);
for (int i = 0; i < 10; i++) {
builder.drawRect(SkRect::MakeXYWH(i * 10, 0, 30, 30));
}
builder.restore();
auto display_list = builder.Build();
EXPECT_TRUE(display_list->can_apply_group_opacity());
}
TEST(DisplayList, SaveLayerTrueSupportsGroupOpacityWithOverlappingChidren) {
DisplayListBuilder builder;
builder.saveLayer(nullptr, true);
for (int i = 0; i < 10; i++) {
builder.drawRect(SkRect::MakeXYWH(i * 10, 0, 30, 30));
}
builder.restore();
auto display_list = builder.Build();
EXPECT_TRUE(display_list->can_apply_group_opacity());
}
TEST(DisplayList, SaveLayerFalseWithSrcBlendSupportsGroupOpacity) {
DisplayListBuilder builder;
builder.setBlendMode(SkBlendMode::kSrc);
builder.saveLayer(nullptr, false);
builder.drawRect({0, 0, 10, 10});
builder.restore();
auto display_list = builder.Build();
EXPECT_TRUE(display_list->can_apply_group_opacity());
}
TEST(DisplayList, SaveLayerTrueWithSrcBlendDoesNotSupportGroupOpacity) {
DisplayListBuilder builder;
builder.setBlendMode(SkBlendMode::kSrc);
builder.saveLayer(nullptr, true);
builder.drawRect({0, 0, 10, 10});
builder.restore();
auto display_list = builder.Build();
EXPECT_FALSE(display_list->can_apply_group_opacity());
}
TEST(DisplayList, SaveLayerFalseSupportsGroupOpacityWithChildSrcBlend) {
DisplayListBuilder builder;
builder.saveLayer(nullptr, false);
builder.setBlendMode(SkBlendMode::kSrc);
builder.drawRect({0, 0, 10, 10});
builder.restore();
auto display_list = builder.Build();
EXPECT_TRUE(display_list->can_apply_group_opacity());
}
TEST(DisplayList, SaveLayerTrueSupportsGroupOpacityWithChildSrcBlend) {
DisplayListBuilder builder;
builder.saveLayer(nullptr, true);
builder.setBlendMode(SkBlendMode::kSrc);
builder.drawRect({0, 0, 10, 10});
builder.restore();
auto display_list = builder.Build();
EXPECT_TRUE(display_list->can_apply_group_opacity());
}
} // namespace testing
} // namespace flutter

View File

@ -26,6 +26,25 @@ constexpr float invert_color_matrix[20] = {
};
// clang-format on
void SkPaintDispatchHelper::save_opacity(bool reset_and_restore) {
if (opacity_ >= SK_Scalar1) {
reset_and_restore = false;
}
save_stack_.emplace_back(opacity_, reset_and_restore);
if (reset_and_restore) {
opacity_ = SK_Scalar1;
setColor(current_color_);
}
}
void SkPaintDispatchHelper::restore_opacity() {
SaveInfo& info = save_stack_.back();
if (info.restore_opacity) {
opacity_ = info.opacity;
setColor(current_color_);
}
save_stack_.pop_back();
}
void SkPaintDispatchHelper::setAntiAlias(bool aa) {
paint_.setAntiAlias(aa);
}
@ -52,7 +71,11 @@ void SkPaintDispatchHelper::setStrokeMiter(SkScalar limit) {
paint_.setStrokeMiter(limit);
}
void SkPaintDispatchHelper::setColor(SkColor color) {
current_color_ = color;
paint_.setColor(color);
if (has_opacity()) {
paint_.setAlphaf(paint_.getAlphaf() * opacity());
}
}
void SkPaintDispatchHelper::setBlendMode(SkBlendMode mode) {
paint_.setBlendMode(mode);

View File

@ -96,6 +96,13 @@ class IgnoreTransformDispatchHelper : public virtual Dispatcher {
// which can be accessed at any time via paint().
class SkPaintDispatchHelper : public virtual Dispatcher {
public:
SkPaintDispatchHelper(SkScalar opacity = SK_Scalar1)
: current_color_(SK_ColorBLACK), opacity_(opacity) {
if (opacity < SK_Scalar1) {
paint_.setAlphaf(opacity);
}
}
void setAntiAlias(bool aa) override;
void setDither(bool dither) override;
void setStyle(SkPaint::Style style) override;
@ -115,6 +122,12 @@ class SkPaintDispatchHelper : public virtual Dispatcher {
void setImageFilter(sk_sp<SkImageFilter> filter) override;
const SkPaint& paint() { return paint_; }
SkScalar opacity() { return opacity_; }
bool has_opacity() { return opacity_ < SK_Scalar1; }
protected:
void save_opacity(bool reset_and_restore);
void restore_opacity();
private:
SkPaint paint_;
@ -122,6 +135,18 @@ class SkPaintDispatchHelper : public virtual Dispatcher {
sk_sp<SkColorFilter> color_filter_;
sk_sp<SkColorFilter> makeColorFilter();
struct SaveInfo {
SaveInfo(SkScalar opacity, bool restore_opacity)
: opacity(opacity), restore_opacity(restore_opacity) {}
SkScalar opacity;
bool restore_opacity;
};
std::vector<SaveInfo> save_stack_;
SkColor current_color_;
SkScalar opacity_;
};
class SkMatrixSource {

View File

@ -10,6 +10,7 @@ namespace flutter {
ClipPathLayer::ClipPathLayer(const SkPath& clip_path, Clip clip_behavior)
: clip_path_(clip_path), clip_behavior_(clip_behavior) {
FML_DCHECK(clip_behavior != Clip::none);
set_layer_can_inherit_opacity(true);
}
void ClipPathLayer::Diff(DiffContext* context, const Layer* old_layer) {
@ -58,16 +59,20 @@ void ClipPathLayer::Paint(PaintContext& context) const {
context.internal_nodes_canvas->clipPath(clip_path_,
clip_behavior_ != Clip::hardEdge);
if (UsesSaveLayer()) {
TRACE_EVENT0("flutter", "Canvas::saveLayer");
context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr);
if (!UsesSaveLayer()) {
PaintChildren(context);
return;
}
AutoCachePaint cache_paint(context);
TRACE_EVENT0("flutter", "Canvas::saveLayer");
context.internal_nodes_canvas->saveLayer(paint_bounds(), cache_paint.paint());
PaintChildren(context);
if (UsesSaveLayer()) {
context.internal_nodes_canvas->restore();
if (context.checkerboard_offscreen_layers) {
DrawCheckerboard(context.internal_nodes_canvas, paint_bounds());
}
context.internal_nodes_canvas->restore();
if (context.checkerboard_offscreen_layers) {
DrawCheckerboard(context.internal_nodes_canvas, paint_bounds());
}
}

View File

@ -10,6 +10,7 @@ namespace flutter {
ClipRectLayer::ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior)
: clip_rect_(clip_rect), clip_behavior_(clip_behavior) {
FML_DCHECK(clip_behavior != Clip::none);
set_layer_can_inherit_opacity(true);
}
void ClipRectLayer::Diff(DiffContext* context, const Layer* old_layer) {
@ -57,16 +58,20 @@ void ClipRectLayer::Paint(PaintContext& context) const {
context.internal_nodes_canvas->clipRect(clip_rect_,
clip_behavior_ != Clip::hardEdge);
if (UsesSaveLayer()) {
TRACE_EVENT0("flutter", "Canvas::saveLayer");
context.internal_nodes_canvas->saveLayer(clip_rect_, nullptr);
if (!UsesSaveLayer()) {
PaintChildren(context);
return;
}
AutoCachePaint cache_paint(context);
TRACE_EVENT0("flutter", "Canvas::saveLayer");
context.internal_nodes_canvas->saveLayer(clip_rect_, cache_paint.paint());
PaintChildren(context);
if (UsesSaveLayer()) {
context.internal_nodes_canvas->restore();
if (context.checkerboard_offscreen_layers) {
DrawCheckerboard(context.internal_nodes_canvas, clip_rect_);
}
context.internal_nodes_canvas->restore();
if (context.checkerboard_offscreen_layers) {
DrawCheckerboard(context.internal_nodes_canvas, clip_rect_);
}
}

View File

@ -10,6 +10,7 @@ namespace flutter {
ClipRRectLayer::ClipRRectLayer(const SkRRect& clip_rrect, Clip clip_behavior)
: clip_rrect_(clip_rrect), clip_behavior_(clip_behavior) {
FML_DCHECK(clip_behavior != Clip::none);
set_layer_can_inherit_opacity(true);
}
void ClipRRectLayer::Diff(DiffContext* context, const Layer* old_layer) {
@ -58,16 +59,20 @@ void ClipRRectLayer::Paint(PaintContext& context) const {
context.internal_nodes_canvas->clipRRect(clip_rrect_,
clip_behavior_ != Clip::hardEdge);
if (UsesSaveLayer()) {
TRACE_EVENT0("flutter", "Canvas::saveLayer");
context.internal_nodes_canvas->saveLayer(paint_bounds(), nullptr);
if (!UsesSaveLayer()) {
PaintChildren(context);
return;
}
AutoCachePaint cache_paint(context);
TRACE_EVENT0("flutter", "Canvas::saveLayer");
context.internal_nodes_canvas->saveLayer(paint_bounds(), cache_paint.paint());
PaintChildren(context);
if (UsesSaveLayer()) {
context.internal_nodes_canvas->restore();
if (context.checkerboard_offscreen_layers) {
DrawCheckerboard(context.internal_nodes_canvas, paint_bounds());
}
context.internal_nodes_canvas->restore();
if (context.checkerboard_offscreen_layers) {
DrawCheckerboard(context.internal_nodes_canvas, paint_bounds());
}
}

View File

@ -121,6 +121,13 @@ void ContainerLayer::Paint(PaintContext& context) const {
PaintChildren(context);
}
static bool safe_intersection_test(const SkRect* rect1, const SkRect& rect2) {
if (rect1->isEmpty() || rect2.isEmpty()) {
return false;
}
return rect1->intersects(rect2);
}
void ContainerLayer::PrerollChildren(PrerollContext* context,
const SkMatrix& child_matrix,
SkRect* child_paint_bounds) {
@ -129,13 +136,28 @@ void ContainerLayer::PrerollChildren(PrerollContext* context,
FML_DCHECK(!context->has_platform_view);
bool child_has_platform_view = false;
bool child_has_texture_layer = false;
bool subtree_can_inherit_opacity = layer_can_inherit_opacity();
for (auto& layer : layers_) {
// Reset context->has_platform_view to false so that layers aren't treated
// as if they have a platform view based on one being previously found in a
// sibling tree.
context->has_platform_view = false;
// Initialize the "inherit opacity" flag to the value recorded in the layer
// and allow it to override the answer during its |Preroll|
context->subtree_can_inherit_opacity = layer->layer_can_inherit_opacity();
layer->Preroll(context, child_matrix);
subtree_can_inherit_opacity =
subtree_can_inherit_opacity && context->subtree_can_inherit_opacity;
if (subtree_can_inherit_opacity &&
safe_intersection_test(child_paint_bounds, layer->paint_bounds())) {
// This will allow inheritance by a linear sequence of non-overlapping
// children, but will fail with a grid or other arbitrary 2D layout.
// See https://github.com/flutter/flutter/issues/93899
subtree_can_inherit_opacity = false;
}
child_paint_bounds->join(layer->paint_bounds());
child_has_platform_view =
@ -146,6 +168,7 @@ void ContainerLayer::PrerollChildren(PrerollContext* context,
context->has_platform_view = child_has_platform_view;
context->has_texture_layer = child_has_texture_layer;
context->subtree_can_inherit_opacity = subtree_can_inherit_opacity;
set_subtree_has_platform_view(child_has_platform_view);
}
@ -189,6 +212,12 @@ MergedContainerLayer::MergedContainerLayer() {
// child becomes the cacheable child, but at the potential cost of
// not being as stable in the raster cache from frame to frame.
ContainerLayer::Add(std::make_shared<ContainerLayer>());
// The interposing Container only recurses to its children with no
// additional processing so, by default, it can pass an inherited
// opacity on to its children, subject only to the accumulation
// logic that happens during |PrerollChildren|.
GetChildContainer()->set_layer_can_inherit_opacity(true);
}
void MergedContainerLayer::DiffChildren(DiffContext* context,

View File

@ -15,7 +15,12 @@ DisplayListLayer::DisplayListLayer(const SkPoint& offset,
: offset_(offset),
display_list_(std::move(display_list)),
is_complex_(is_complex),
will_change_(will_change) {}
will_change_(will_change) {
if (display_list_.skia_object()) {
set_layer_can_inherit_opacity(
display_list_.skia_object()->can_apply_group_opacity());
}
}
bool DisplayListLayer::IsReplacing(DiffContext* context,
const Layer* layer) const {
@ -94,8 +99,10 @@ void DisplayListLayer::Preroll(PrerollContext* context,
if (auto* cache = context->raster_cache) {
TRACE_EVENT0("flutter", "DisplayListLayer::RasterCache (Preroll)");
if (context->cull_rect.intersects(bounds)) {
cache->Prepare(context, disp_list, is_complex_, will_change_, matrix,
offset_);
if (cache->Prepare(context, disp_list, is_complex_, will_change_, matrix,
offset_)) {
context->subtree_can_inherit_opacity = true;
}
} else {
// Don't evict raster cache entry during partial repaint
cache->Touch(disp_list, matrix);
@ -116,13 +123,17 @@ void DisplayListLayer::Paint(PaintContext& context) const {
context.leaf_nodes_canvas->getTotalMatrix()));
#endif
if (context.raster_cache &&
context.raster_cache->Draw(*display_list(), *context.leaf_nodes_canvas)) {
TRACE_EVENT_INSTANT0("flutter", "raster cache hit");
return;
if (context.raster_cache) {
AutoCachePaint cache_paint(context);
if (context.raster_cache->Draw(*display_list(), *context.leaf_nodes_canvas,
cache_paint.paint())) {
TRACE_EVENT_INSTANT0("flutter", "raster cache hit");
return;
}
}
display_list()->RenderTo(context.leaf_nodes_canvas);
display_list()->RenderTo(context.leaf_nodes_canvas,
context.inherited_opacity);
}
} // namespace flutter

View File

@ -13,7 +13,8 @@ Layer::Layer()
: paint_bounds_(SkRect::MakeEmpty()),
unique_id_(NextUniqueID()),
original_layer_id_(unique_id_),
subtree_has_platform_view_(false) {}
subtree_has_platform_view_(false),
layer_can_inherit_opacity_(false) {}
Layer::~Layer() = default;

View File

@ -61,6 +61,25 @@ struct PrerollContext {
// These allow us to track properties like elevation, opacity, and the
// prescence of a texture layer during Preroll.
bool has_texture_layer = false;
// This value indicates that the entire subtree below the layer can inherit
// an opacity value and modulate its own visibility accordingly.
// For Layers which cannot either apply such an inherited opacity nor pass
// it along to their children, they can ignore this value as its default
// behavior is "opt-in".
// For Layers that support this condition, it can be recorded in their
// constructor using the |set_layer_can_inherit_opacity| method and the
// value will be accumulated and recorded by the |PrerollChidren| method
// automatically.
// If the property is more dynamic then the Layer can dynamically set this
// flag before returning from the |Preroll| method.
// For ContainerLayers that need to know if their children can inherit
// the value, the |PrerollChildren| method will have set this value in
// the context before it returns. If the container can support it as long
// as the subtree can support it, no further work needs to be done other
// than to remember the value so that it can choose the right strategy
// for its |Paint| method.
bool subtree_can_inherit_opacity = false;
};
class PictureLayer;
@ -147,6 +166,34 @@ class Layer {
const RasterCache* raster_cache;
const bool checkerboard_offscreen_layers;
const float frame_device_pixel_ratio;
// The following value should be used to modulate the opacity of the
// layer during |Paint|. If the layer does not set the corresponding
// |layer_can_inherit_opacity()| flag, then this value should always
// be |SK_Scalar1|. The value is to be applied as if by using a
// |saveLayer| with an |SkPaint| initialized to this alphaf value and
// a |kSrcOver| blend mode.
SkScalar inherited_opacity = SK_Scalar1;
};
class AutoCachePaint {
public:
AutoCachePaint(PaintContext& context) : context_(context) {
needs_paint_ = context.inherited_opacity < SK_Scalar1;
if (needs_paint_) {
paint_.setAlphaf(context.inherited_opacity);
context.inherited_opacity = SK_Scalar1;
}
}
~AutoCachePaint() { context_.inherited_opacity = paint_.getAlphaf(); }
const SkPaint* paint() { return needs_paint_ ? &paint_ : nullptr; }
private:
PaintContext& context_;
SkPaint paint_;
bool needs_paint_;
};
// Calls SkCanvas::saveLayer and restores the layer upon destruction. Also
@ -216,6 +263,18 @@ class Layer {
subtree_has_platform_view_ = value;
}
// Returns true if the layer can render with an added opacity value inherited
// from an OpacityLayer ancestor and delivered to its |Paint| method through
// the |PaintContext.inherited_opacity| field. This flag can be set either
// in the Layer's constructor if it is a lifetime constant value, or during
// the |Preroll| method if it must determine the capability based on data
// only available when it is part of a tree. It must set this value before
// recursing to its children if it is a |ContainerLayer|.
bool layer_can_inherit_opacity() const { return layer_can_inherit_opacity_; }
void set_layer_can_inherit_opacity(bool value) {
layer_can_inherit_opacity_ = value;
}
// Returns the paint bounds in the layer's local coordinate system
// as determined during Preroll(). The bounds should include any
// transform, clip or distortions performed by the layer itself,
@ -253,6 +312,9 @@ class Layer {
// See https://github.com/flutter/flutter/issues/81419
return true;
}
if (context.inherited_opacity == 0) {
return false;
}
// Workaround for Skia bug (quickReject does not reject empty bounds).
// https://bugs.chromium.org/p/skia/issues/detail?id=10951
if (paint_bounds_.isEmpty()) {
@ -282,6 +344,7 @@ class Layer {
uint64_t unique_id_;
uint64_t original_layer_id_;
bool subtree_has_platform_view_;
bool layer_can_inherit_opacity_;
static uint64_t NextUniqueID();

View File

@ -10,7 +10,12 @@
namespace flutter {
OpacityLayer::OpacityLayer(SkAlpha alpha, const SkPoint& offset)
: alpha_(alpha), offset_(offset) {}
: alpha_(alpha), offset_(offset), children_can_accept_opacity_(false) {
// We can always inhert opacity even if we cannot pass it along to
// our children as we can accumulate the inherited opacity into our
// own opacity value before we recurse.
set_layer_can_inherit_opacity(true);
}
void OpacityLayer::Diff(DiffContext* context, const Layer* old_layer) {
DiffContext::AutoSubtreeRestore subtree(context);
@ -50,8 +55,11 @@ void OpacityLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
context->mutators_stack.Pop();
context->mutators_stack.Pop();
{
set_paint_bounds(paint_bounds().makeOffset(offset_.fX, offset_.fY));
set_children_can_accept_opacity(context->subtree_can_inherit_opacity);
set_paint_bounds(paint_bounds().makeOffset(offset_.fX, offset_.fY));
if (!children_can_accept_opacity()) {
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
child_matrix = RasterCache::GetIntegralTransCTM(child_matrix);
#endif
@ -66,9 +74,6 @@ void OpacityLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("flutter", "OpacityLayer::Paint");
FML_DCHECK(needs_painting(context));
SkPaint paint;
paint.setAlpha(alpha_);
SkAutoCanvasRestore save(context.internal_nodes_canvas, true);
context.internal_nodes_canvas->translate(offset_.fX, offset_.fY);
@ -77,6 +82,19 @@ void OpacityLayer::Paint(PaintContext& context) const {
context.leaf_nodes_canvas->getTotalMatrix()));
#endif
SkScalar inherited_opacity = context.inherited_opacity;
SkScalar subtree_opacity = opacity() * inherited_opacity;
if (children_can_accept_opacity()) {
context.inherited_opacity = subtree_opacity;
PaintChildren(context);
context.inherited_opacity = inherited_opacity;
return;
}
SkPaint paint;
paint.setAlphaf(subtree_opacity);
if (context.raster_cache &&
context.raster_cache->Draw(GetCacheableChild(),
*context.leaf_nodes_canvas, &paint)) {

View File

@ -33,9 +33,22 @@ class OpacityLayer : public MergedContainerLayer {
void Paint(PaintContext& context) const override;
// Returns whether the children are capable of inheriting an opacity value
// and modifying their rendering accordingly. This value is only guaranteed
// to be valid after the local |Preroll| method is called.
bool children_can_accept_opacity() const {
return children_can_accept_opacity_;
}
void set_children_can_accept_opacity(bool value) {
children_can_accept_opacity_ = value;
}
SkScalar opacity() const { return alpha_ * 1.0 / SK_AlphaOPAQUE; }
private:
SkAlpha alpha_;
SkPoint offset_;
bool children_can_accept_opacity_;
FML_DISALLOW_COPY_AND_ASSIGN(OpacityLayer);
};

View File

@ -262,8 +262,8 @@ TEST_F(OpacityLayerTest, HalfTransparent) {
EXPECT_EQ(mock_layer->parent_mutators(),
std::vector({Mutator(layer_transform), Mutator(alpha_half)}));
const SkPaint opacity_paint =
SkPaint(SkColor4f::FromColor(SkColorSetA(SK_ColorBLACK, alpha_half)));
SkPaint opacity_paint;
opacity_paint.setAlphaf(alpha_half * (1.0 / SK_AlphaOPAQUE));
SkRect opacity_bounds;
expected_layer_bounds.makeOffset(-layer_offset.fX, -layer_offset.fY)
.roundOut(&opacity_bounds);
@ -352,10 +352,10 @@ TEST_F(OpacityLayerTest, Nested) {
// EXPECT_EQ(mock_layer3->parent_mutators(),
// std::vector({Mutator(layer1_transform), Mutator(alpha1)}));
const SkPaint opacity1_paint =
SkPaint(SkColor4f::FromColor(SkColorSetA(SK_ColorBLACK, alpha1)));
const SkPaint opacity2_paint =
SkPaint(SkColor4f::FromColor(SkColorSetA(SK_ColorBLACK, alpha2)));
SkPaint opacity1_paint;
opacity1_paint.setAlphaf(alpha1 * (1.0 / SK_AlphaOPAQUE));
SkPaint opacity2_paint;
opacity2_paint.setAlphaf(alpha2 * (1.0 / SK_AlphaOPAQUE));
SkRect opacity1_bounds, opacity2_bounds;
expected_layer1_bounds.makeOffset(-layer1_offset.fX, -layer1_offset.fY)
.roundOut(&opacity1_bounds);

View File

@ -117,8 +117,10 @@ void PictureLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
if (auto* cache = context->raster_cache) {
TRACE_EVENT0("flutter", "PictureLayer::RasterCache (Preroll)");
if (context->cull_rect.intersects(bounds)) {
cache->Prepare(context, sk_picture, is_complex_, will_change_, matrix,
offset_);
if (cache->Prepare(context, sk_picture, is_complex_, will_change_, matrix,
offset_)) {
context->subtree_can_inherit_opacity = true;
}
} else {
// Don't evict raster cache entry during partial repaint
cache->Touch(sk_picture, matrix);
@ -140,11 +142,16 @@ void PictureLayer::Paint(PaintContext& context) const {
context.leaf_nodes_canvas->getTotalMatrix()));
#endif
if (context.raster_cache &&
context.raster_cache->Draw(*picture(), *context.leaf_nodes_canvas)) {
TRACE_EVENT_INSTANT0("flutter", "raster cache hit");
return;
if (context.raster_cache) {
AutoCachePaint cache_paint(context);
if (context.raster_cache->Draw(*picture(), *context.leaf_nodes_canvas,
cache_paint.paint())) {
TRACE_EVENT_INSTANT0("flutter", "raster cache hit");
return;
}
}
FML_DCHECK(context.inherited_opacity == SK_Scalar1);
picture()->playback(context.leaf_nodes_canvas);
}

View File

@ -9,7 +9,9 @@ namespace flutter {
ShaderMaskLayer::ShaderMaskLayer(sk_sp<SkShader> shader,
const SkRect& mask_rect,
SkBlendMode blend_mode)
: shader_(shader), mask_rect_(mask_rect), blend_mode_(blend_mode) {}
: shader_(shader), mask_rect_(mask_rect), blend_mode_(blend_mode) {
set_layer_can_inherit_opacity(true);
}
void ShaderMaskLayer::Diff(DiffContext* context, const Layer* old_layer) {
DiffContext::AutoSubtreeRestore subtree(context);
@ -37,8 +39,9 @@ void ShaderMaskLayer::Paint(PaintContext& context) const {
TRACE_EVENT0("flutter", "ShaderMaskLayer::Paint");
FML_DCHECK(needs_painting(context));
Layer::AutoSaveLayer save =
Layer::AutoSaveLayer::Create(context, paint_bounds(), nullptr);
AutoCachePaint cache_paint(context);
Layer::AutoSaveLayer save = Layer::AutoSaveLayer::Create(
context, paint_bounds(), cache_paint.paint());
PaintChildren(context);
SkPaint paint;

View File

@ -17,7 +17,9 @@ TextureLayer::TextureLayer(const SkPoint& offset,
size_(size),
texture_id_(texture_id),
freeze_(freeze),
sampling_(sampling) {}
sampling_(sampling) {
set_layer_can_inherit_opacity(true);
}
void TextureLayer::Diff(DiffContext* context, const Layer* old_layer) {
DiffContext::AutoSubtreeRestore subtree(context);
@ -58,8 +60,9 @@ void TextureLayer::Paint(PaintContext& context) const {
TRACE_EVENT_INSTANT0("flutter", "null texture");
return;
}
AutoCachePaint cache_paint(context);
texture->Paint(*context.leaf_nodes_canvas, paint_bounds(), freeze_,
context.gr_context, sampling_);
context.gr_context, sampling_, cache_paint.paint());
}
} // namespace flutter

View File

@ -24,6 +24,7 @@ TransformLayer::TransformLayer(const SkMatrix& transform)
FML_LOG(ERROR) << "TransformLayer is constructed with an invalid matrix.";
transform_.setIdentity();
}
set_layer_can_inherit_opacity(true);
}
void TransformLayer::Diff(DiffContext* context, const Layer* old_layer) {

View File

@ -341,7 +341,9 @@ void RasterCache::Touch(DisplayList* display_list,
}
}
bool RasterCache::Draw(const SkPicture& picture, SkCanvas& canvas) const {
bool RasterCache::Draw(const SkPicture& picture,
SkCanvas& canvas,
const SkPaint* paint) const {
PictureRasterCacheKey cache_key(picture.uniqueID(), canvas.getTotalMatrix());
auto it = picture_cache_.find(cache_key);
if (it == picture_cache_.end()) {
@ -353,7 +355,7 @@ bool RasterCache::Draw(const SkPicture& picture, SkCanvas& canvas) const {
entry.used_this_frame = true;
if (entry.image) {
entry.image->draw(canvas, nullptr);
entry.image->draw(canvas, paint);
return true;
}
@ -361,7 +363,8 @@ bool RasterCache::Draw(const SkPicture& picture, SkCanvas& canvas) const {
}
bool RasterCache::Draw(const DisplayList& display_list,
SkCanvas& canvas) const {
SkCanvas& canvas,
const SkPaint* paint) const {
DisplayListRasterCacheKey cache_key(display_list.unique_id(),
canvas.getTotalMatrix());
auto it = display_list_cache_.find(cache_key);
@ -374,7 +377,7 @@ bool RasterCache::Draw(const DisplayList& display_list,
entry.used_this_frame = true;
if (entry.image) {
entry.image->draw(canvas, nullptr);
entry.image->draw(canvas, paint);
return true;
}
@ -383,7 +386,7 @@ bool RasterCache::Draw(const DisplayList& display_list,
bool RasterCache::Draw(const Layer* layer,
SkCanvas& canvas,
SkPaint* paint) const {
const SkPaint* paint) const {
LayerRasterCacheKey cache_key(layer->unique_id(), canvas.getTotalMatrix());
auto it = layer_cache_.find(cache_key);
if (it == layer_cache_.end()) {

View File

@ -205,12 +205,16 @@ class RasterCache {
// Find the raster cache for the picture and draw it to the canvas.
//
// Return true if it's found and drawn.
bool Draw(const SkPicture& picture, SkCanvas& canvas) const;
bool Draw(const SkPicture& picture,
SkCanvas& canvas,
const SkPaint* paint = nullptr) const;
// Find the raster cache for the display list and draw it to the canvas.
//
// Return true if it's found and drawn.
bool Draw(const DisplayList& display_list, SkCanvas& canvas) const;
bool Draw(const DisplayList& display_list,
SkCanvas& canvas,
const SkPaint* paint = nullptr) const;
// Find the raster cache for the layer and draw it to the canvas.
//
@ -220,7 +224,7 @@ class RasterCache {
// Return true if the layer raster cache is found and drawn.
bool Draw(const Layer* layer,
SkCanvas& canvas,
SkPaint* paint = nullptr) const;
const SkPaint* paint = nullptr) const;
void PrepareNewFrame();
void CleanupAfterFrame();

View File

@ -13,21 +13,22 @@ void MockTexture::Paint(SkCanvas& canvas,
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) {
const SkSamplingOptions& sampling,
const SkPaint* paint) {
paint_calls_.emplace_back(
PaintCall{canvas, bounds, freeze, context, sampling});
PaintCall{canvas, bounds, freeze, context, sampling, paint});
}
bool operator==(const MockTexture::PaintCall& a,
const MockTexture::PaintCall& b) {
return &a.canvas == &b.canvas && a.bounds == b.bounds &&
a.context == b.context && a.freeze == b.freeze &&
a.sampling == b.sampling;
a.sampling == b.sampling && a.paint == b.paint;
}
std::ostream& operator<<(std::ostream& os, const MockTexture::PaintCall& data) {
return os << &data.canvas << " " << data.bounds << " " << data.context << " "
<< data.freeze << " " << data.sampling;
<< data.freeze << " " << data.sampling << " " << data.paint;
}
} // namespace testing

View File

@ -22,6 +22,7 @@ class MockTexture : public Texture {
bool freeze;
GrDirectContext* context;
SkSamplingOptions sampling;
const SkPaint* paint;
};
explicit MockTexture(int64_t textureId);
@ -31,7 +32,8 @@ class MockTexture : public Texture {
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) override;
const SkSamplingOptions& sampling,
const SkPaint* paint = nullptr) override;
void OnGrContextCreated() override { gr_context_created_ = true; }
void OnGrContextDestroyed() override { gr_context_destroyed_ = true; }

View File

@ -1719,7 +1719,8 @@ class MockTexture : public Texture {
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions&) override {}
const SkSamplingOptions&,
const SkPaint* paint) override {}
void OnGrContextCreated() override {}

View File

@ -38,7 +38,8 @@ void AndroidExternalTextureGL::Paint(SkCanvas& canvas,
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) {
const SkSamplingOptions& sampling,
const SkPaint* paint) {
if (state_ == AttachmentState::detached) {
return;
}
@ -69,7 +70,7 @@ void AndroidExternalTextureGL::Paint(SkCanvas& canvas,
transformAroundCenter.postTranslate(0.5, 0.5);
canvas.concat(transformAroundCenter);
}
canvas.drawImage(image, 0, 0, sampling, nullptr);
canvas.drawImage(image, 0, 0, sampling, paint);
}
}

View File

@ -25,7 +25,8 @@ class AndroidExternalTextureGL : public flutter::Texture {
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) override;
const SkSamplingOptions& sampling,
const SkPaint* paint) override;
void OnGrContextCreated() override;

View File

@ -30,11 +30,12 @@
textureID:(int64_t)textureID
texture:(nonnull NSObject<FlutterTexture>*)texture;
- (void)paint:(SkCanvas&)canvas
bounds:(const SkRect&)bounds
freeze:(BOOL)freeze
grContext:(nonnull GrDirectContext*)grContext
sampling:(const SkSamplingOptions&)sampling;
- (void)canvas:(SkCanvas&)canvas
bounds:(const SkRect&)bounds
freeze:(BOOL)freeze
grContext:(nonnull GrDirectContext*)grContext
sampling:(const SkSamplingOptions&)sampling
paint:(nullable const SkPaint*)paint;
- (void)onGrContextCreated;

View File

@ -46,11 +46,12 @@ FLUTTER_ASSERT_ARC
}
}
- (void)paint:(SkCanvas&)canvas
bounds:(const SkRect&)bounds
freeze:(BOOL)freeze
grContext:(nonnull GrDirectContext*)grContext
sampling:(const SkSamplingOptions&)sampling {
- (void)canvas:(SkCanvas&)canvas
bounds:(const SkRect&)bounds
freeze:(BOOL)freeze
grContext:(nonnull GrDirectContext*)grContext
sampling:(const SkSamplingOptions&)sampling
paint:(nullable const SkPaint*)paint {
const bool needsUpdatedTexture = (!freeze && _textureFrameAvailable) || !_externalImage;
if (needsUpdatedTexture) {
@ -62,7 +63,7 @@ FLUTTER_ASSERT_ARC
SkRect::Make(_externalImage->bounds()), // source rect
bounds, // destination rect
sampling, // sampling
nullptr, // paint
paint, // paint
SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint // constraint
);
}

View File

@ -37,7 +37,8 @@ class IOSExternalTextureGL final : public Texture {
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) override;
const SkSamplingOptions& sampling,
const SkPaint* paint) override;
// |Texture|
void OnGrContextCreated() override;

View File

@ -144,7 +144,8 @@ void IOSExternalTextureGL::Paint(SkCanvas& canvas,
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) {
const SkSamplingOptions& sampling,
const SkPaint* paint) {
EnsureTextureCacheExists();
if (NeedUpdateTexture(freeze)) {
auto pixelBuffer = [external_texture_.get() copyPixelBuffer];
@ -169,7 +170,7 @@ void IOSExternalTextureGL::Paint(SkCanvas& canvas,
FML_DCHECK(image) << "Failed to create SkImage from Texture.";
if (image) {
canvas.drawImage(image, bounds.x(), bounds.y(), sampling, nullptr);
canvas.drawImage(image, bounds.x(), bounds.y(), sampling, paint);
}
}

View File

@ -30,7 +30,8 @@ class IOSExternalTextureMetal final : public Texture {
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) override;
const SkSamplingOptions& sampling,
const SkPaint* paint) override;
// |Texture|
void OnGrContextCreated() override;

View File

@ -17,12 +17,14 @@ void IOSExternalTextureMetal::Paint(SkCanvas& canvas,
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) {
[darwin_external_texture_metal_ paint:canvas
bounds:bounds
freeze:freeze
grContext:context
sampling:sampling];
const SkSamplingOptions& sampling,
const SkPaint* paint) {
[darwin_external_texture_metal_ canvas:canvas
bounds:bounds
freeze:freeze
grContext:context
sampling:sampling
paint:paint];
}
void IOSExternalTextureMetal::OnGrContextCreated() {

View File

@ -26,7 +26,8 @@ void EmbedderExternalTextureGL::Paint(SkCanvas& canvas,
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) {
const SkSamplingOptions& sampling,
const SkPaint* paint) {
if (last_image_ == nullptr) {
last_image_ =
ResolveTexture(Id(), //
@ -37,9 +38,9 @@ void EmbedderExternalTextureGL::Paint(SkCanvas& canvas,
if (last_image_) {
if (bounds != SkRect::Make(last_image_->bounds())) {
canvas.drawImageRect(last_image_, bounds, sampling);
canvas.drawImageRect(last_image_, bounds, sampling, paint);
} else {
canvas.drawImage(last_image_, bounds.x(), bounds.y(), sampling, nullptr);
canvas.drawImage(last_image_, bounds.x(), bounds.y(), sampling, paint);
}
}
}

View File

@ -36,7 +36,8 @@ class EmbedderExternalTextureGL : public flutter::Texture {
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) override;
const SkSamplingOptions& sampling,
const SkPaint* paint) override;
// |flutter::Texture|
void OnGrContextCreated() override;

View File

@ -36,7 +36,8 @@ class EmbedderExternalTextureMetal : public flutter::Texture {
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) override;
const SkSamplingOptions& sampling,
const SkPaint* paint) override;
// |flutter::Texture|
void OnGrContextCreated() override;

View File

@ -35,16 +35,17 @@ void EmbedderExternalTextureMetal::Paint(SkCanvas& canvas,
const SkRect& bounds,
bool freeze,
GrDirectContext* context,
const SkSamplingOptions& sampling) {
const SkSamplingOptions& sampling,
const SkPaint* paint) {
if (last_image_ == nullptr) {
last_image_ = ResolveTexture(Id(), context, SkISize::Make(bounds.width(), bounds.height()));
}
if (last_image_) {
if (bounds != SkRect::Make(last_image_->bounds())) {
canvas.drawImageRect(last_image_, bounds, sampling);
canvas.drawImageRect(last_image_, bounds, sampling, paint);
} else {
canvas.drawImage(last_image_, bounds.x(), bounds.y(), sampling, nullptr);
canvas.drawImage(last_image_, bounds.x(), bounds.y(), sampling, paint);
}
}
}

View File

@ -301,9 +301,11 @@ void null_platform_messages() {
Picture CreateSimplePicture() {
Paint blackPaint = Paint();
Paint whitePaint = Paint()..color = Color.fromARGB(255, 255, 255, 255);
PictureRecorder baseRecorder = PictureRecorder();
Canvas canvas = Canvas(baseRecorder);
canvas.drawRect(Rect.fromLTRB(0.0, 0.0, 1000.0, 1000.0), blackPaint);
canvas.drawRect(Rect.fromLTRB(10.0, 10.0, 990.0, 990.0), whitePaint);
return baseRecorder.endRecording();
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB