diff --git a/display_list/benchmarking/dl_complexity_unittests.cc b/display_list/benchmarking/dl_complexity_unittests.cc index 4ec6111485d88..eee77ac9bdf55 100644 --- a/display_list/benchmarking/dl_complexity_unittests.cc +++ b/display_list/benchmarking/dl_complexity_unittests.cc @@ -423,7 +423,7 @@ TEST(DisplayListComplexity, DrawAtlas) { std::vector 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 378f86804f260..e06bcf246d3d4 100644 --- a/display_list/display_list.cc +++ b/display_list/display_list.cc @@ -22,7 +22,8 @@ DisplayList::DisplayList() 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 @@ DisplayList::DisplayList(DisplayListStorage&& storage, const SkRect& bounds, bool can_apply_group_opacity, bool is_ui_thread_safe, + bool modifies_transparent_black, sk_sp rtree) : storage_(std::move(storage)), byte_count_(byte_count), @@ -42,6 +44,7 @@ DisplayList::DisplayList(DisplayListStorage&& storage, 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 4a6f339fdea0d..668a247d5596f 100644 --- a/display_list/display_list.h +++ b/display_list/display_list.h @@ -265,6 +265,19 @@ class DisplayList : public SkRefCnt { 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 @@ class DisplayList : public SkRefCnt { const SkRect& bounds, bool can_apply_group_opacity, bool is_ui_thread_safe, + bool modifies_transparent_black, sk_sp rtree); static uint32_t next_unique_id(); @@ -292,6 +306,8 @@ class DisplayList : public SkRefCnt { const bool can_apply_group_opacity_; const bool is_ui_thread_safe_; + const bool modifies_transparent_black_; + const sk_sp rtree_; void Dispatch(DlOpReceiver& ctx, diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index 4c16f6e4438dc..9bead1ee8ff76 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -24,6 +24,7 @@ #include "third_party/skia/include/core/SkBBHFactory.h" #include "third_party/skia/include/core/SkColorFilter.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 { @@ -3018,5 +3019,164 @@ TEST_F(DisplayListTest, DrawUnorderedRoundRectPathCCW) { check_inverted_bounds(renderer, "DrawRoundRectPath Counter-Clockwise"); } +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 ea2a132580780..266a8609ec5ed 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 @@ sk_sp DisplayListBuilder::Build() { 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( - new DisplayList(std::move(storage_), bytes, count, nested_bytes, - nested_count, bounds(), compatible, is_safe, rtree())); + return sk_sp(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::checkForDeferredSave() { } 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 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, const SaveLayerOptions in_options, const DlImageFilter* backdrop) { SaveLayerOptions options = in_options.without_optimizations(); - size_t save_layer_offset = used_; - if (backdrop) { - bounds // - ? Push(0, 1, options, *bounds, backdrop) - : Push(0, 1, options, backdrop); - } else { - bounds // - ? Push(0, 1, options, *bounds) - : Push(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 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, // 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(0, 1, options, *bounds, backdrop) + : Push(0, 1, options, backdrop); + } else { + bounds // + ? Push(0, 1, options, *bounds) + : Push(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 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, 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 @@ void DisplayListBuilder::saveLayer(const SkRect* bounds, 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 @@ void DisplayListBuilder::ClipRect(const SkRect& rect, 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 @@ void DisplayListBuilder::ClipRect(const SkRect& rect, Push(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 @@ void DisplayListBuilder::ClipRRect(const SkRRect& rrect, 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 @@ void DisplayListBuilder::ClipRRect(const SkRRect& rrect, Push(0, 1, rrect, is_aa); break; } - tracker_.clipRRect(rrect, clip_op, is_aa); } } void DisplayListBuilder::ClipPath(const SkPath& path, @@ -703,6 +736,11 @@ void DisplayListBuilder::ClipPath(const SkPath& path, 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 @@ void DisplayListBuilder::ClipPath(const SkPath& path, Push(0, 1, path, is_aa); break; } - tracker_.clipPath(path, clip_op, is_aa); } bool DisplayListBuilder::QuickReject(const SkRect& bounds) const { @@ -720,27 +757,36 @@ bool DisplayListBuilder::QuickReject(const SkRect& bounds) const { } void DisplayListBuilder::drawPaint() { - Push(0, 1); - CheckLayerOpacityCompatibility(); - AccumulateUnbounded(); + OpResult result = PaintResult(current_, kDrawPaintFlags); + if (result != OpResult::kNoEffect && AccumulateUnbounded()) { + Push(0, 1); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } } void DisplayListBuilder::DrawPaint(const DlPaint& paint) { SetAttributesFromPaint(paint, DisplayListOpFlags::kDrawPaintFlags); drawPaint(); } void DisplayListBuilder::DrawColor(DlColor color, DlBlendMode mode) { - Push(0, 1, color, mode); - CheckLayerOpacityCompatibility(mode); - AccumulateUnbounded(); + OpResult result = PaintResult(DlPaint(color).setBlendMode(mode)); + if (result != OpResult::kNoEffect && AccumulateUnbounded()) { + Push(0, 1, color, mode); + CheckLayerOpacityCompatibility(mode); + UpdateLayerResult(result); + } } void DisplayListBuilder::drawLine(const SkPoint& p0, const SkPoint& p1) { - Push(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(0, 1, p0, p1); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } } void DisplayListBuilder::DrawLine(const SkPoint& p0, const SkPoint& p1, @@ -749,29 +795,45 @@ void DisplayListBuilder::DrawLine(const SkPoint& p0, drawLine(p0, p1); } void DisplayListBuilder::drawRect(const SkRect& rect) { - Push(0, 1, rect); - CheckLayerOpacityCompatibility(); - AccumulateOpBounds(rect.makeSorted(), kDrawRectFlags); + DisplayListAttributeFlags flags = kDrawRectFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect && + AccumulateOpBounds(rect.makeSorted(), flags)) { + Push(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(0, 1, bounds); - CheckLayerOpacityCompatibility(); - AccumulateOpBounds(bounds.makeSorted(), kDrawOvalFlags); + DisplayListAttributeFlags flags = kDrawOvalFlags; + OpResult result = PaintResult(current_, flags); + if (result != OpResult::kNoEffect && + AccumulateOpBounds(bounds.makeSorted(), flags)) { + Push(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(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(0, 1, center, radius); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } + } } void DisplayListBuilder::DrawCircle(const SkPoint& center, SkScalar radius, @@ -785,9 +847,14 @@ void DisplayListBuilder::drawRRect(const SkRRect& rrect) { } else if (rrect.isOval()) { drawOval(rrect.rect()); } else { - Push(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(0, 1, rrect); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } } } void DisplayListBuilder::DrawRRect(const SkRRect& rrect, const DlPaint& paint) { @@ -796,9 +863,14 @@ void DisplayListBuilder::DrawRRect(const SkRRect& rrect, const DlPaint& paint) { } void DisplayListBuilder::drawDRRect(const SkRRect& outer, const SkRRect& inner) { - Push(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(0, 1, outer, inner); + CheckLayerOpacityCompatibility(); + UpdateLayerResult(result); + } } void DisplayListBuilder::DrawDRRect(const SkRRect& outer, const SkRRect& inner, @@ -807,12 +879,17 @@ void DisplayListBuilder::DrawDRRect(const SkRRect& outer, drawDRRect(outer, inner); } void DisplayListBuilder::drawPath(const SkPath& path) { - Push(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(0, 1, path); + CheckLayerOpacityHairlineCompatibility(); + UpdateLayerResult(result); + } } } void DisplayListBuilder::DrawPath(const SkPath& path, const DlPaint& paint) { @@ -824,19 +901,23 @@ void DisplayListBuilder::drawArc(const SkRect& bounds, SkScalar start, SkScalar sweep, bool useCenter) { - Push(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(0, 1, bounds, start, sweep, useCenter); + if (useCenter) { + CheckLayerOpacityHairlineCompatibility(); + } else { + CheckLayerOpacityCompatibility(); + } + UpdateLayerResult(result); + } } void DisplayListBuilder::DrawArc(const SkRect& bounds, SkScalar start, @@ -847,14 +928,31 @@ void DisplayListBuilder::DrawArc(const SkRect& bounds, 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 +960,23 @@ void DisplayListBuilder::drawPoints(PointMode mode, 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(bytes, 1, count); - AccumulateOpBounds(point_bounds, kDrawPointsAsPointsFlags); break; case PointMode::kLines: data_ptr = Push(bytes, 1, count); - AccumulateOpBounds(point_bounds, kDrawPointsAsLinesFlags); break; case PointMode::kPolygon: data_ptr = Push(bytes, 1, count); - AccumulateOpBounds(point_bounds, kDrawPointsAsPolygonFlags); break; default: - FML_DCHECK(false); + FML_UNREACHABLE(); return; } CopyV(data_ptr, pts, count); @@ -886,39 +986,30 @@ void DisplayListBuilder::drawPoints(PointMode mode, // 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(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(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 +1022,23 @@ void DisplayListBuilder::drawImage(const sk_sp image, const SkPoint point, DlImageSampling sampling, bool render_with_attributes) { - render_with_attributes - ? Push(0, 1, image, point, sampling) - : Push(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(0, 1, image, point, sampling) + : Push(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& image, const SkPoint point, @@ -961,14 +1058,17 @@ void DisplayListBuilder::drawImageRect(const sk_sp image, DlImageSampling sampling, bool render_with_attributes, SrcRectConstraint constraint) { - Push(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(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& image, const SkRect& src, @@ -989,15 +1089,18 @@ void DisplayListBuilder::drawImageNine(const sk_sp image, const SkRect& dst, DlFilterMode filter, bool render_with_attributes) { - render_with_attributes - ? Push(0, 1, image, center, dst, filter) - : Push(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(0, 1, image, center, dst, filter) + : Push(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& image, const SkIRect& center, @@ -1021,6 +1124,27 @@ void DisplayListBuilder::drawAtlas(const sk_sp atlas, 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 +1173,8 @@ void DisplayListBuilder::drawAtlas(const sk_sp atlas, // 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& atlas, const SkRSXform xform[], @@ -1089,35 +1198,49 @@ void DisplayListBuilder::DrawAtlas(const sk_sp& atlas, void DisplayListBuilder::DrawDisplayList(const sk_sp display_list, SkScalar opacity) { - DlPaint current_paint = current_; - Push(0, 1, display_list, opacity); - 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 - // for those who may have been using the stateful Dispatcher methods. - SetAttributesFromPaint(current_paint, - DisplayListOpFlags::kSaveLayerWithPaintFlags); - + 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: - AccumulateOpBounds(bounds, kDrawDisplayListFlags); + accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags); break; case BoundsAccumulatorType::kRTree: auto rtree = display_list->rtree(); if (rtree) { std::list rects = rtree->searchAndConsolidateRects(bounds, false); + accumulated = false; for (const SkRect& rect : rects) { // TODO (https://github.com/flutter/flutter/issues/114919): Attributes // are not necessarily `kDrawDisplayListFlags`. - AccumulateOpBounds(rect, kDrawDisplayListFlags); + if (AccumulateOpBounds(rect, kDrawDisplayListFlags)) { + accumulated = true; + } } } else { - AccumulateOpBounds(bounds, kDrawDisplayListFlags); + accumulated = AccumulateOpBounds(bounds, kDrawDisplayListFlags); } break; } + if (!accumulated) { + return; + } + + DlPaint current_paint = current_; + Push(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 + // for those who may have been using the stateful Dispatcher methods. + SetAttributesFromPaint(current_paint, + DisplayListOpFlags::kSaveLayerWithPaintFlags); + // 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 @@ -1127,18 +1250,38 @@ void DisplayListBuilder::DrawDisplayList(const sk_sp display_list, 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 blob, SkScalar x, SkScalar y) { - Push(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(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& blob, SkScalar x, @@ -1152,14 +1295,19 @@ void DisplayListBuilder::DrawShadow(const SkPath& path, const SkScalar elevation, bool transparent_occluder, SkScalar dpr) { - transparent_occluder // - ? Push(0, 1, path, color, elevation, dpr) - : Push(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(0, 1, path, color, elevation, + dpr) + : Push(0, 1, path, color, elevation, dpr); + UpdateLayerOpacityCompatibility(false); + UpdateLayerResult(result); + } + } } bool DisplayListBuilder::ComputeFilteredBounds(SkRect& bounds, @@ -1229,31 +1377,40 @@ bool DisplayListBuilder::AdjustBoundsForPaint(SkRect& bounds, 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; } @@ -1263,8 +1420,8 @@ bool DisplayListBuilder::paint_nops_on_transparency() { // 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; } @@ -1321,4 +1478,130 @@ bool DisplayListBuilder::paint_nops_on_transparency() { 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 1d9cb8eb12299..ab0bce0db3c38 100644 --- a/display_list/dl_builder.h +++ b/display_list/dl_builder.h @@ -506,15 +506,13 @@ class DisplayListBuilder final : public virtual DlCanvas, class LayerInfo { public: - explicit LayerInfo(size_t save_offset = 0, - bool has_layer = false, - std::shared_ptr filter = nullptr) + explicit LayerInfo( + size_t save_offset = 0, + bool has_layer = false, + const std::shared_ptr& 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 @@ class DisplayListBuilder final : public virtual DlCanvas, 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 @@ class DisplayListBuilder final : public virtual DlCanvas, } } + // 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 filter() { return filter_; } @@ -583,11 +590,13 @@ class DisplayListBuilder final : public virtual DlCanvas, 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 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 @@ class DisplayListBuilder final : public virtual DlCanvas, 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 @@ class DisplayListBuilder final : public virtual DlCanvas, // 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 d926e58c3b818..92a39150d2f2e 100644 --- a/display_list/dl_color.h +++ b/display_list/dl_color.h @@ -34,20 +34,20 @@ struct DlColor { 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 @@ struct DlColor { 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 77619a2567164..3d9220f57e3d6 100644 --- a/display_list/dl_paint.h +++ b/display_list/dl_paint.h @@ -83,6 +83,7 @@ class DlPaint { 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 55afd24d806d6..ddf205a98c658 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" @@ -283,20 +284,26 @@ using BackendType = DlSurfaceProvider::BackendType; class RenderResult { public: - explicit RenderResult(const sk_sp& surface) { + explicit RenderResult(const sk_sp& 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 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 image_; SkPixmap pixmap_; void* addr_ = nullptr; }; @@ -912,7 +919,14 @@ class CanvasCompareTester { }; 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 @@ TEST_F(DisplayListCanvas, MatrixColorFilterOpacityCommuteCheck) { } #define FOR_EACH_BLEND_MODE_ENUM(FUNC) \ - FUNC(kSrc) \ FUNC(kClear) \ FUNC(kSrc) \ FUNC(kDst) \ @@ -3816,6 +3829,18 @@ TEST_F(DisplayListCanvas, MatrixColorFilterOpacityCommuteCheck) { 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> environments; for (auto& provider : CanvasCompareTester::kTestProviders) { @@ -3826,7 +3851,8 @@ TEST_F(DisplayListCanvas, BlendColorFilterModifyTransparencyCheck) { 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 @@ TEST_F(DisplayListCanvas, BlendColorFilterOpacityCommuteCheck) { 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 @@ TEST_F(DisplayListCanvas, BlendColorFilterOpacityCommuteCheck) { #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 test_src_colors; + std::vector test_dst_colors; + + std::shared_ptr color_filter_nomtb; + std::shared_ptr color_filter_mtb; + + // A 1-row image containing every color in test_dst_colors + std::unique_ptr test_data; + + // A square image containing test_data duplicated in each row + std::unique_ptr test_image_dst_data; + + // A square image containing test_data duplicated in each column + std::unique_ptr test_image_src_data; + + std::unique_ptr get_output( + int w, + int h, + bool snapshot, + const std::function& renderer) { + auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(w, h)); + SkCanvas* canvas = surface->getCanvas(); + renderer(canvas); + canvas->flush(); + surface->flushAndSubmit(true); + return std::make_unique(surface, snapshot); + } + + int check_color_result(DlColor dst_color, + DlColor result_color, + const sk_sp& 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& dst_data, + const std::unique_ptr& result_data, + const sk_sp& 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& 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(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(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(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(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(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 4479f63c1d678..0a53f0337afd9 100644 --- a/display_list/testing/dl_test_snippets.cc +++ b/display_list/testing/dl_test_snippets.cc @@ -517,7 +517,7 @@ std::vector CreateAllRenderingOps() { }}, {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 1f05e77dc83c6..536cb224e8ef9 100644 --- a/display_list/testing/dl_test_snippets.h +++ b/display_list/testing/dl_test_snippets.h @@ -151,9 +151,9 @@ static const DlBlurImageFilter kTestBlurImageFilter4(5.0, 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 a0d74b85f02af..0f613b2508147 100644 --- a/display_list/utils/dl_matrix_clip_tracker.h +++ b/display_list/utils/dl_matrix_clip_tracker.h @@ -40,6 +40,7 @@ class DisplayListMatrixClipTracker { 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(); @@ -88,9 +89,10 @@ class DisplayListMatrixClipTracker { 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 e981258ef1a03..d8b78a94070c0 100644 --- a/flow/diff_context_unittests.cc +++ b/flow/diff_context_unittests.cc @@ -10,7 +10,7 @@ namespace testing { 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 42317ff9541a2..206be009966a7 100644 --- a/flow/layers/container_layer_unittests.cc +++ b/flow/layers/container_layer_unittests.cc @@ -564,9 +564,9 @@ using ContainerLayerDiffTest = DiffContextTest; // 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 @@ TEST_F(ContainerLayerDiffTest, PictureLayerInsertion) { // 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 fccfe7b4ab044..2791577666b8d 100644 --- a/flow/layers/display_list_layer_unittests.cc +++ b/flow/layers/display_list_layer_unittests.cc @@ -355,7 +355,7 @@ TEST_F(DisplayListLayerTest, RasterCachePreservesRTree) { 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)); @@ -375,7 +375,7 @@ TEST_F(DisplayListLayerDiffTest, SimpleDisplayList) { } 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( @@ -388,7 +388,7 @@ TEST_F(DisplayListLayerDiffTest, FractionalTranslation) { } 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( @@ -402,21 +402,25 @@ TEST_F(DisplayListLayerDiffTest, FractionalTranslationWithRasterCache) { 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))); @@ -426,7 +430,8 @@ TEST_F(DisplayListLayerDiffTest, DisplayListCompare) { 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 ca1f1067e87c3..71ccdc15c8476 100644 --- a/flow/layers/opacity_layer_unittests.cc +++ b/flow/layers/opacity_layer_unittests.cc @@ -659,7 +659,7 @@ using OpacityLayerDiffTest = DiffContextTest; 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, FractionalTranslation) { 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 20153a0140d5c..c4c68bb7ab271 100644 --- a/flow/testing/diff_context_test.cc +++ b/flow/testing/diff_context_test.cc @@ -30,7 +30,7 @@ Damage DiffContextTest::DiffLayerTree(MockLayerTree& layer_tree, } sk_sp 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 297a2bdf5f7f8..be10a01b1f7a5 100644 --- a/flow/testing/diff_context_test.h +++ b/flow/testing/diff_context_test.h @@ -45,7 +45,8 @@ class DiffContextTest : public LayerTest { // 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 CreateDisplayList(const SkRect& bounds, uint32_t color); + sk_sp CreateDisplayList(const SkRect& bounds, + DlColor color = DlColor::kBlack()); std::shared_ptr CreateDisplayListLayer( const sk_sp& display_list, diff --git a/impeller/display_list/dl_unittests.cc b/impeller/display_list/dl_unittests.cc index 05a0b5d9b072b..a445684c8b1b4 100644 --- a/impeller/display_list/dl_unittests.cc +++ b/impeller/display_list/dl_unittests.cc @@ -856,18 +856,12 @@ TEST_P(DisplayListTest, CanDrawShadow) { } 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 rrect_blur; diff --git a/shell/common/dl_op_spy_unittests.cc b/shell/common/dl_op_spy_unittests.cc index dba02134238c8..7aaac1cbe52a8 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 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 @@ TEST(DlOpSpy, SetColor) { sk_sp 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 @@ TEST(DlOpSpy, SetColor) { sk_sp 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 @@ TEST(DlOpSpy, SetColor) { sk_sp 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 @@ TEST(DlOpSpy, SetColorSource) { sk_sp 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 @@ TEST(DlOpSpy, SetColorSource) { sk_sp 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 @@ TEST(DlOpSpy, SetColorSource) { sk_sp 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 @@ TEST(DlOpSpy, DrawColor) { sk_sp 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 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 dl = builder.Build(); + DlOpSpy dl_op_spy; + dl->Dispatch(dl_op_spy); + ASSERT_NO_DRAW(dl_op_spy, dl); } } @@ -112,7 +146,7 @@ TEST(DlOpSpy, DrawPaint) { sk_sp 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 @@ TEST(DlOpSpy, DrawPaint) { sk_sp 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 @@ TEST(DlOpSpy, DrawLine) { sk_sp 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 @@ TEST(DlOpSpy, DrawLine) { sk_sp 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 @@ TEST(DlOpSpy, DrawRect) { sk_sp 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 @@ TEST(DlOpSpy, DrawRect) { sk_sp 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 @@ TEST(DlOpSpy, drawOval) { sk_sp 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 @@ TEST(DlOpSpy, drawOval) { sk_sp 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 @@ TEST(DlOpSpy, drawCircle) { sk_sp 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 @@ TEST(DlOpSpy, drawCircle) { sk_sp 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 @@ TEST(DlOpSpy, drawRRect) { sk_sp 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 @@ TEST(DlOpSpy, drawRRect) { sk_sp 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 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 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 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 @@ TEST(DlOpSpy, drawArc) { sk_sp 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 @@ TEST(DlOpSpy, drawArc) { sk_sp 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 @@ TEST(DlOpSpy, drawPoints) { sk_sp 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 @@ TEST(DlOpSpy, drawPoints) { sk_sp 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 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 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 @@ TEST(DlOpSpy, Images) { sk_sp 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 @@ TEST(DlOpSpy, Images) { sk_sp 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 @@ TEST(DlOpSpy, Images) { sk_sp 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 @@ TEST(DlOpSpy, Images) { 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 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 @@ TEST(DlOpSpy, drawDisplayList) { 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 @@ TEST(DlOpSpy, drawDisplayList) { 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 @@ TEST(DlOpSpy, drawDisplayList) { 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 @@ TEST(DlOpSpy, drawDisplayList) { 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 @@ TEST(DlOpSpy, drawTextBlob) { sk_sp 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 @@ TEST(DlOpSpy, drawTextBlob) { sk_sp 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 @@ TEST(DlOpSpy, drawShadow) { sk_sp 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 @@ TEST(DlOpSpy, drawShadow) { sk_sp 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); } }