Reland "add non-rendering operation culling to DisplayListBuilder" (#41463) (#42330)

The original PR caused some golden test failures down the line, likely due to bad analysis of when the combined BlendMode and color would result in a NOP situation.

This PR adds tests that go through every BlandMode and pair it with a variety of colors and Color/ImageFilters to verify that the operations are only omitted when they actually produce no change in the output. It also checks the validity of the "modifies_transparent_black" property of DisplayLists which can be used in place of the current CanvasSpy/DlOpSpy classes.

The description from the [previous PR](https://github.com/flutter/engine/pull/41463) updated with the new name of the DL property:

---------------------------------
This optimization avoids recording unnecessary render operations that will not affect the output and also eliminates the need for "draw detection" mechanisms like `DlOpSpy` and `CanvasSpy` by remembering if any non-transparent operations were included. The `DlOpSpy` unit tests were updated to check if the results from that object match the new `DisplayList::modifies_transparent_black()` method.

Fixes https://github.com/flutter/flutter/issues/125338

In addition, this change will unblock some other Issues:

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