Reland "add non-rendering operation culling to DisplayListBuilder" (#41463) (#42330)
The original PR caused some golden test failures down the line, likely due to bad analysis of when the combined BlendMode and color would result in a NOP situation.
This PR adds tests that go through every BlandMode and pair it with a variety of colors and Color/ImageFilters to verify that the operations are only omitted when they actually produce no change in the output. It also checks the validity of the "modifies_transparent_black" property of DisplayLists which can be used in place of the current CanvasSpy/DlOpSpy classes.
The description from the [previous PR](https://github.com/flutter/engine/pull/41463) updated with the new name of the DL property:
---------------------------------
This optimization avoids recording unnecessary render operations that will not affect the output and also eliminates the need for "draw detection" mechanisms like `DlOpSpy` and `CanvasSpy` by remembering if any non-transparent operations were included. The `DlOpSpy` unit tests were updated to check if the results from that object match the new `DisplayList::modifies_transparent_black()` method.
Fixes https://github.com/flutter/flutter/issues/125338
In addition, this change will unblock some other Issues:
- https://github.com/flutter/flutter/issues/125318
- https://github.com/flutter/flutter/issues/125403
diff --git a/display_list/benchmarking/dl_complexity_unittests.cc b/display_list/benchmarking/dl_complexity_unittests.cc
index 4ec6111..eee77ac 100644
--- a/display_list/benchmarking/dl_complexity_unittests.cc
+++ b/display_list/benchmarking/dl_complexity_unittests.cc
@@ -423,7 +423,7 @@
std::vector<SkRSXform> xforms;
for (int i = 0; i < 10; i++) {
rects.push_back(SkRect::MakeXYWH(0, 0, 10, 10));
- xforms.push_back(SkRSXform::Make(0, 0, 0, 0));
+ xforms.push_back(SkRSXform::Make(1, 0, 0, 0));
}
DisplayListBuilder builder;
diff --git a/display_list/display_list.cc b/display_list/display_list.cc
index 378f868..e06bcf2 100644
--- a/display_list/display_list.cc
+++ b/display_list/display_list.cc
@@ -22,7 +22,8 @@
unique_id_(0),
bounds_({0, 0, 0, 0}),
can_apply_group_opacity_(true),
- is_ui_thread_safe_(true) {}
+ is_ui_thread_safe_(true),
+ modifies_transparent_black_(false) {}
DisplayList::DisplayList(DisplayListStorage&& storage,
size_t byte_count,
@@ -32,6 +33,7 @@
const SkRect& bounds,
bool can_apply_group_opacity,
bool is_ui_thread_safe,
+ bool modifies_transparent_black,
sk_sp<const DlRTree> rtree)
: storage_(std::move(storage)),
byte_count_(byte_count),
@@ -42,6 +44,7 @@
bounds_(bounds),
can_apply_group_opacity_(can_apply_group_opacity),
is_ui_thread_safe_(is_ui_thread_safe),
+ modifies_transparent_black_(modifies_transparent_black),
rtree_(std::move(rtree)) {}
DisplayList::~DisplayList() {
diff --git a/display_list/display_list.h b/display_list/display_list.h
index 4a6f339..668a247 100644
--- a/display_list/display_list.h
+++ b/display_list/display_list.h
@@ -265,6 +265,19 @@
bool can_apply_group_opacity() const { return can_apply_group_opacity_; }
bool isUIThreadSafe() const { return is_ui_thread_safe_; }
+ /// @brief Indicates if there are any rendering operations in this
+ /// DisplayList that will modify a surface of transparent black
+ /// pixels.
+ ///
+ /// This condition can be used to determine whether to create a cleared
+ /// surface, render a DisplayList into it, and then composite the
+ /// result into a scene. It is not uncommon for code in the engine to
+ /// come across such degenerate DisplayList objects when slicing up a
+ /// frame between platform views.
+ bool modifies_transparent_black() const {
+ return modifies_transparent_black_;
+ }
+
private:
DisplayList(DisplayListStorage&& ptr,
size_t byte_count,
@@ -274,6 +287,7 @@
const SkRect& bounds,
bool can_apply_group_opacity,
bool is_ui_thread_safe,
+ bool modifies_transparent_black,
sk_sp<const DlRTree> rtree);
static uint32_t next_unique_id();
@@ -292,6 +306,8 @@
const bool can_apply_group_opacity_;
const bool is_ui_thread_safe_;
+ const bool modifies_transparent_black_;
+
const sk_sp<const DlRTree> rtree_;
void Dispatch(DlOpReceiver& ctx,
diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc
index c757b6d..cf33114 100644
--- a/display_list/display_list_unittests.cc
+++ b/display_list/display_list_unittests.cc
@@ -22,6 +22,7 @@
#include "flutter/testing/testing.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"
+#include "third_party/skia/include/core/SkRSXform.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace flutter {
@@ -2874,5 +2875,164 @@
ASSERT_FALSE(display_list->can_apply_group_opacity());
}
+TEST_F(DisplayListTest, NopOperationsOmittedFromRecords) {
+ auto run_tests = [](const std::string& name,
+ void init(DisplayListBuilder & builder, DlPaint & paint),
+ uint32_t expected_op_count = 0u) {
+ auto run_one_test =
+ [init](const std::string& name,
+ void build(DisplayListBuilder & builder, DlPaint & paint),
+ uint32_t expected_op_count = 0u) {
+ DisplayListBuilder builder;
+ DlPaint paint;
+ init(builder, paint);
+ build(builder, paint);
+ auto list = builder.Build();
+ if (list->op_count() != expected_op_count) {
+ FML_LOG(ERROR) << *list;
+ }
+ ASSERT_EQ(list->op_count(), expected_op_count) << name;
+ ASSERT_TRUE(list->bounds().isEmpty()) << name;
+ };
+ run_one_test(
+ name + " DrawColor",
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ builder.DrawColor(paint.getColor(), paint.getBlendMode());
+ },
+ expected_op_count);
+ run_one_test(
+ name + " DrawPaint",
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ builder.DrawPaint(paint);
+ },
+ expected_op_count);
+ run_one_test(
+ name + " DrawRect",
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ builder.DrawRect({10, 10, 20, 20}, paint);
+ },
+ expected_op_count);
+ run_one_test(
+ name + " Other Draw Ops",
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ builder.DrawLine({10, 10}, {20, 20}, paint);
+ builder.DrawOval({10, 10, 20, 20}, paint);
+ builder.DrawCircle({50, 50}, 20, paint);
+ builder.DrawRRect(SkRRect::MakeRectXY({10, 10, 20, 20}, 5, 5), paint);
+ builder.DrawDRRect(SkRRect::MakeRectXY({5, 5, 100, 100}, 5, 5),
+ SkRRect::MakeRectXY({10, 10, 20, 20}, 5, 5),
+ paint);
+ builder.DrawPath(kTestPath1, paint);
+ builder.DrawArc({10, 10, 20, 20}, 45, 90, true, paint);
+ SkPoint pts[] = {{10, 10}, {20, 20}};
+ builder.DrawPoints(PointMode::kLines, 2, pts, paint);
+ builder.DrawVertices(TestVertices1, DlBlendMode::kSrcOver, paint);
+ builder.DrawImage(TestImage1, {10, 10}, DlImageSampling::kLinear,
+ &paint);
+ builder.DrawImageRect(TestImage1, SkRect{0.0f, 0.0f, 10.0f, 10.0f},
+ SkRect{10.0f, 10.0f, 25.0f, 25.0f},
+ DlImageSampling::kLinear, &paint);
+ builder.DrawImageNine(TestImage1, {10, 10, 20, 20},
+ {10, 10, 100, 100}, DlFilterMode::kLinear,
+ &paint);
+ SkRSXform xforms[] = {{1, 0, 10, 10}, {0, 1, 10, 10}};
+ SkRect rects[] = {{10, 10, 20, 20}, {10, 20, 30, 20}};
+ builder.DrawAtlas(TestImage1, xforms, rects, nullptr, 2,
+ DlBlendMode::kSrcOver, DlImageSampling::kLinear,
+ nullptr, &paint);
+ builder.DrawTextBlob(TestBlob1, 10, 10, paint);
+
+ // Dst mode eliminates most rendering ops except for
+ // the following two, so we'll prune those manually...
+ if (paint.getBlendMode() != DlBlendMode::kDst) {
+ builder.DrawDisplayList(TestDisplayList1, paint.getOpacity());
+ builder.DrawShadow(kTestPath1, paint.getColor(), 1, true, 1);
+ }
+ },
+ expected_op_count);
+ run_one_test(
+ name + " SaveLayer",
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ builder.SaveLayer(nullptr, &paint, nullptr);
+ builder.DrawRect({10, 10, 20, 20}, DlPaint());
+ builder.Restore();
+ },
+ expected_op_count);
+ run_one_test(
+ name + " inside Save",
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ builder.Save();
+ builder.DrawRect({10, 10, 20, 20}, paint);
+ builder.Restore();
+ },
+ expected_op_count);
+ };
+ run_tests("transparent color", //
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ paint.setColor(DlColor::kTransparent());
+ });
+ run_tests("0 alpha", //
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ // The transparent test above already tested transparent
+ // black (all 0s), we set White color here so we can test
+ // the case of all 1s with a 0 alpha
+ paint.setColor(DlColor::kWhite());
+ paint.setAlpha(0);
+ });
+ run_tests("BlendMode::kDst", //
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ paint.setBlendMode(DlBlendMode::kDst);
+ });
+ run_tests("Empty rect clip", //
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ builder.ClipRect(SkRect::MakeEmpty(), ClipOp::kIntersect, false);
+ });
+ run_tests("Empty rrect clip", //
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ builder.ClipRRect(SkRRect::MakeEmpty(), ClipOp::kIntersect,
+ false);
+ });
+ run_tests("Empty path clip", //
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ builder.ClipPath(SkPath(), ClipOp::kIntersect, false);
+ });
+ run_tests("Transparent SaveLayer", //
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ DlPaint save_paint;
+ save_paint.setColor(DlColor::kTransparent());
+ builder.SaveLayer(nullptr, &save_paint);
+ });
+ run_tests("0 alpha SaveLayer", //
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ DlPaint save_paint;
+ // The transparent test above already tested transparent
+ // black (all 0s), we set White color here so we can test
+ // the case of all 1s with a 0 alpha
+ save_paint.setColor(DlColor::kWhite());
+ save_paint.setAlpha(0);
+ builder.SaveLayer(nullptr, &save_paint);
+ });
+ run_tests("Dst blended SaveLayer", //
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ DlPaint save_paint;
+ save_paint.setBlendMode(DlBlendMode::kDst);
+ builder.SaveLayer(nullptr, &save_paint);
+ });
+ run_tests(
+ "Nop inside SaveLayer",
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ builder.SaveLayer(nullptr, nullptr);
+ paint.setBlendMode(DlBlendMode::kDst);
+ },
+ 2u);
+ run_tests("DrawImage inside Culled SaveLayer", //
+ [](DisplayListBuilder& builder, DlPaint& paint) {
+ DlPaint save_paint;
+ save_paint.setColor(DlColor::kTransparent());
+ builder.SaveLayer(nullptr, &save_paint);
+ builder.DrawImage(TestImage1, {10, 10}, DlImageSampling::kLinear);
+ });
+}
+
} // namespace testing
} // namespace flutter
diff --git a/display_list/dl_builder.cc b/display_list/dl_builder.cc
index 6aa6c94..afb1217 100644
--- a/display_list/dl_builder.cc
+++ b/display_list/dl_builder.cc
@@ -6,10 +6,12 @@
#include "flutter/display_list/display_list.h"
#include "flutter/display_list/dl_blend_mode.h"
+#include "flutter/display_list/dl_op_flags.h"
#include "flutter/display_list/dl_op_records.h"
#include "flutter/display_list/effects/dl_color_source.h"
#include "flutter/display_list/utils/dl_bounds_accumulator.h"
#include "fml/logging.h"
+#include "third_party/skia/include/core/SkScalar.h"
namespace flutter {
@@ -70,20 +72,22 @@
int count = render_op_count_;
size_t nested_bytes = nested_bytes_;
int nested_count = nested_op_count_;
- bool compatible = layer_stack_.back().is_group_opacity_compatible();
+ bool compatible = current_layer_->is_group_opacity_compatible();
bool is_safe = is_ui_thread_safe_;
+ bool affects_transparency = current_layer_->affects_transparent_layer();
used_ = allocated_ = render_op_count_ = op_index_ = 0;
nested_bytes_ = nested_op_count_ = 0;
+ is_ui_thread_safe_ = true;
storage_.realloc(bytes);
layer_stack_.pop_back();
layer_stack_.emplace_back();
tracker_.reset();
current_ = DlPaint();
- return sk_sp<DisplayList>(
- new DisplayList(std::move(storage_), bytes, count, nested_bytes,
- nested_count, bounds(), compatible, is_safe, rtree()));
+ return sk_sp<DisplayList>(new DisplayList(
+ std::move(storage_), bytes, count, nested_bytes, nested_count, bounds(),
+ compatible, is_safe, affects_transparency, rtree()));
}
DisplayListBuilder::DisplayListBuilder(const SkRect& cull_rect,
@@ -389,9 +393,11 @@
}
void DisplayListBuilder::Save() {
+ bool is_nop = current_layer_->is_nop_;
layer_stack_.emplace_back();
current_layer_ = &layer_stack_.back();
current_layer_->has_deferred_save_op_ = true;
+ current_layer_->is_nop_ = is_nop;
tracker_.save();
accumulator()->save();
}
@@ -478,18 +484,16 @@
const SaveLayerOptions in_options,
const DlImageFilter* backdrop) {
SaveLayerOptions options = in_options.without_optimizations();
- size_t save_layer_offset = used_;
- if (backdrop) {
- bounds //
- ? Push<SaveLayerBackdropBoundsOp>(0, 1, options, *bounds, backdrop)
- : Push<SaveLayerBackdropOp>(0, 1, options, backdrop);
- } else {
- bounds //
- ? Push<SaveLayerBoundsOp>(0, 1, options, *bounds)
- : Push<SaveLayerOp>(0, 1, options);
+ DisplayListAttributeFlags flags = options.renders_with_attributes()
+ ? kSaveLayerWithPaintFlags
+ : kSaveLayerFlags;
+ OpResult result = PaintResult(current_, flags);
+ if (result == OpResult::kNoEffect) {
+ save();
+ current_layer_->is_nop_ = true;
+ return;
}
- CheckLayerOpacityCompatibility(options.renders_with_attributes());
-
+ size_t save_layer_offset = used_;
if (options.renders_with_attributes()) {
// The actual flood of the outer layer clip will occur after the
// (eventual) corresponding restore is called, but rather than
@@ -500,17 +504,41 @@
// with its full bounds and the right op_index so that it doesn't
// get culled during rendering.
if (!paint_nops_on_transparency()) {
- // We will fill the clip of the outer layer when we restore
- AccumulateUnbounded();
+ // We will fill the clip of the outer layer when we restore.
+ // Accumulate should always return true here because if the
+ // clip was empty then that would have been caught up above
+ // when we tested the PaintResult.
+ [[maybe_unused]] bool unclipped = AccumulateUnbounded();
+ FML_DCHECK(unclipped);
}
+ CheckLayerOpacityCompatibility(true);
layer_stack_.emplace_back(save_layer_offset, true,
current_.getImageFilter());
} else {
+ CheckLayerOpacityCompatibility(false);
layer_stack_.emplace_back(save_layer_offset, true, nullptr);
}
+ current_layer_ = &layer_stack_.back();
+
tracker_.save();
accumulator()->save();
- current_layer_ = &layer_stack_.back();
+
+ if (backdrop) {
+ // A backdrop will affect up to the entire surface, bounded by the clip
+ // Accumulate should always return true here because if the
+ // clip was empty then that would have been caught up above
+ // when we tested the PaintResult.
+ [[maybe_unused]] bool unclipped = AccumulateUnbounded();
+ FML_DCHECK(unclipped);
+ bounds //
+ ? Push<SaveLayerBackdropBoundsOp>(0, 1, options, *bounds, backdrop)
+ : Push<SaveLayerBackdropOp>(0, 1, options, backdrop);
+ } else {
+ bounds //
+ ? Push<SaveLayerBoundsOp>(0, 1, options, *bounds)
+ : Push<SaveLayerOp>(0, 1, options);
+ }
+
if (options.renders_with_attributes()) {
// |current_opacity_compatibility_| does not take an ImageFilter into
// account because an individual primitive with an ImageFilter can apply
@@ -521,6 +549,7 @@
UpdateLayerOpacityCompatibility(false);
}
}
+ UpdateLayerResult(result);
// Even though Skia claims that the bounds are only a hint, they actually
// use them as the temporary layer bounds during rendering the layer, so
@@ -528,10 +557,6 @@
if (bounds) {
tracker_.clipRect(*bounds, ClipOp::kIntersect, false);
}
- if (backdrop) {
- // A backdrop will affect up to the entire surface, bounded by the clip
- AccumulateUnbounded();
- }
}
void DisplayListBuilder::SaveLayer(const SkRect* bounds,
const DlPaint* paint,
@@ -654,6 +679,11 @@
if (!rect.isFinite()) {
return;
}
+ tracker_.clipRect(rect, clip_op, is_aa);
+ if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) {
+ current_layer_->is_nop_ = true;
+ return;
+ }
checkForDeferredSave();
switch (clip_op) {
case ClipOp::kIntersect:
@@ -663,7 +693,6 @@
Push<ClipDifferenceRectOp>(0, 1, rect, is_aa);
break;
}
- tracker_.clipRect(rect, clip_op, is_aa);
}
void DisplayListBuilder::ClipRRect(const SkRRect& rrect,
ClipOp clip_op,
@@ -671,6 +700,11 @@
if (rrect.isRect()) {
clipRect(rrect.rect(), clip_op, is_aa);
} else {
+ tracker_.clipRRect(rrect, clip_op, is_aa);
+ if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) {
+ current_layer_->is_nop_ = true;
+ return;
+ }
checkForDeferredSave();
switch (clip_op) {
case ClipOp::kIntersect:
@@ -680,7 +714,6 @@
Push<ClipDifferenceRRectOp>(0, 1, rrect, is_aa);
break;
}
- tracker_.clipRRect(rrect, clip_op, is_aa);
}
}
void DisplayListBuilder::ClipPath(const SkPath& path,
@@ -703,6 +736,11 @@
return;
}
}
+ tracker_.clipPath(path, clip_op, is_aa);
+ if (current_layer_->is_nop_ || tracker_.is_cull_rect_empty()) {
+ current_layer_->is_nop_ = true;
+ return;
+ }
checkForDeferredSave();
switch (clip_op) {
case ClipOp::kIntersect:
@@ -712,7 +750,6 @@
Push<ClipDifferencePathOp>(0, 1, path, is_aa);
break;
}
- tracker_.clipPath(path, clip_op, is_aa);
}
bool DisplayListBuilder::QuickReject(const SkRect& bounds) const {
@@ -720,27 +757,36 @@
}
void DisplayListBuilder::drawPaint() {
- Push<DrawPaintOp>(0, 1);
- CheckLayerOpacityCompatibility();
- AccumulateUnbounded();
+ OpResult result = PaintResult(current_, kDrawPaintFlags);
+ if (result != OpResult::kNoEffect && AccumulateUnbounded()) {
+ Push<DrawPaintOp>(0, 1);
+ CheckLayerOpacityCompatibility();
+ UpdateLayerResult(result);
+ }
}
void DisplayListBuilder::DrawPaint(const DlPaint& paint) {
SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawPaintFlags);
drawPaint();
}
void DisplayListBuilder::DrawColor(DlColor color, DlBlendMode mode) {
- Push<DrawColorOp>(0, 1, color, mode);
- CheckLayerOpacityCompatibility(mode);
- AccumulateUnbounded();
+ OpResult result = PaintResult(DlPaint(color).setBlendMode(mode));
+ if (result != OpResult::kNoEffect && AccumulateUnbounded()) {
+ Push<DrawColorOp>(0, 1, color, mode);
+ CheckLayerOpacityCompatibility(mode);
+ UpdateLayerResult(result);
+ }
}
void DisplayListBuilder::drawLine(const SkPoint& p0, const SkPoint& p1) {
- Push<DrawLineOp>(0, 1, p0, p1);
- CheckLayerOpacityCompatibility();
SkRect bounds = SkRect::MakeLTRB(p0.fX, p0.fY, p1.fX, p1.fY).makeSorted();
DisplayListAttributeFlags flags =
(bounds.width() > 0.0f && bounds.height() > 0.0f) ? kDrawLineFlags
: kDrawHVLineFlags;
- AccumulateOpBounds(bounds, flags);
+ OpResult result = PaintResult(current_, flags);
+ if (result != OpResult::kNoEffect && AccumulateOpBounds(bounds, flags)) {
+ Push<DrawLineOp>(0, 1, p0, p1);
+ CheckLayerOpacityCompatibility();
+ UpdateLayerResult(result);
+ }
}
void DisplayListBuilder::DrawLine(const SkPoint& p0,
const SkPoint& p1,
@@ -749,29 +795,43 @@
drawLine(p0, p1);
}
void DisplayListBuilder::drawRect(const SkRect& rect) {
- Push<DrawRectOp>(0, 1, rect);
- CheckLayerOpacityCompatibility();
- AccumulateOpBounds(rect, kDrawRectFlags);
+ DisplayListAttributeFlags flags = kDrawRectFlags;
+ OpResult result = PaintResult(current_, flags);
+ if (result != OpResult::kNoEffect && AccumulateOpBounds(rect, flags)) {
+ Push<DrawRectOp>(0, 1, rect);
+ CheckLayerOpacityCompatibility();
+ UpdateLayerResult(result);
+ }
}
void DisplayListBuilder::DrawRect(const SkRect& rect, const DlPaint& paint) {
SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawRectFlags);
drawRect(rect);
}
void DisplayListBuilder::drawOval(const SkRect& bounds) {
- Push<DrawOvalOp>(0, 1, bounds);
- CheckLayerOpacityCompatibility();
- AccumulateOpBounds(bounds, kDrawOvalFlags);
+ DisplayListAttributeFlags flags = kDrawOvalFlags;
+ OpResult result = PaintResult(current_, flags);
+ if (result != OpResult::kNoEffect && AccumulateOpBounds(bounds, flags)) {
+ Push<DrawOvalOp>(0, 1, bounds);
+ CheckLayerOpacityCompatibility();
+ UpdateLayerResult(result);
+ }
}
void DisplayListBuilder::DrawOval(const SkRect& bounds, const DlPaint& paint) {
SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawOvalFlags);
drawOval(bounds);
}
void DisplayListBuilder::drawCircle(const SkPoint& center, SkScalar radius) {
- Push<DrawCircleOp>(0, 1, center, radius);
- CheckLayerOpacityCompatibility();
- AccumulateOpBounds(SkRect::MakeLTRB(center.fX - radius, center.fY - radius,
- center.fX + radius, center.fY + radius),
- kDrawCircleFlags);
+ DisplayListAttributeFlags flags = kDrawCircleFlags;
+ OpResult result = PaintResult(current_, flags);
+ if (result != OpResult::kNoEffect) {
+ SkRect bounds = SkRect::MakeLTRB(center.fX - radius, center.fY - radius,
+ center.fX + radius, center.fY + radius);
+ if (AccumulateOpBounds(bounds, flags)) {
+ Push<DrawCircleOp>(0, 1, center, radius);
+ CheckLayerOpacityCompatibility();
+ UpdateLayerResult(result);
+ }
+ }
}
void DisplayListBuilder::DrawCircle(const SkPoint& center,
SkScalar radius,
@@ -785,9 +845,14 @@
} else if (rrect.isOval()) {
drawOval(rrect.rect());
} else {
- Push<DrawRRectOp>(0, 1, rrect);
- CheckLayerOpacityCompatibility();
- AccumulateOpBounds(rrect.getBounds(), kDrawRRectFlags);
+ DisplayListAttributeFlags flags = kDrawRRectFlags;
+ OpResult result = PaintResult(current_, flags);
+ if (result != OpResult::kNoEffect &&
+ AccumulateOpBounds(rrect.getBounds(), flags)) {
+ Push<DrawRRectOp>(0, 1, rrect);
+ CheckLayerOpacityCompatibility();
+ UpdateLayerResult(result);
+ }
}
}
void DisplayListBuilder::DrawRRect(const SkRRect& rrect, const DlPaint& paint) {
@@ -796,9 +861,14 @@
}
void DisplayListBuilder::drawDRRect(const SkRRect& outer,
const SkRRect& inner) {
- Push<DrawDRRectOp>(0, 1, outer, inner);
- CheckLayerOpacityCompatibility();
- AccumulateOpBounds(outer.getBounds(), kDrawDRRectFlags);
+ DisplayListAttributeFlags flags = kDrawDRRectFlags;
+ OpResult result = PaintResult(current_, flags);
+ if (result != OpResult::kNoEffect &&
+ AccumulateOpBounds(outer.getBounds(), flags)) {
+ Push<DrawDRRectOp>(0, 1, outer, inner);
+ CheckLayerOpacityCompatibility();
+ UpdateLayerResult(result);
+ }
}
void DisplayListBuilder::DrawDRRect(const SkRRect& outer,
const SkRRect& inner,
@@ -807,12 +877,17 @@
drawDRRect(outer, inner);
}
void DisplayListBuilder::drawPath(const SkPath& path) {
- Push<DrawPathOp>(0, 1, path);
- CheckLayerOpacityHairlineCompatibility();
- if (path.isInverseFillType()) {
- AccumulateUnbounded();
- } else {
- AccumulateOpBounds(path.getBounds(), kDrawPathFlags);
+ DisplayListAttributeFlags flags = kDrawPathFlags;
+ OpResult result = PaintResult(current_, flags);
+ if (result != OpResult::kNoEffect) {
+ bool is_visible = path.isInverseFillType()
+ ? AccumulateUnbounded()
+ : AccumulateOpBounds(path.getBounds(), flags);
+ if (is_visible) {
+ Push<DrawPathOp>(0, 1, path);
+ CheckLayerOpacityHairlineCompatibility();
+ UpdateLayerResult(result);
+ }
}
}
void DisplayListBuilder::DrawPath(const SkPath& path, const DlPaint& paint) {
@@ -824,19 +899,23 @@
SkScalar start,
SkScalar sweep,
bool useCenter) {
- Push<DrawArcOp>(0, 1, bounds, start, sweep, useCenter);
- if (useCenter) {
- CheckLayerOpacityHairlineCompatibility();
- } else {
- CheckLayerOpacityCompatibility();
- }
+ DisplayListAttributeFlags flags = //
+ useCenter //
+ ? kDrawArcWithCenterFlags
+ : kDrawArcNoCenterFlags;
+ OpResult result = PaintResult(current_, flags);
// This could be tighter if we compute where the start and end
// angles are and then also consider the quadrants swept and
// the center if specified.
- AccumulateOpBounds(bounds,
- useCenter //
- ? kDrawArcWithCenterFlags
- : kDrawArcNoCenterFlags);
+ if (result != OpResult::kNoEffect && AccumulateOpBounds(bounds, flags)) {
+ Push<DrawArcOp>(0, 1, bounds, start, sweep, useCenter);
+ if (useCenter) {
+ CheckLayerOpacityHairlineCompatibility();
+ } else {
+ CheckLayerOpacityCompatibility();
+ }
+ UpdateLayerResult(result);
+ }
}
void DisplayListBuilder::DrawArc(const SkRect& bounds,
SkScalar start,
@@ -847,14 +926,31 @@
paint, useCenter ? kDrawArcWithCenterFlags : kDrawArcNoCenterFlags);
drawArc(bounds, start, sweep, useCenter);
}
+
+DisplayListAttributeFlags DisplayListBuilder::FlagsForPointMode(
+ PointMode mode) {
+ switch (mode) {
+ case DlCanvas::PointMode::kPoints:
+ return kDrawPointsAsPointsFlags;
+ case PointMode::kLines:
+ return kDrawPointsAsLinesFlags;
+ case PointMode::kPolygon:
+ return kDrawPointsAsPolygonFlags;
+ }
+ FML_UNREACHABLE();
+}
void DisplayListBuilder::drawPoints(PointMode mode,
uint32_t count,
const SkPoint pts[]) {
if (count == 0) {
return;
}
+ DisplayListAttributeFlags flags = FlagsForPointMode(mode);
+ OpResult result = PaintResult(current_, flags);
+ if (result == OpResult::kNoEffect) {
+ return;
+ }
- void* data_ptr;
FML_DCHECK(count < DlOpReceiver::kMaxDrawPointsCount);
int bytes = count * sizeof(SkPoint);
RectBoundsAccumulator ptBounds;
@@ -862,21 +958,23 @@
ptBounds.accumulate(pts[i]);
}
SkRect point_bounds = ptBounds.bounds();
+ if (!AccumulateOpBounds(point_bounds, flags)) {
+ return;
+ }
+
+ void* data_ptr;
switch (mode) {
case PointMode::kPoints:
data_ptr = Push<DrawPointsOp>(bytes, 1, count);
- AccumulateOpBounds(point_bounds, kDrawPointsAsPointsFlags);
break;
case PointMode::kLines:
data_ptr = Push<DrawLinesOp>(bytes, 1, count);
- AccumulateOpBounds(point_bounds, kDrawPointsAsLinesFlags);
break;
case PointMode::kPolygon:
data_ptr = Push<DrawPolygonOp>(bytes, 1, count);
- AccumulateOpBounds(point_bounds, kDrawPointsAsPolygonFlags);
break;
default:
- FML_DCHECK(false);
+ FML_UNREACHABLE();
return;
}
CopyV(data_ptr, pts, count);
@@ -886,39 +984,30 @@
// bounds of every sub-primitive.
// See: https://fiddle.skia.org/c/228459001d2de8db117ce25ef5cedb0c
UpdateLayerOpacityCompatibility(false);
+ UpdateLayerResult(result);
}
void DisplayListBuilder::DrawPoints(PointMode mode,
uint32_t count,
const SkPoint pts[],
const DlPaint& paint) {
- const DisplayListAttributeFlags* flags;
- switch (mode) {
- case PointMode::kPoints:
- flags = &DisplayListOpFlags::kDrawPointsAsPointsFlags;
- break;
- case PointMode::kLines:
- flags = &DisplayListOpFlags::kDrawPointsAsLinesFlags;
- break;
- case PointMode::kPolygon:
- flags = &DisplayListOpFlags::kDrawPointsAsPolygonFlags;
- break;
- default:
- FML_DCHECK(false);
- return;
- }
- SetAttributesFromPaint(paint, *flags);
+ SetAttributesFromPaint(paint, FlagsForPointMode(mode));
drawPoints(mode, count, pts);
}
void DisplayListBuilder::drawVertices(const DlVertices* vertices,
DlBlendMode mode) {
- void* pod = Push<DrawVerticesOp>(vertices->size(), 1, mode);
- new (pod) DlVertices(vertices);
- // DrawVertices applies its colors to the paint so we have no way
- // of controlling opacity using the current paint attributes.
- // Although, examination of the |mode| might find some predictable
- // cases.
- UpdateLayerOpacityCompatibility(false);
- AccumulateOpBounds(vertices->bounds(), kDrawVerticesFlags);
+ DisplayListAttributeFlags flags = kDrawVerticesFlags;
+ OpResult result = PaintResult(current_, flags);
+ if (result != OpResult::kNoEffect &&
+ AccumulateOpBounds(vertices->bounds(), flags)) {
+ void* pod = Push<DrawVerticesOp>(vertices->size(), 1, mode);
+ new (pod) DlVertices(vertices);
+ // DrawVertices applies its colors to the paint so we have no way
+ // of controlling opacity using the current paint attributes.
+ // Although, examination of the |mode| might find some predictable
+ // cases.
+ UpdateLayerOpacityCompatibility(false);
+ UpdateLayerResult(result);
+ }
}
void DisplayListBuilder::DrawVertices(const DlVertices* vertices,
DlBlendMode mode,
@@ -931,17 +1020,23 @@
const SkPoint point,
DlImageSampling sampling,
bool render_with_attributes) {
- render_with_attributes
- ? Push<DrawImageWithAttrOp>(0, 1, image, point, sampling)
- : Push<DrawImageOp>(0, 1, image, point, sampling);
- CheckLayerOpacityCompatibility(render_with_attributes);
- is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe();
- SkRect bounds = SkRect::MakeXYWH(point.fX, point.fY, //
- image->width(), image->height());
DisplayListAttributeFlags flags = render_with_attributes //
? kDrawImageWithPaintFlags
: kDrawImageFlags;
- AccumulateOpBounds(bounds, flags);
+ OpResult result = PaintResult(current_, flags);
+ if (result == OpResult::kNoEffect) {
+ return;
+ }
+ SkRect bounds = SkRect::MakeXYWH(point.fX, point.fY, //
+ image->width(), image->height());
+ if (AccumulateOpBounds(bounds, flags)) {
+ render_with_attributes
+ ? Push<DrawImageWithAttrOp>(0, 1, image, point, sampling)
+ : Push<DrawImageOp>(0, 1, image, point, sampling);
+ CheckLayerOpacityCompatibility(render_with_attributes);
+ UpdateLayerResult(result);
+ is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe();
+ }
}
void DisplayListBuilder::DrawImage(const sk_sp<DlImage>& image,
const SkPoint point,
@@ -961,14 +1056,17 @@
DlImageSampling sampling,
bool render_with_attributes,
SrcRectConstraint constraint) {
- Push<DrawImageRectOp>(0, 1, image, src, dst, sampling, render_with_attributes,
- constraint);
- CheckLayerOpacityCompatibility(render_with_attributes);
- is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe();
DisplayListAttributeFlags flags = render_with_attributes
? kDrawImageRectWithPaintFlags
: kDrawImageRectFlags;
- AccumulateOpBounds(dst, flags);
+ OpResult result = PaintResult(current_, flags);
+ if (result != OpResult::kNoEffect && AccumulateOpBounds(dst, flags)) {
+ Push<DrawImageRectOp>(0, 1, image, src, dst, sampling,
+ render_with_attributes, constraint);
+ CheckLayerOpacityCompatibility(render_with_attributes);
+ UpdateLayerResult(result);
+ is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe();
+ }
}
void DisplayListBuilder::DrawImageRect(const sk_sp<DlImage>& image,
const SkRect& src,
@@ -989,15 +1087,18 @@
const SkRect& dst,
DlFilterMode filter,
bool render_with_attributes) {
- render_with_attributes
- ? Push<DrawImageNineWithAttrOp>(0, 1, image, center, dst, filter)
- : Push<DrawImageNineOp>(0, 1, image, center, dst, filter);
- CheckLayerOpacityCompatibility(render_with_attributes);
- is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe();
DisplayListAttributeFlags flags = render_with_attributes
? kDrawImageNineWithPaintFlags
: kDrawImageNineFlags;
- AccumulateOpBounds(dst, flags);
+ OpResult result = PaintResult(current_, flags);
+ if (result != OpResult::kNoEffect && AccumulateOpBounds(dst, flags)) {
+ render_with_attributes
+ ? Push<DrawImageNineWithAttrOp>(0, 1, image, center, dst, filter)
+ : Push<DrawImageNineOp>(0, 1, image, center, dst, filter);
+ CheckLayerOpacityCompatibility(render_with_attributes);
+ UpdateLayerResult(result);
+ is_ui_thread_safe_ = is_ui_thread_safe_ && image->isUIThreadSafe();
+ }
}
void DisplayListBuilder::DrawImageNine(const sk_sp<DlImage>& image,
const SkIRect& center,
@@ -1021,6 +1122,27 @@
DlImageSampling sampling,
const SkRect* cull_rect,
bool render_with_attributes) {
+ DisplayListAttributeFlags flags = render_with_attributes //
+ ? kDrawAtlasWithPaintFlags
+ : kDrawAtlasFlags;
+ OpResult result = PaintResult(current_, flags);
+ if (result == OpResult::kNoEffect) {
+ return;
+ }
+ SkPoint quad[4];
+ RectBoundsAccumulator atlasBounds;
+ for (int i = 0; i < count; i++) {
+ const SkRect& src = tex[i];
+ xform[i].toQuad(src.width(), src.height(), quad);
+ for (int j = 0; j < 4; j++) {
+ atlasBounds.accumulate(quad[j]);
+ }
+ }
+ if (atlasBounds.is_empty() ||
+ !AccumulateOpBounds(atlasBounds.bounds(), flags)) {
+ return;
+ }
+
int bytes = count * (sizeof(SkRSXform) + sizeof(SkRect));
void* data_ptr;
if (colors != nullptr) {
@@ -1049,23 +1171,8 @@
// on it to distribute the opacity without overlap without checking all
// of the transforms and texture rectangles.
UpdateLayerOpacityCompatibility(false);
+ UpdateLayerResult(result);
is_ui_thread_safe_ = is_ui_thread_safe_ && atlas->isUIThreadSafe();
-
- SkPoint quad[4];
- RectBoundsAccumulator atlasBounds;
- for (int i = 0; i < count; i++) {
- const SkRect& src = tex[i];
- xform[i].toQuad(src.width(), src.height(), quad);
- for (int j = 0; j < 4; j++) {
- atlasBounds.accumulate(quad[j]);
- }
- }
- if (atlasBounds.is_not_empty()) {
- DisplayListAttributeFlags flags = render_with_attributes //
- ? kDrawAtlasWithPaintFlags
- : kDrawAtlasFlags;
- AccumulateOpBounds(atlasBounds.bounds(), flags);
- }
}
void DisplayListBuilder::DrawAtlas(const sk_sp<DlImage>& atlas,
const SkRSXform xform[],
@@ -1089,8 +1196,41 @@
void DisplayListBuilder::DrawDisplayList(const sk_sp<DisplayList> display_list,
SkScalar opacity) {
+ if (!SkScalarIsFinite(opacity) || opacity <= SK_ScalarNearlyZero ||
+ display_list->op_count() == 0 || display_list->bounds().isEmpty() ||
+ current_layer_->is_nop_) {
+ return;
+ }
+ const SkRect bounds = display_list->bounds();
+ bool accumulated;
+ switch (accumulator()->type()) {
+ case BoundsAccumulatorType::kRect:
+ accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags);
+ break;
+ case BoundsAccumulatorType::kRTree:
+ auto rtree = display_list->rtree();
+ if (rtree) {
+ std::list<SkRect> rects = rtree->searchAndConsolidateRects(bounds);
+ accumulated = false;
+ for (const SkRect& rect : rects) {
+ // TODO (https://github.com/flutter/flutter/issues/114919): Attributes
+ // are not necessarily `kDrawDisplayListFlags`.
+ if (AccumulateOpBounds(rect, kDrawDisplayListFlags)) {
+ accumulated = true;
+ }
+ }
+ } else {
+ accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags);
+ }
+ break;
+ }
+ if (!accumulated) {
+ return;
+ }
+
DlPaint current_paint = current_;
- Push<DrawDisplayListOp>(0, 1, display_list, opacity);
+ Push<DrawDisplayListOp>(0, 1, display_list,
+ opacity < SK_Scalar1 ? opacity : SK_Scalar1);
is_ui_thread_safe_ = is_ui_thread_safe_ && display_list->isUIThreadSafe();
// Not really necessary if the developer is interacting with us via
// our attribute-state-less DlCanvas methods, but this avoids surprises
@@ -1098,25 +1238,6 @@
SetAttributesFromPaint(current_paint,
DisplayListOpFlags::kSaveLayerWithPaintFlags);
- const SkRect bounds = display_list->bounds();
- switch (accumulator()->type()) {
- case BoundsAccumulatorType::kRect:
- AccumulateOpBounds(bounds, kDrawDisplayListFlags);
- break;
- case BoundsAccumulatorType::kRTree:
- auto rtree = display_list->rtree();
- if (rtree) {
- std::list<SkRect> rects = rtree->searchAndConsolidateRects(bounds);
- for (const SkRect& rect : rects) {
- // TODO (https://github.com/flutter/flutter/issues/114919): Attributes
- // are not necessarily `kDrawDisplayListFlags`.
- AccumulateOpBounds(rect, kDrawDisplayListFlags);
- }
- } else {
- AccumulateOpBounds(bounds, kDrawDisplayListFlags);
- }
- break;
- }
// The non-nested op count accumulated in the |Push| method will include
// this call to |drawDisplayList| for non-nested op count metrics.
// But, for nested op count metrics we want the |drawDisplayList| call itself
@@ -1126,18 +1247,38 @@
nested_op_count_ += display_list->op_count(true) - 1;
nested_bytes_ += display_list->bytes(true);
UpdateLayerOpacityCompatibility(display_list->can_apply_group_opacity());
+ // Nop DisplayLists are eliminated above so we either affect transparent
+ // pixels or we do not. We should not have [kNoEffect].
+ UpdateLayerResult(display_list->modifies_transparent_black()
+ ? OpResult::kAffectsAll
+ : OpResult::kPreservesTransparency);
}
void DisplayListBuilder::drawTextBlob(const sk_sp<SkTextBlob> blob,
SkScalar x,
SkScalar y) {
- Push<DrawTextBlobOp>(0, 1, blob, x, y);
- AccumulateOpBounds(blob->bounds().makeOffset(x, y), kDrawTextBlobFlags);
- // There is no way to query if the glyphs of a text blob overlap and
- // there are no current guarantees from either Skia or Impeller that
- // they will protect overlapping glyphs from the effects of overdraw
- // so we must make the conservative assessment that this DL layer is
- // not compatible with group opacity inheritance.
- UpdateLayerOpacityCompatibility(false);
+ DisplayListAttributeFlags flags = kDrawTextBlobFlags;
+ OpResult result = PaintResult(current_, flags);
+ if (result == OpResult::kNoEffect) {
+ return;
+ }
+ bool unclipped = AccumulateOpBounds(blob->bounds().makeOffset(x, y), flags);
+ // TODO(https://github.com/flutter/flutter/issues/82202): Remove once the
+ // unit tests can use Fuchsia's font manager instead of the empty default.
+ // Until then we might encounter empty bounds for otherwise valid text and
+ // thus we ignore the results from AccumulateOpBounds.
+#if defined(OS_FUCHSIA)
+ unclipped = true;
+#endif // OS_FUCHSIA
+ if (unclipped) {
+ Push<DrawTextBlobOp>(0, 1, blob, x, y);
+ // There is no way to query if the glyphs of a text blob overlap and
+ // there are no current guarantees from either Skia or Impeller that
+ // they will protect overlapping glyphs from the effects of overdraw
+ // so we must make the conservative assessment that this DL layer is
+ // not compatible with group opacity inheritance.
+ UpdateLayerOpacityCompatibility(false);
+ UpdateLayerResult(result);
+ }
}
void DisplayListBuilder::DrawTextBlob(const sk_sp<SkTextBlob>& blob,
SkScalar x,
@@ -1151,14 +1292,19 @@
const SkScalar elevation,
bool transparent_occluder,
SkScalar dpr) {
- transparent_occluder //
- ? Push<DrawShadowTransparentOccluderOp>(0, 1, path, color, elevation, dpr)
- : Push<DrawShadowOp>(0, 1, path, color, elevation, dpr);
-
- SkRect shadow_bounds =
- DlCanvas::ComputeShadowBounds(path, elevation, dpr, GetTransform());
- AccumulateOpBounds(shadow_bounds, kDrawShadowFlags);
- UpdateLayerOpacityCompatibility(false);
+ OpResult result = PaintResult(DlPaint(color));
+ if (result != OpResult::kNoEffect) {
+ SkRect shadow_bounds =
+ DlCanvas::ComputeShadowBounds(path, elevation, dpr, GetTransform());
+ if (AccumulateOpBounds(shadow_bounds, kDrawShadowFlags)) {
+ transparent_occluder //
+ ? Push<DrawShadowTransparentOccluderOp>(0, 1, path, color, elevation,
+ dpr)
+ : Push<DrawShadowOp>(0, 1, path, color, elevation, dpr);
+ UpdateLayerOpacityCompatibility(false);
+ UpdateLayerResult(result);
+ }
+ }
}
bool DisplayListBuilder::ComputeFilteredBounds(SkRect& bounds,
@@ -1228,31 +1374,40 @@
return true;
}
-void DisplayListBuilder::AccumulateUnbounded() {
- accumulator()->accumulate(tracker_.device_cull_rect(), op_index_ - 1);
+bool DisplayListBuilder::AccumulateUnbounded() {
+ SkRect clip = tracker_.device_cull_rect();
+ if (clip.isEmpty()) {
+ return false;
+ }
+ accumulator()->accumulate(clip, op_index_);
+ return true;
}
-void DisplayListBuilder::AccumulateOpBounds(SkRect& bounds,
+bool DisplayListBuilder::AccumulateOpBounds(SkRect& bounds,
DisplayListAttributeFlags flags) {
if (AdjustBoundsForPaint(bounds, flags)) {
- AccumulateBounds(bounds);
+ return AccumulateBounds(bounds);
} else {
- AccumulateUnbounded();
+ return AccumulateUnbounded();
}
}
-void DisplayListBuilder::AccumulateBounds(SkRect& bounds) {
- tracker_.mapRect(&bounds);
- if (bounds.intersect(tracker_.device_cull_rect())) {
- accumulator()->accumulate(bounds, op_index_ - 1);
+bool DisplayListBuilder::AccumulateBounds(SkRect& bounds) {
+ if (!bounds.isEmpty()) {
+ tracker_.mapRect(&bounds);
+ if (bounds.intersect(tracker_.device_cull_rect())) {
+ accumulator()->accumulate(bounds, op_index_);
+ return true;
+ }
}
+ return false;
}
bool DisplayListBuilder::paint_nops_on_transparency() {
// SkImageFilter::canComputeFastBounds tests for transparency behavior
// This test assumes that the blend mode checked down below will
// NOP on transparent black.
- if (current_.getImageFilter() &&
- current_.getImageFilter()->modifies_transparent_black()) {
+ if (current_.getImageFilterPtr() &&
+ current_.getImageFilterPtr()->modifies_transparent_black()) {
return false;
}
@@ -1262,8 +1417,8 @@
// save layer untouched out to the edge of the output surface.
// This test assumes that the blend mode checked down below will
// NOP on transparent black.
- if (current_.getColorFilter() &&
- current_.getColorFilter()->modifies_transparent_black()) {
+ if (current_.getColorFilterPtr() &&
+ current_.getColorFilterPtr()->modifies_transparent_black()) {
return false;
}
@@ -1320,4 +1475,130 @@
break;
}
}
+
+DlColor DisplayListBuilder::GetEffectiveColor(const DlPaint& paint,
+ DisplayListAttributeFlags flags) {
+ DlColor color;
+ if (flags.applies_color()) {
+ const DlColorSource* source = paint.getColorSourcePtr();
+ if (source) {
+ if (source->asColor()) {
+ color = source->asColor()->color();
+ } else {
+ color = source->is_opaque() ? DlColor::kBlack() : kAnyColor;
+ }
+ } else {
+ color = paint.getColor();
+ }
+ } else if (flags.applies_alpha()) {
+ // If the operation applies alpha, but not color, then the only impact
+ // of the alpha is to modulate the output towards transparency.
+ // We can not guarantee an opaque source even if the alpha is opaque
+ // since that would require knowing something about the colors that
+ // the alpha is modulating, but we can guarantee a transparent source
+ // if the alpha is 0.
+ color = (paint.getAlpha() == 0) ? DlColor::kTransparent() : kAnyColor;
+ } else {
+ color = kAnyColor;
+ }
+ if (flags.applies_image_filter()) {
+ auto filter = paint.getImageFilterPtr();
+ if (filter) {
+ if (!color.isTransparent() || filter->modifies_transparent_black()) {
+ color = kAnyColor;
+ }
+ }
+ }
+ if (flags.applies_color_filter()) {
+ auto filter = paint.getColorFilterPtr();
+ if (filter) {
+ if (!color.isTransparent() || filter->modifies_transparent_black()) {
+ color = kAnyColor;
+ }
+ }
+ }
+ return color;
+}
+
+DisplayListBuilder::OpResult DisplayListBuilder::PaintResult(
+ const DlPaint& paint,
+ DisplayListAttributeFlags flags) {
+ if (current_layer_->is_nop_) {
+ return OpResult::kNoEffect;
+ }
+ if (flags.applies_blend()) {
+ switch (paint.getBlendMode()) {
+ // Nop blend mode (singular, there is only one)
+ case DlBlendMode::kDst:
+ return OpResult::kNoEffect;
+
+ // Always clears pixels blend mode (singular, there is only one)
+ case DlBlendMode::kClear:
+ return OpResult::kPreservesTransparency;
+
+ case DlBlendMode::kHue:
+ case DlBlendMode::kSaturation:
+ case DlBlendMode::kColor:
+ case DlBlendMode::kLuminosity:
+ case DlBlendMode::kColorBurn:
+ return GetEffectiveColor(paint, flags).isTransparent()
+ ? OpResult::kNoEffect
+ : OpResult::kAffectsAll;
+
+ // kSrcIn modifies pixels towards transparency
+ case DlBlendMode::kSrcIn:
+ return OpResult::kPreservesTransparency;
+
+ // These blend modes preserve destination alpha
+ case DlBlendMode::kSrcATop:
+ case DlBlendMode::kDstOut:
+ return GetEffectiveColor(paint, flags).isTransparent()
+ ? OpResult::kNoEffect
+ : OpResult::kPreservesTransparency;
+
+ // Always destructive blend modes, potentially not affecting transparency
+ case DlBlendMode::kSrc:
+ case DlBlendMode::kSrcOut:
+ case DlBlendMode::kDstATop:
+ return GetEffectiveColor(paint, flags).isTransparent()
+ ? OpResult::kPreservesTransparency
+ : OpResult::kAffectsAll;
+
+ // The kDstIn blend mode modifies the destination unless the
+ // source color is opaque.
+ case DlBlendMode::kDstIn:
+ return GetEffectiveColor(paint, flags).isOpaque()
+ ? OpResult::kNoEffect
+ : OpResult::kPreservesTransparency;
+
+ // The next group of blend modes modifies the destination unless the
+ // source color is transparent.
+ case DlBlendMode::kSrcOver:
+ case DlBlendMode::kDstOver:
+ case DlBlendMode::kXor:
+ case DlBlendMode::kPlus:
+ case DlBlendMode::kScreen:
+ case DlBlendMode::kMultiply:
+ case DlBlendMode::kOverlay:
+ case DlBlendMode::kDarken:
+ case DlBlendMode::kLighten:
+ case DlBlendMode::kColorDodge:
+ case DlBlendMode::kHardLight:
+ case DlBlendMode::kSoftLight:
+ case DlBlendMode::kDifference:
+ case DlBlendMode::kExclusion:
+ return GetEffectiveColor(paint, flags).isTransparent()
+ ? OpResult::kNoEffect
+ : OpResult::kAffectsAll;
+
+ // Modulate only leaves the pixel alone when the source is white.
+ case DlBlendMode::kModulate:
+ return GetEffectiveColor(paint, flags) == DlColor::kWhite()
+ ? OpResult::kNoEffect
+ : OpResult::kPreservesTransparency;
+ }
+ }
+ return OpResult::kAffectsAll;
+}
+
} // namespace flutter
diff --git a/display_list/dl_builder.h b/display_list/dl_builder.h
index 1d9cb8e..ab0bce0 100644
--- a/display_list/dl_builder.h
+++ b/display_list/dl_builder.h
@@ -506,15 +506,13 @@
class LayerInfo {
public:
- explicit LayerInfo(size_t save_offset = 0,
- bool has_layer = false,
- std::shared_ptr<const DlImageFilter> filter = nullptr)
+ explicit LayerInfo(
+ size_t save_offset = 0,
+ bool has_layer = false,
+ const std::shared_ptr<const DlImageFilter>& filter = nullptr)
: save_offset_(save_offset),
has_layer_(has_layer),
- cannot_inherit_opacity_(false),
- has_compatible_op_(false),
- filter_(filter),
- is_unbounded_(false) {}
+ filter_(filter) {}
// The offset into the memory buffer where the saveLayer DLOp record
// for this saveLayer() call is placed. This may be needed if the
@@ -527,6 +525,9 @@
bool has_layer() const { return has_layer_; }
bool cannot_inherit_opacity() const { return cannot_inherit_opacity_; }
bool has_compatible_op() const { return has_compatible_op_; }
+ bool affects_transparent_layer() const {
+ return affects_transparent_layer_;
+ }
bool is_group_opacity_compatible() const {
return !cannot_inherit_opacity_;
@@ -549,6 +550,12 @@
}
}
+ // Records that the current layer contains an op that produces visible
+ // output on a transparent surface.
+ void add_visible_op() {
+ affects_transparent_layer_ = true;
+ }
+
// The filter to apply to the layer bounds when it is restored
std::shared_ptr<const DlImageFilter> filter() { return filter_; }
@@ -583,11 +590,13 @@
private:
size_t save_offset_;
bool has_layer_;
- bool cannot_inherit_opacity_;
- bool has_compatible_op_;
+ bool cannot_inherit_opacity_ = false;
+ bool has_compatible_op_ = false;
std::shared_ptr<const DlImageFilter> filter_;
- bool is_unbounded_;
+ bool is_unbounded_ = false;
bool has_deferred_save_op_ = false;
+ bool is_nop_ = false;
+ bool affects_transparent_layer_ = false;
friend class DisplayListBuilder;
};
@@ -701,9 +710,40 @@
return accumulator_->rtree();
}
+ static DisplayListAttributeFlags FlagsForPointMode(PointMode mode);
+
+ enum class OpResult {
+ kNoEffect,
+ kPreservesTransparency,
+ kAffectsAll,
+ };
+
bool paint_nops_on_transparency();
+ OpResult PaintResult(const DlPaint& paint,
+ DisplayListAttributeFlags flags = kDrawPaintFlags);
+
+ void UpdateLayerResult(OpResult result) {
+ switch (result) {
+ case OpResult::kNoEffect:
+ case OpResult::kPreservesTransparency:
+ break;
+ case OpResult::kAffectsAll:
+ current_layer_->add_visible_op();
+ break;
+ }
+ }
+
+ // kAnyColor is a non-opaque and non-transparent color that will not
+ // trigger any short-circuit tests about the results of a blend.
+ static constexpr DlColor kAnyColor = DlColor::kMidGrey().withAlpha(0x80);
+ static_assert(!kAnyColor.isOpaque());
+ static_assert(!kAnyColor.isTransparent());
+ static DlColor GetEffectiveColor(const DlPaint& paint,
+ DisplayListAttributeFlags flags);
// Computes the bounds of an operation adjusted for a given ImageFilter
+ // and returns whether the computation was possible. If the method
+ // returns false then the caller should assume the worst about the bounds.
static bool ComputeFilteredBounds(SkRect& bounds,
const DlImageFilter* filter);
@@ -713,24 +753,24 @@
// Records the fact that we encountered an op that either could not
// estimate its bounds or that fills all of the destination space.
- void AccumulateUnbounded();
+ bool AccumulateUnbounded();
// Records the bounds for an op after modifying them according to the
// supplied attribute flags and transforming by the current matrix.
- void AccumulateOpBounds(const SkRect& bounds,
+ bool AccumulateOpBounds(const SkRect& bounds,
DisplayListAttributeFlags flags) {
SkRect safe_bounds = bounds;
- AccumulateOpBounds(safe_bounds, flags);
+ return AccumulateOpBounds(safe_bounds, flags);
}
// Records the bounds for an op after modifying them according to the
// supplied attribute flags and transforming by the current matrix
// and clipping against the current clip.
- void AccumulateOpBounds(SkRect& bounds, DisplayListAttributeFlags flags);
+ bool AccumulateOpBounds(SkRect& bounds, DisplayListAttributeFlags flags);
// Records the given bounds after transforming by the current matrix
// and clipping against the current clip.
- void AccumulateBounds(SkRect& bounds);
+ bool AccumulateBounds(SkRect& bounds);
DlPaint current_;
};
diff --git a/display_list/dl_color.h b/display_list/dl_color.h
index d926e58..92a3915 100644
--- a/display_list/dl_color.h
+++ b/display_list/dl_color.h
@@ -34,20 +34,20 @@
uint32_t argb;
- bool isOpaque() const { return getAlpha() == 0xFF; }
- bool isTransparent() const { return getAlpha() == 0; }
+ constexpr bool isOpaque() const { return getAlpha() == 0xFF; }
+ constexpr bool isTransparent() const { return getAlpha() == 0; }
- int getAlpha() const { return argb >> 24; }
- int getRed() const { return (argb >> 16) & 0xFF; }
- int getGreen() const { return (argb >> 8) & 0xFF; }
- int getBlue() const { return argb & 0xFF; }
+ constexpr int getAlpha() const { return argb >> 24; }
+ constexpr int getRed() const { return (argb >> 16) & 0xFF; }
+ constexpr int getGreen() const { return (argb >> 8) & 0xFF; }
+ constexpr int getBlue() const { return argb & 0xFF; }
- float getAlphaF() const { return toF(getAlpha()); }
- float getRedF() const { return toF(getRed()); }
- float getGreenF() const { return toF(getGreen()); }
- float getBlueF() const { return toF(getBlue()); }
+ constexpr float getAlphaF() const { return toF(getAlpha()); }
+ constexpr float getRedF() const { return toF(getRed()); }
+ constexpr float getGreenF() const { return toF(getGreen()); }
+ constexpr float getBlueF() const { return toF(getBlue()); }
- uint32_t premultipliedArgb() const {
+ constexpr uint32_t premultipliedArgb() const {
if (isOpaque()) {
return argb;
}
@@ -58,20 +58,20 @@
toC(getBlueF() * f);
}
- DlColor withAlpha(uint8_t alpha) const { //
+ constexpr DlColor withAlpha(uint8_t alpha) const { //
return (argb & 0x00FFFFFF) | (alpha << 24);
}
- DlColor withRed(uint8_t red) const { //
+ constexpr DlColor withRed(uint8_t red) const { //
return (argb & 0xFF00FFFF) | (red << 16);
}
- DlColor withGreen(uint8_t green) const { //
+ constexpr DlColor withGreen(uint8_t green) const { //
return (argb & 0xFFFF00FF) | (green << 8);
}
- DlColor withBlue(uint8_t blue) const { //
+ constexpr DlColor withBlue(uint8_t blue) const { //
return (argb & 0xFFFFFF00) | (blue << 0);
}
- DlColor modulateOpacity(float opacity) const {
+ constexpr DlColor modulateOpacity(float opacity) const {
return opacity <= 0 ? withAlpha(0)
: opacity >= 1 ? *this
: withAlpha(round(getAlpha() * opacity));
diff --git a/display_list/dl_paint.h b/display_list/dl_paint.h
index 77619a2..3d9220f 100644
--- a/display_list/dl_paint.h
+++ b/display_list/dl_paint.h
@@ -83,6 +83,7 @@
color_.argb = alpha << 24 | (color_.argb & 0x00FFFFFF);
return *this;
}
+ SkScalar getOpacity() const { return color_.getAlphaF(); }
DlPaint& setOpacity(SkScalar opacity) {
setAlpha(SkScalarRoundToInt(opacity * 0xff));
return *this;
diff --git a/display_list/testing/dl_rendering_unittests.cc b/display_list/testing/dl_rendering_unittests.cc
index 565c9da..d894044 100644
--- a/display_list/testing/dl_rendering_unittests.cc
+++ b/display_list/testing/dl_rendering_unittests.cc
@@ -9,6 +9,7 @@
#include "flutter/display_list/dl_op_flags.h"
#include "flutter/display_list/dl_sampling_options.h"
#include "flutter/display_list/skia/dl_sk_canvas.h"
+#include "flutter/display_list/skia/dl_sk_conversions.h"
#include "flutter/display_list/skia/dl_sk_dispatcher.h"
#include "flutter/display_list/testing/dl_test_surface_provider.h"
#include "flutter/display_list/utils/dl_comparable.h"
@@ -282,20 +283,26 @@
class RenderResult {
public:
- explicit RenderResult(const sk_sp<SkSurface>& surface) {
+ explicit RenderResult(const sk_sp<SkSurface>& surface,
+ bool take_snapshot = false) {
SkImageInfo info = surface->imageInfo();
info = SkImageInfo::MakeN32Premul(info.dimensions());
addr_ = malloc(info.computeMinByteSize() * info.height());
pixmap_.reset(info, addr_, info.minRowBytes());
- EXPECT_TRUE(surface->readPixels(pixmap_, 0, 0));
+ surface->readPixels(pixmap_, 0, 0);
+ if (take_snapshot) {
+ image_ = surface->makeImageSnapshot();
+ }
}
~RenderResult() { free(addr_); }
+ sk_sp<SkImage> image() const { return image_; }
int width() const { return pixmap_.width(); }
int height() const { return pixmap_.height(); }
const uint32_t* addr32(int x, int y) const { return pixmap_.addr32(x, y); }
private:
+ sk_sp<SkImage> image_;
SkPixmap pixmap_;
void* addr_ = nullptr;
};
@@ -912,7 +919,14 @@
};
DlRenderer dl_safe_restore = [=](DlCanvas* cv, const DlPaint& p) {
// Draw another primitive to disable peephole optimizations
- cv->DrawRect(kRenderBounds.makeOffset(500, 500), DlPaint());
+ // As the rendering op rejection in the DisplayList Builder
+ // gets smarter and smarter, this operation has had to get
+ // sneakier and sneakier about specifying an operation that
+ // won't practically show up in the output, but technically
+ // can't be culled.
+ cv->DrawRect(
+ SkRect::MakeXYWH(kRenderCenterX, kRenderCenterY, 0.0001, 0.0001),
+ DlPaint());
cv->Restore();
};
SkRenderer sk_opt_restore = [=](SkCanvas* cv, const SkPaint& p) {
@@ -3785,7 +3799,6 @@
}
#define FOR_EACH_BLEND_MODE_ENUM(FUNC) \
- FUNC(kSrc) \
FUNC(kClear) \
FUNC(kSrc) \
FUNC(kDst) \
@@ -3816,6 +3829,18 @@
FUNC(kColor) \
FUNC(kLuminosity)
+// This function serves both to enhance error output below and to double
+// check that the macro supplies all modes (otherwise it won't compile)
+static std::string BlendModeToString(DlBlendMode mode) {
+ switch (mode) {
+#define MODE_CASE(m) \
+ case DlBlendMode::m: \
+ return #m;
+ FOR_EACH_BLEND_MODE_ENUM(MODE_CASE)
+#undef MODE_CASE
+ }
+}
+
TEST_F(DisplayListCanvas, BlendColorFilterModifyTransparencyCheck) {
std::vector<std::unique_ptr<RenderEnvironment>> environments;
for (auto& provider : CanvasCompareTester::kTestProviders) {
@@ -3826,7 +3851,8 @@
auto test_mode_color = [&environments](DlBlendMode mode, DlColor color) {
std::stringstream desc_str;
- desc_str << "blend[" << mode << ", " << color << "]";
+ std::string mode_string = BlendModeToString(mode);
+ desc_str << "blend[" << mode_string << ", " << color << "]";
std::string desc = desc_str.str();
DlBlendColorFilter filter(color, mode);
if (filter.modifies_transparent_black()) {
@@ -3887,7 +3913,8 @@
auto test_mode_color = [&environments](DlBlendMode mode, DlColor color) {
std::stringstream desc_str;
- desc_str << "blend[" << mode << ", " << color << "]";
+ std::string mode_string = BlendModeToString(mode);
+ desc_str << "blend[" << mode_string << ", " << color << "]";
std::string desc = desc_str.str();
DlBlendColorFilter filter(color, mode);
if (filter.can_commute_with_opacity()) {
@@ -3946,7 +3973,359 @@
#undef TEST_MODE
}
-#undef FOR_EACH_ENUM
+class DisplayListNopTest : public DisplayListCanvas {
+ // The following code uses the acronym MTB for "modifies_transparent_black"
+
+ protected:
+ DisplayListNopTest() : DisplayListCanvas() {
+ test_src_colors = {
+ DlColor::kBlack().withAlpha(0), // transparent black
+ DlColor::kBlack().withAlpha(0x7f), // half transparent black
+ DlColor::kWhite().withAlpha(0x7f), // half transparent white
+ DlColor::kBlack(), // opaque black
+ DlColor::kWhite(), // opaque white
+ DlColor::kRed(), // opaque red
+ DlColor::kGreen(), // opaque green
+ DlColor::kBlue(), // opaque blue
+ DlColor::kDarkGrey(), // dark grey
+ DlColor::kLightGrey(), // light grey
+ };
+
+ // We test against a color cube of 3x3x3 colors [55,aa,ff]
+ // plus transparency as the first color/pixel
+ test_dst_colors.push_back(DlColor::kTransparent());
+ const int step = 0x55;
+ static_assert(step * 3 == 255);
+ for (int a = step; a < 256; a += step) {
+ for (int r = step; r < 256; r += step) {
+ for (int g = step; g < 256; g += step) {
+ for (int b = step; b < 256; b += step) {
+ test_dst_colors.push_back(DlColor(a << 24 | r << 16 | g << 8 | b));
+ }
+ }
+ }
+ }
+
+ static constexpr float color_filter_matrix_nomtb[] = {
+ 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
+ 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
+ 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
+ 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
+ };
+ static constexpr float color_filter_matrix_mtb[] = {
+ 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
+ 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
+ 0.0001, 0.0001, 0.0001, 0.9997, 0.0, //
+ 0.0001, 0.0001, 0.0001, 0.9997, 0.1, //
+ };
+ color_filter_nomtb = DlMatrixColorFilter::Make(color_filter_matrix_nomtb);
+ color_filter_mtb = DlMatrixColorFilter::Make(color_filter_matrix_mtb);
+ EXPECT_FALSE(color_filter_nomtb->modifies_transparent_black());
+ EXPECT_TRUE(color_filter_mtb->modifies_transparent_black());
+
+ test_data =
+ get_output(test_dst_colors.size(), 1, true, [this](SkCanvas* canvas) {
+ int x = 0;
+ for (DlColor color : test_dst_colors) {
+ SkPaint paint;
+ paint.setColor(color);
+ paint.setBlendMode(SkBlendMode::kSrc);
+ canvas->drawRect(SkRect::MakeXYWH(x, 0, 1, 1), paint);
+ x++;
+ }
+ });
+
+ // For image-on-image tests, the src and dest images will have repeated
+ // rows/columns that have every color, but laid out at right angles to
+ // each other so we see an interaction with every test color against
+ // every other test color.
+ int data_count = test_data->image()->width();
+ test_image_dst_data = get_output(
+ data_count, data_count, true, [this, data_count](SkCanvas* canvas) {
+ ASSERT_EQ(test_data->width(), data_count);
+ ASSERT_EQ(test_data->height(), 1);
+ for (int y = 0; y < data_count; y++) {
+ canvas->drawImage(test_data->image().get(), 0, y);
+ }
+ });
+ test_image_src_data = get_output(
+ data_count, data_count, true, [this, data_count](SkCanvas* canvas) {
+ ASSERT_EQ(test_data->width(), data_count);
+ ASSERT_EQ(test_data->height(), 1);
+ canvas->translate(data_count, 0);
+ canvas->rotate(90);
+ for (int y = 0; y < data_count; y++) {
+ canvas->drawImage(test_data->image().get(), 0, y);
+ }
+ });
+ // Double check that the pixel data is laid out in orthogonal stripes
+ for (int y = 0; y < data_count; y++) {
+ for (int x = 0; x < data_count; x++) {
+ EXPECT_EQ(*test_image_dst_data->addr32(x, y), *test_data->addr32(x, 0));
+ EXPECT_EQ(*test_image_src_data->addr32(x, y), *test_data->addr32(y, 0));
+ }
+ }
+ }
+
+ // These flags are 0 by default until they encounter a counter-example
+ // result and get set.
+ static constexpr int kWasNotNop = 0x1; // Some tested pixel was modified
+ static constexpr int kWasMTB = 0x2; // A transparent pixel was modified
+
+ std::vector<DlColor> test_src_colors;
+ std::vector<DlColor> test_dst_colors;
+
+ std::shared_ptr<DlColorFilter> color_filter_nomtb;
+ std::shared_ptr<DlColorFilter> color_filter_mtb;
+
+ // A 1-row image containing every color in test_dst_colors
+ std::unique_ptr<RenderResult> test_data;
+
+ // A square image containing test_data duplicated in each row
+ std::unique_ptr<RenderResult> test_image_dst_data;
+
+ // A square image containing test_data duplicated in each column
+ std::unique_ptr<RenderResult> test_image_src_data;
+
+ std::unique_ptr<RenderResult> get_output(
+ int w,
+ int h,
+ bool snapshot,
+ const std::function<void(SkCanvas*)>& renderer) {
+ auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(w, h));
+ SkCanvas* canvas = surface->getCanvas();
+ renderer(canvas);
+ canvas->flush();
+ surface->flushAndSubmit(true);
+ return std::make_unique<RenderResult>(surface, snapshot);
+ }
+
+ int check_color_result(DlColor dst_color,
+ DlColor result_color,
+ const sk_sp<DisplayList>& dl,
+ const std::string& desc) {
+ int ret = 0;
+ bool is_error = false;
+ if (dst_color.isTransparent() && !result_color.isTransparent()) {
+ ret |= kWasMTB;
+ is_error = !dl->modifies_transparent_black();
+ }
+ if (result_color != dst_color) {
+ ret |= kWasNotNop;
+ is_error = (dl->op_count() == 0u);
+ }
+ if (is_error) {
+ FML_LOG(ERROR) << std::hex << dst_color << " filters to " << result_color
+ << desc;
+ }
+ return ret;
+ }
+
+ int check_image_result(const std::unique_ptr<RenderResult>& dst_data,
+ const std::unique_ptr<RenderResult>& result_data,
+ const sk_sp<DisplayList>& dl,
+ const std::string& desc) {
+ EXPECT_EQ(dst_data->width(), result_data->width());
+ EXPECT_EQ(dst_data->height(), result_data->height());
+ int all_flags = 0;
+ for (int y = 0; y < dst_data->height(); y++) {
+ const uint32_t* dst_pixels = dst_data->addr32(0, y);
+ const uint32_t* result_pixels = result_data->addr32(0, y);
+ for (int x = 0; x < dst_data->width(); x++) {
+ all_flags |=
+ check_color_result(dst_pixels[x], result_pixels[x], dl, desc);
+ }
+ }
+ return all_flags;
+ }
+
+ void report_results(int all_flags,
+ const sk_sp<DisplayList>& dl,
+ const std::string& desc) {
+ if (!dl->modifies_transparent_black()) {
+ EXPECT_TRUE((all_flags & kWasMTB) == 0);
+ } else if ((all_flags & kWasMTB) == 0) {
+ FML_LOG(INFO) << "combination does not affect transparency: " << desc;
+ }
+ if (dl->op_count() == 0u) {
+ EXPECT_TRUE((all_flags & kWasNotNop) == 0);
+ } else if ((all_flags & kWasNotNop) == 0) {
+ FML_LOG(INFO) << "combination could be classified as a nop: " << desc;
+ }
+ };
+
+ void test_mode_color_via_filter(DlBlendMode mode, DlColor color) {
+ std::stringstream desc_stream;
+ desc_stream << " using SkColorFilter::filterColor() with: ";
+ desc_stream << BlendModeToString(mode);
+ desc_stream << "/" << color;
+ std::string desc = desc_stream.str();
+ DisplayListBuilder builder({0.0f, 0.0f, 100.0f, 100.0f});
+ DlPaint paint = DlPaint(color).setBlendMode(mode);
+ builder.DrawRect({0.0f, 0.0f, 10.0f, 10.0f}, paint);
+ auto dl = builder.Build();
+ if (dl->modifies_transparent_black()) {
+ ASSERT_TRUE(dl->op_count() != 0u);
+ }
+
+ auto sk_mode = static_cast<SkBlendMode>(mode);
+ auto sk_color_filter = SkColorFilters::Blend(color, sk_mode);
+ int all_flags = 0;
+ if (sk_color_filter) {
+ for (DlColor dst_color : test_dst_colors) {
+ DlColor result = sk_color_filter->filterColor(dst_color);
+ all_flags |= check_color_result(dst_color, result, dl, desc);
+ }
+ if ((all_flags & kWasMTB) != 0) {
+ EXPECT_FALSE(sk_color_filter->isAlphaUnchanged());
+ }
+ }
+ report_results(all_flags, dl, desc);
+ };
+
+ void test_mode_color_via_rendering(DlBlendMode mode, DlColor color) {
+ std::stringstream desc_stream;
+ desc_stream << " rendering with: ";
+ desc_stream << BlendModeToString(mode);
+ desc_stream << "/" << color;
+ std::string desc = desc_stream.str();
+ auto test_image = test_data->image();
+ SkRect test_bounds =
+ SkRect::MakeWH(test_image->width(), test_image->height());
+ DisplayListBuilder builder(test_bounds);
+ DlPaint dl_paint = DlPaint(color).setBlendMode(mode);
+ builder.DrawRect(test_bounds, dl_paint);
+ auto dl = builder.Build();
+ bool dl_is_elided = dl->op_count() == 0u;
+ bool dl_affects_transparent_pixels = dl->modifies_transparent_black();
+ ASSERT_TRUE(!dl_is_elided || !dl_affects_transparent_pixels);
+
+ auto sk_mode = static_cast<SkBlendMode>(mode);
+ SkPaint sk_paint;
+ sk_paint.setBlendMode(sk_mode);
+ sk_paint.setColor(color);
+ for (auto& provider : CanvasCompareTester::kTestProviders) {
+ auto result_surface = provider->MakeOffscreenSurface(
+ test_image->width(), test_image->height(),
+ DlSurfaceProvider::kN32Premul_PixelFormat);
+ SkCanvas* result_canvas = result_surface->sk_surface()->getCanvas();
+ result_canvas->clear(SK_ColorTRANSPARENT);
+ result_canvas->drawImage(test_image.get(), 0, 0);
+ result_canvas->drawRect(test_bounds, sk_paint);
+ result_canvas->flush();
+ result_surface->sk_surface()->flushAndSubmit(true);
+ auto result_pixels =
+ std::make_unique<RenderResult>(result_surface->sk_surface());
+
+ int all_flags = check_image_result(test_data, result_pixels, dl, desc);
+ report_results(all_flags, dl, desc);
+ }
+ };
+
+ void test_attributes_image(DlBlendMode mode,
+ DlColor color,
+ DlColorFilter* color_filter,
+ DlImageFilter* image_filter) {
+ // if (true) { return; }
+ std::stringstream desc_stream;
+ desc_stream << " rendering with: ";
+ desc_stream << BlendModeToString(mode);
+ desc_stream << "/" << color;
+ std::string cf_mtb = color_filter
+ ? color_filter->modifies_transparent_black()
+ ? "modifies transparency"
+ : "preserves transparency"
+ : "no filter";
+ desc_stream << ", CF: " << cf_mtb;
+ std::string if_mtb = image_filter
+ ? image_filter->modifies_transparent_black()
+ ? "modifies transparency"
+ : "preserves transparency"
+ : "no filter";
+ desc_stream << ", IF: " << if_mtb;
+ std::string desc = desc_stream.str();
+
+ DisplayListBuilder builder({0.0f, 0.0f, 100.0f, 100.0f});
+ DlPaint paint = DlPaint(color) //
+ .setBlendMode(mode) //
+ .setColorFilter(color_filter) //
+ .setImageFilter(image_filter);
+ builder.DrawImage(DlImage::Make(test_image_src_data->image()), {0, 0},
+ DlImageSampling::kNearestNeighbor, &paint);
+ auto dl = builder.Build();
+
+ int w = test_image_src_data->width();
+ int h = test_image_src_data->height();
+ auto sk_mode = static_cast<SkBlendMode>(mode);
+ SkPaint sk_paint;
+ sk_paint.setBlendMode(sk_mode);
+ sk_paint.setColor(color);
+ sk_paint.setColorFilter(ToSk(color_filter));
+ sk_paint.setImageFilter(ToSk(image_filter));
+ for (auto& provider : CanvasCompareTester::kTestProviders) {
+ auto result_surface = provider->MakeOffscreenSurface(
+ w, h, DlSurfaceProvider::kN32Premul_PixelFormat);
+ SkCanvas* result_canvas = result_surface->sk_surface()->getCanvas();
+ result_canvas->clear(SK_ColorTRANSPARENT);
+ result_canvas->drawImage(test_image_dst_data->image(), 0, 0);
+ result_canvas->drawImage(test_image_src_data->image(), 0, 0,
+ SkSamplingOptions(), &sk_paint);
+ result_canvas->flush();
+ result_surface->sk_surface()->flushAndSubmit(true);
+ auto result_pixels =
+ std::make_unique<RenderResult>(result_surface->sk_surface());
+
+ int all_flags =
+ check_image_result(test_image_dst_data, result_pixels, dl, desc);
+ report_results(all_flags, dl, desc);
+ }
+ };
+};
+
+TEST_F(DisplayListNopTest, BlendModeAndColorViaColorFilter) {
+ auto test_mode_filter = [this](DlBlendMode mode) {
+ for (DlColor color : test_src_colors) {
+ test_mode_color_via_filter(mode, color);
+ }
+ };
+
+#define TEST_MODE(V) test_mode_filter(DlBlendMode::V);
+ FOR_EACH_BLEND_MODE_ENUM(TEST_MODE)
+#undef TEST_MODE
+}
+
+TEST_F(DisplayListNopTest, BlendModeAndColorByRendering) {
+ auto test_mode_render = [this](DlBlendMode mode) {
+ // First check rendering a variety of colors onto image
+ for (DlColor color : test_src_colors) {
+ test_mode_color_via_rendering(mode, color);
+ }
+ };
+
+#define TEST_MODE(V) test_mode_render(DlBlendMode::V);
+ FOR_EACH_BLEND_MODE_ENUM(TEST_MODE)
+#undef TEST_MODE
+}
+
+TEST_F(DisplayListNopTest, BlendModeAndColorAndFiltersByRendering) {
+ auto test_mode_render = [this](DlBlendMode mode) {
+ auto image_filter_nomtb = DlColorFilterImageFilter(color_filter_nomtb);
+ auto image_filter_mtb = DlColorFilterImageFilter(color_filter_mtb);
+ for (DlColor color : test_src_colors) {
+ test_attributes_image(mode, color, nullptr, nullptr);
+ test_attributes_image(mode, color, color_filter_nomtb.get(), nullptr);
+ test_attributes_image(mode, color, color_filter_mtb.get(), nullptr);
+ test_attributes_image(mode, color, nullptr, &image_filter_nomtb);
+ test_attributes_image(mode, color, nullptr, &image_filter_mtb);
+ }
+ };
+
+#define TEST_MODE(V) test_mode_render(DlBlendMode::V);
+ FOR_EACH_BLEND_MODE_ENUM(TEST_MODE)
+#undef TEST_MODE
+}
+
+#undef FOR_EACH_BLEND_MODE_ENUM
} // namespace testing
} // namespace flutter
diff --git a/display_list/testing/dl_test_snippets.cc b/display_list/testing/dl_test_snippets.cc
index 4479f63..0a53f03 100644
--- a/display_list/testing/dl_test_snippets.cc
+++ b/display_list/testing/dl_test_snippets.cc
@@ -517,7 +517,7 @@
}},
{1, 16, 1, 24,
[](DlOpReceiver& r) {
- r.drawColor(SK_ColorBLUE, DlBlendMode::kDstIn);
+ r.drawColor(SK_ColorBLUE, DlBlendMode::kDstOut);
}},
{1, 16, 1, 24,
[](DlOpReceiver& r) {
diff --git a/display_list/testing/dl_test_snippets.h b/display_list/testing/dl_test_snippets.h
index 94f7995..98b4059 100644
--- a/display_list/testing/dl_test_snippets.h
+++ b/display_list/testing/dl_test_snippets.h
@@ -151,9 +151,9 @@
static const DlDilateImageFilter kTestDilateImageFilter1(5.0, 5.0);
static const DlDilateImageFilter kTestDilateImageFilter2(6.0, 5.0);
static const DlDilateImageFilter kTestDilateImageFilter3(5.0, 6.0);
-static const DlErodeImageFilter kTestErodeImageFilter1(5.0, 5.0);
-static const DlErodeImageFilter kTestErodeImageFilter2(6.0, 5.0);
-static const DlErodeImageFilter kTestErodeImageFilter3(5.0, 6.0);
+static const DlErodeImageFilter kTestErodeImageFilter1(4.0, 4.0);
+static const DlErodeImageFilter kTestErodeImageFilter2(4.0, 3.0);
+static const DlErodeImageFilter kTestErodeImageFilter3(3.0, 4.0);
static const DlMatrixImageFilter kTestMatrixImageFilter1(
SkMatrix::RotateDeg(45),
kNearestSampling);
diff --git a/display_list/utils/dl_matrix_clip_tracker.h b/display_list/utils/dl_matrix_clip_tracker.h
index c6efaaf..d50ab89 100644
--- a/display_list/utils/dl_matrix_clip_tracker.h
+++ b/display_list/utils/dl_matrix_clip_tracker.h
@@ -38,6 +38,7 @@
bool content_culled(const SkRect& content_bounds) const {
return current_->content_culled(content_bounds);
}
+ bool is_cull_rect_empty() const { return current_->is_cull_rect_empty(); }
void save();
void restore();
@@ -86,9 +87,10 @@
virtual SkMatrix matrix_3x3() const = 0;
virtual SkM44 matrix_4x4() const = 0;
- virtual SkRect device_cull_rect() const { return cull_rect_; }
+ SkRect device_cull_rect() const { return cull_rect_; }
virtual SkRect local_cull_rect() const = 0;
virtual bool content_culled(const SkRect& content_bounds) const;
+ bool is_cull_rect_empty() const { return cull_rect_.isEmpty(); }
virtual void translate(SkScalar tx, SkScalar ty) = 0;
virtual void scale(SkScalar sx, SkScalar sy) = 0;
diff --git a/flow/diff_context_unittests.cc b/flow/diff_context_unittests.cc
index e981258..d8b78a9 100644
--- a/flow/diff_context_unittests.cc
+++ b/flow/diff_context_unittests.cc
@@ -10,7 +10,7 @@
TEST_F(DiffContextTest, ClipAlignment) {
MockLayerTree t1;
t1.root()->Add(CreateDisplayListLayer(
- CreateDisplayList(SkRect::MakeLTRB(30, 30, 50, 50), 1)));
+ CreateDisplayList(SkRect::MakeLTRB(30, 30, 50, 50))));
auto damage = DiffLayerTree(t1, MockLayerTree(), SkIRect::MakeEmpty(), 0, 0);
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(30, 30, 50, 50));
EXPECT_EQ(damage.buffer_damage, SkIRect::MakeLTRB(30, 30, 50, 50));
diff --git a/flow/layers/container_layer_unittests.cc b/flow/layers/container_layer_unittests.cc
index 42317ff..206be00 100644
--- a/flow/layers/container_layer_unittests.cc
+++ b/flow/layers/container_layer_unittests.cc
@@ -564,9 +564,9 @@
// Insert PictureLayer amongst container layers
TEST_F(ContainerLayerDiffTest, PictureLayerInsertion) {
- auto pic1 = CreateDisplayList(SkRect::MakeLTRB(0, 0, 50, 50), 1);
- auto pic2 = CreateDisplayList(SkRect::MakeLTRB(100, 0, 150, 50), 1);
- auto pic3 = CreateDisplayList(SkRect::MakeLTRB(200, 0, 250, 50), 1);
+ auto pic1 = CreateDisplayList(SkRect::MakeLTRB(0, 0, 50, 50));
+ auto pic2 = CreateDisplayList(SkRect::MakeLTRB(100, 0, 150, 50));
+ auto pic3 = CreateDisplayList(SkRect::MakeLTRB(200, 0, 250, 50));
MockLayerTree t1;
@@ -616,9 +616,9 @@
// Insert picture layer amongst other picture layers
TEST_F(ContainerLayerDiffTest, PictureInsertion) {
- auto pic1 = CreateDisplayList(SkRect::MakeLTRB(0, 0, 50, 50), 1);
- auto pic2 = CreateDisplayList(SkRect::MakeLTRB(100, 0, 150, 50), 1);
- auto pic3 = CreateDisplayList(SkRect::MakeLTRB(200, 0, 250, 50), 1);
+ auto pic1 = CreateDisplayList(SkRect::MakeLTRB(0, 0, 50, 50));
+ auto pic2 = CreateDisplayList(SkRect::MakeLTRB(100, 0, 150, 50));
+ auto pic3 = CreateDisplayList(SkRect::MakeLTRB(200, 0, 250, 50));
MockLayerTree t1;
t1.root()->Add(CreateDisplayListLayer(pic1));
diff --git a/flow/layers/display_list_layer_unittests.cc b/flow/layers/display_list_layer_unittests.cc
index 956bfe8..dee93d2 100644
--- a/flow/layers/display_list_layer_unittests.cc
+++ b/flow/layers/display_list_layer_unittests.cc
@@ -297,7 +297,7 @@
using DisplayListLayerDiffTest = DiffContextTest;
TEST_F(DisplayListLayerDiffTest, SimpleDisplayList) {
- auto display_list = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1);
+ auto display_list = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60));
MockLayerTree tree1;
tree1.root()->Add(CreateDisplayListLayer(display_list));
@@ -317,7 +317,7 @@
}
TEST_F(DisplayListLayerDiffTest, FractionalTranslation) {
- auto display_list = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1);
+ auto display_list = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60));
MockLayerTree tree1;
tree1.root()->Add(
@@ -330,7 +330,7 @@
}
TEST_F(DisplayListLayerDiffTest, FractionalTranslationWithRasterCache) {
- auto display_list = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1);
+ auto display_list = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60));
MockLayerTree tree1;
tree1.root()->Add(
@@ -344,21 +344,25 @@
TEST_F(DisplayListLayerDiffTest, DisplayListCompare) {
MockLayerTree tree1;
- auto display_list1 = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1);
+ auto display_list1 =
+ CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), DlColor::kGreen());
tree1.root()->Add(CreateDisplayListLayer(display_list1));
auto damage = DiffLayerTree(tree1, MockLayerTree());
EXPECT_EQ(damage.frame_damage, SkIRect::MakeLTRB(10, 10, 60, 60));
MockLayerTree tree2;
- auto display_list2 = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1);
+ // same DL, same offset
+ auto display_list2 =
+ CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), DlColor::kGreen());
tree2.root()->Add(CreateDisplayListLayer(display_list2));
damage = DiffLayerTree(tree2, tree1);
EXPECT_EQ(damage.frame_damage, SkIRect::MakeEmpty());
MockLayerTree tree3;
- auto display_list3 = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1);
+ auto display_list3 =
+ CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), DlColor::kGreen());
// add offset
tree3.root()->Add(
CreateDisplayListLayer(display_list3, SkPoint::Make(10, 10)));
@@ -368,7 +372,8 @@
MockLayerTree tree4;
// different color
- auto display_list4 = CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 2);
+ auto display_list4 =
+ CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), DlColor::kRed());
tree4.root()->Add(
CreateDisplayListLayer(display_list4, SkPoint::Make(10, 10)));
diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc
index ca1f106..71ccdc1 100644
--- a/flow/layers/opacity_layer_unittests.cc
+++ b/flow/layers/opacity_layer_unittests.cc
@@ -659,7 +659,7 @@
TEST_F(OpacityLayerDiffTest, FractionalTranslation) {
auto picture = CreateDisplayListLayer(
- CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1));
+ CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60)));
auto layer = CreateOpacityLater({picture}, 128, SkPoint::Make(0.5, 0.5));
MockLayerTree tree1;
@@ -672,7 +672,7 @@
TEST_F(OpacityLayerDiffTest, FractionalTranslationWithRasterCache) {
auto picture = CreateDisplayListLayer(
- CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60), 1));
+ CreateDisplayList(SkRect::MakeLTRB(10, 10, 60, 60)));
auto layer = CreateOpacityLater({picture}, 128, SkPoint::Make(0.5, 0.5));
MockLayerTree tree1;
diff --git a/flow/testing/diff_context_test.cc b/flow/testing/diff_context_test.cc
index 20153a0..c4c68bb 100644
--- a/flow/testing/diff_context_test.cc
+++ b/flow/testing/diff_context_test.cc
@@ -30,7 +30,7 @@
}
sk_sp<DisplayList> DiffContextTest::CreateDisplayList(const SkRect& bounds,
- SkColor color) {
+ DlColor color) {
DisplayListBuilder builder;
builder.DrawRect(bounds, DlPaint().setColor(color));
return builder.Build();
diff --git a/flow/testing/diff_context_test.h b/flow/testing/diff_context_test.h
index 69beb41..f225615 100644
--- a/flow/testing/diff_context_test.h
+++ b/flow/testing/diff_context_test.h
@@ -47,7 +47,8 @@
// Create display list consisting of filled rect with given color; Being able
// to specify different color is useful to test deep comparison of pictures
- sk_sp<DisplayList> CreateDisplayList(const SkRect& bounds, uint32_t color);
+ sk_sp<DisplayList> CreateDisplayList(const SkRect& bounds,
+ DlColor color = DlColor::kBlack());
std::shared_ptr<DisplayListLayer> CreateDisplayListLayer(
const sk_sp<DisplayList>& display_list,
diff --git a/impeller/display_list/dl_unittests.cc b/impeller/display_list/dl_unittests.cc
index 22f118f..01a81d3 100644
--- a/impeller/display_list/dl_unittests.cc
+++ b/impeller/display_list/dl_unittests.cc
@@ -830,18 +830,12 @@
}
TEST_P(DisplayListTest, TransparentShadowProducesCorrectColor) {
- flutter::DisplayListBuilder builder;
- {
- builder.Save();
- builder.Scale(1.618, 1.618);
- builder.DrawShadow(SkPath{}.addRect(SkRect::MakeXYWH(0, 0, 200, 100)),
- SK_ColorTRANSPARENT, 15, false, 1);
- builder.Restore();
- }
- auto dl = builder.Build();
-
DlDispatcher dispatcher;
- dispatcher.drawDisplayList(dl, 1);
+ dispatcher.save();
+ dispatcher.scale(1.618, 1.618);
+ dispatcher.drawShadow(SkPath{}.addRect(SkRect::MakeXYWH(0, 0, 200, 100)),
+ SK_ColorTRANSPARENT, 15, false, 1);
+ dispatcher.restore();
auto picture = dispatcher.EndRecordingAsPicture();
std::shared_ptr<SolidRRectBlurContents> rrect_blur;
diff --git a/shell/common/dl_op_spy_unittests.cc b/shell/common/dl_op_spy_unittests.cc
index dba0213..7aaac1c 100644
--- a/shell/common/dl_op_spy_unittests.cc
+++ b/shell/common/dl_op_spy_unittests.cc
@@ -7,15 +7,40 @@
#include "flutter/shell/common/dl_op_spy.h"
#include "flutter/testing/testing.h"
#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkRSXform.h"
namespace flutter {
namespace testing {
+// The following macros demonstrate that the DlOpSpy class is equivalent
+// to DisplayList::affects_transparent_surface() now that DisplayListBuilder
+// implements operation culling.
+// See https://github.com/flutter/flutter/issues/125403
+#define ASSERT_DID_DRAW(spy, dl) \
+ do { \
+ ASSERT_TRUE(spy.did_draw()); \
+ ASSERT_TRUE(dl->modifies_transparent_black()); \
+ } while (0)
+
+#define ASSERT_NO_DRAW(spy, dl) \
+ do { \
+ ASSERT_FALSE(spy.did_draw()); \
+ ASSERT_FALSE(dl->modifies_transparent_black()); \
+ } while (0)
+
TEST(DlOpSpy, DidDrawIsFalseByDefault) {
DlOpSpy dl_op_spy;
ASSERT_FALSE(dl_op_spy.did_draw());
}
+TEST(DlOpSpy, EmptyDisplayList) {
+ DisplayListBuilder builder;
+ sk_sp<DisplayList> dl = builder.Build();
+ DlOpSpy dl_op_spy;
+ dl->Dispatch(dl_op_spy);
+ ASSERT_NO_DRAW(dl_op_spy, dl);
+}
+
TEST(DlOpSpy, SetColor) {
{ // No Color set.
DisplayListBuilder builder;
@@ -24,7 +49,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // Set transparent color.
DisplayListBuilder builder;
@@ -33,7 +58,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
{ // Set black color.
DisplayListBuilder builder;
@@ -42,7 +67,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
}
@@ -55,7 +80,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // Set transparent color.
DisplayListBuilder builder;
@@ -67,7 +92,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
{ // Set black color.
DisplayListBuilder builder;
@@ -79,7 +104,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
}
@@ -91,16 +116,25 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
- { // Transparent color source.
+ { // Transparent color with kSrc.
DisplayListBuilder builder;
auto color = DlColor::kTransparent();
builder.DrawColor(color, DlBlendMode::kSrc);
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
+ }
+ { // Transparent color with kSrcOver.
+ DisplayListBuilder builder;
+ auto color = DlColor::kTransparent();
+ builder.DrawColor(color, DlBlendMode::kSrcOver);
+ sk_sp<DisplayList> dl = builder.Build();
+ DlOpSpy dl_op_spy;
+ dl->Dispatch(dl_op_spy);
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}
@@ -112,7 +146,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
{ // black color in paint.
DisplayListBuilder builder;
@@ -121,7 +155,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
}
@@ -133,7 +167,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // transparent
DisplayListBuilder builder;
@@ -142,7 +176,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}
@@ -154,7 +188,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // transparent
DisplayListBuilder builder;
@@ -163,11 +197,11 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}
-TEST(DlOpSpy, drawOval) {
+TEST(DlOpSpy, DrawOval) {
{ // black
DisplayListBuilder builder;
DlPaint paint(DlColor::kBlack());
@@ -175,7 +209,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // transparent
DisplayListBuilder builder;
@@ -184,11 +218,11 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}
-TEST(DlOpSpy, drawCircle) {
+TEST(DlOpSpy, DrawCircle) {
{ // black
DisplayListBuilder builder;
DlPaint paint(DlColor::kBlack());
@@ -196,7 +230,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // transparent
DisplayListBuilder builder;
@@ -205,11 +239,11 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}
-TEST(DlOpSpy, drawRRect) {
+TEST(DlOpSpy, DrawRRect) {
{ // black
DisplayListBuilder builder;
DlPaint paint(DlColor::kBlack());
@@ -217,7 +251,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // transparent
DisplayListBuilder builder;
@@ -226,34 +260,49 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}
-TEST(DlOpSpy, drawPath) {
- { // black
+TEST(DlOpSpy, DrawPath) {
+ { // black line
DisplayListBuilder builder;
DlPaint paint(DlColor::kBlack());
+ paint.setDrawStyle(DlDrawStyle::kStroke);
builder.DrawPath(SkPath::Line(SkPoint::Make(0, 1), SkPoint::Make(1, 1)),
paint);
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
- { // transparent
+ { // triangle
+ DisplayListBuilder builder;
+ DlPaint paint(DlColor::kBlack());
+ SkPath path;
+ path.moveTo({0, 0});
+ path.lineTo({1, 0});
+ path.lineTo({0, 1});
+ builder.DrawPath(path, paint);
+ sk_sp<DisplayList> dl = builder.Build();
+ DlOpSpy dl_op_spy;
+ dl->Dispatch(dl_op_spy);
+ ASSERT_DID_DRAW(dl_op_spy, dl);
+ }
+ { // transparent line
DisplayListBuilder builder;
DlPaint paint(DlColor::kTransparent());
+ paint.setDrawStyle(DlDrawStyle::kStroke);
builder.DrawPath(SkPath::Line(SkPoint::Make(0, 1), SkPoint::Make(1, 1)),
paint);
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}
-TEST(DlOpSpy, drawArc) {
+TEST(DlOpSpy, DrawArc) {
{ // black
DisplayListBuilder builder;
DlPaint paint(DlColor::kBlack());
@@ -261,7 +310,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // transparent
DisplayListBuilder builder;
@@ -270,11 +319,11 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}
-TEST(DlOpSpy, drawPoints) {
+TEST(DlOpSpy, DrawPoints) {
{ // black
DisplayListBuilder builder;
DlPaint paint(DlColor::kBlack());
@@ -283,7 +332,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // transparent
DisplayListBuilder builder;
@@ -293,38 +342,62 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}
-TEST(DlOpSpy, drawVertices) {
+TEST(DlOpSpy, DrawVertices) {
{ // black
DisplayListBuilder builder;
DlPaint paint(DlColor::kBlack());
- const SkPoint vertices[] = {SkPoint::Make(5, 5)};
- const SkPoint texture_coordinates[] = {SkPoint::Make(5, 5)};
- const DlColor colors[] = {DlColor::kBlack()};
- auto dl_vertices = DlVertices::Make(DlVertexMode::kTriangles, 1, vertices,
+ const SkPoint vertices[] = {
+ SkPoint::Make(5, 5),
+ SkPoint::Make(5, 15),
+ SkPoint::Make(15, 5),
+ };
+ const SkPoint texture_coordinates[] = {
+ SkPoint::Make(5, 5),
+ SkPoint::Make(15, 5),
+ SkPoint::Make(5, 15),
+ };
+ const DlColor colors[] = {
+ DlColor::kBlack(),
+ DlColor::kRed(),
+ DlColor::kGreen(),
+ };
+ auto dl_vertices = DlVertices::Make(DlVertexMode::kTriangles, 3, vertices,
texture_coordinates, colors, 0);
builder.DrawVertices(dl_vertices.get(), DlBlendMode::kSrc, paint);
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // transparent
DisplayListBuilder builder;
DlPaint paint(DlColor::kTransparent());
- const SkPoint vertices[] = {SkPoint::Make(5, 5)};
- const SkPoint texture_coordinates[] = {SkPoint::Make(5, 5)};
- const DlColor colors[] = {DlColor::kBlack()};
- auto dl_vertices = DlVertices::Make(DlVertexMode::kTriangles, 1, vertices,
+ const SkPoint vertices[] = {
+ SkPoint::Make(5, 5),
+ SkPoint::Make(5, 15),
+ SkPoint::Make(15, 5),
+ };
+ const SkPoint texture_coordinates[] = {
+ SkPoint::Make(5, 5),
+ SkPoint::Make(15, 5),
+ SkPoint::Make(5, 15),
+ };
+ const DlColor colors[] = {
+ DlColor::kBlack(),
+ DlColor::kRed(),
+ DlColor::kGreen(),
+ };
+ auto dl_vertices = DlVertices::Make(DlVertexMode::kTriangles, 3, vertices,
texture_coordinates, colors, 0);
builder.DrawVertices(dl_vertices.get(), DlBlendMode::kSrc, paint);
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}
@@ -343,7 +416,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // DrawImageRect
DisplayListBuilder builder;
@@ -359,7 +432,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // DrawImageNine
DisplayListBuilder builder;
@@ -375,7 +448,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // DrawAtlas
DisplayListBuilder builder;
@@ -386,20 +459,19 @@
SkBitmap bitmap;
bitmap.allocPixels(info, 0);
auto sk_image = SkImages::RasterFromBitmap(bitmap);
- const SkRSXform xform[] = {};
- const SkRect tex[] = {};
- const DlColor colors[] = {};
+ const SkRSXform xform[] = {SkRSXform::Make(1, 0, 0, 0)};
+ const SkRect tex[] = {SkRect::MakeXYWH(10, 10, 10, 10)};
SkRect cull_rect = SkRect::MakeWH(5, 5);
- builder.DrawAtlas(DlImage::Make(sk_image), xform, tex, colors, 0,
+ builder.DrawAtlas(DlImage::Make(sk_image), xform, tex, nullptr, 1,
DlBlendMode::kSrc, DlImageSampling::kLinear, &cull_rect);
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
}
-TEST(DlOpSpy, drawDisplayList) {
+TEST(DlOpSpy, DrawDisplayList) {
{ // Recursive Transparent DisplayList
DisplayListBuilder builder;
DlPaint paint(DlColor::kTransparent());
@@ -414,7 +486,7 @@
DlOpSpy dl_op_spy;
dl2->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl2);
}
{ // Sub non-transparent DisplayList,
DisplayListBuilder builder;
@@ -430,7 +502,7 @@
DlOpSpy dl_op_spy;
dl2->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl2);
}
{ // Sub non-transparent DisplayList, 0 opacity
@@ -447,7 +519,7 @@
DlOpSpy dl_op_spy;
dl2->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl2);
}
{ // Parent non-transparent DisplayList
@@ -464,11 +536,11 @@
DlOpSpy dl_op_spy;
dl2->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl2);
}
}
-TEST(DlOpSpy, drawTextBlob) {
+TEST(DlOpSpy, DrawTextBlob) {
{ // Non-transparent color.
DisplayListBuilder builder;
DlPaint paint(DlColor::kBlack());
@@ -479,7 +551,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // transparent color.
DisplayListBuilder builder;
@@ -491,11 +563,11 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}
-TEST(DlOpSpy, drawShadow) {
+TEST(DlOpSpy, DrawShadow) {
{ // valid shadow
DisplayListBuilder builder;
DlPaint paint;
@@ -505,7 +577,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_TRUE(dl_op_spy.did_draw());
+ ASSERT_DID_DRAW(dl_op_spy, dl);
}
{ // transparent color
DisplayListBuilder builder;
@@ -516,7 +588,7 @@
sk_sp<DisplayList> dl = builder.Build();
DlOpSpy dl_op_spy;
dl->Dispatch(dl_op_spy);
- ASSERT_FALSE(dl_op_spy.did_draw());
+ ASSERT_NO_DRAW(dl_op_spy, dl);
}
}