Reland "Sequester all Skia<->DL interactions into the skia sub-module" (#40435)

Reland "Sequester all Skia<->DL interactions into the skia sub-module"
diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files
index 063e8c3..7d5658d 100644
--- a/ci/licenses_golden/excluded_files
+++ b/ci/licenses_golden/excluded_files
@@ -29,12 +29,10 @@
 ../../../flutter/build
 ../../../flutter/ci
 ../../../flutter/common/README.md
-../../../flutter/display_list/display_list_canvas_unittests.cc
 ../../../flutter/display_list/display_list_color_filter_unittests.cc
 ../../../flutter/display_list/display_list_color_source_unittests.cc
 ../../../flutter/display_list/display_list_color_unittests.cc
 ../../../flutter/display_list/display_list_complexity_unittests.cc
-../../../flutter/display_list/display_list_enum_unittests.cc
 ../../../flutter/display_list/display_list_image_filter_unittests.cc
 ../../../flutter/display_list/display_list_mask_filter_unittests.cc
 ../../../flutter/display_list/display_list_matrix_clip_tracker_unittests.cc
@@ -42,8 +40,10 @@
 ../../../flutter/display_list/display_list_path_effect_unittests.cc
 ../../../flutter/display_list/display_list_rtree_unittests.cc
 ../../../flutter/display_list/display_list_unittests.cc
-../../../flutter/display_list/display_list_utils_unittests.cc
 ../../../flutter/display_list/display_list_vertices_unittests.cc
+../../../flutter/display_list/dl_rendering_unittests.cc
+../../../flutter/display_list/skia/dl_sk_conversions_unittests.cc
+../../../flutter/display_list/skia/dl_sk_utils_unittests.cc
 ../../../flutter/display_list/testing
 ../../../flutter/docs
 ../../../flutter/examples
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index dc931e8..5a60d16 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -714,8 +714,6 @@
 ORIGIN: ../../../flutter/display_list/display_list_builder.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_builder.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_builder_benchmarks.cc + ../../../flutter/LICENSE
-ORIGIN: ../../../flutter/display_list/display_list_canvas_dispatcher.cc + ../../../flutter/LICENSE
-ORIGIN: ../../../flutter/display_list/display_list_canvas_dispatcher.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_color.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_color_filter.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_color_filter.h + ../../../flutter/LICENSE
@@ -729,8 +727,6 @@
 ORIGIN: ../../../flutter/display_list/display_list_complexity_helper.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_complexity_metal.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_complexity_metal.h + ../../../flutter/LICENSE
-ORIGIN: ../../../flutter/display_list/display_list_dispatcher.cc + ../../../flutter/LICENSE
-ORIGIN: ../../../flutter/display_list/display_list_dispatcher.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_flags.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_flags.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_image.cc + ../../../flutter/LICENSE
@@ -759,9 +755,18 @@
 ORIGIN: ../../../flutter/display_list/display_list_utils.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_vertices.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/display_list_vertices.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/display_list/dl_canvas.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/dl_canvas.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/display_list/dl_op_receiver.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/display_list/dl_op_receiver.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/skia/dl_sk_canvas.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/skia/dl_sk_canvas.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/display_list/skia/dl_sk_conversions.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/display_list/skia/dl_sk_conversions.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/display_list/skia/dl_sk_dispatcher.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/display_list/skia/dl_sk_dispatcher.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/display_list/skia/dl_sk_utils.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/display_list/skia/dl_sk_utils.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/display_list/types.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/flow/compositor_context.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/flow/compositor_context.h + ../../../flutter/LICENSE
@@ -3245,8 +3250,6 @@
 FILE: ../../../flutter/display_list/display_list_builder.cc
 FILE: ../../../flutter/display_list/display_list_builder.h
 FILE: ../../../flutter/display_list/display_list_builder_benchmarks.cc
-FILE: ../../../flutter/display_list/display_list_canvas_dispatcher.cc
-FILE: ../../../flutter/display_list/display_list_canvas_dispatcher.h
 FILE: ../../../flutter/display_list/display_list_color.h
 FILE: ../../../flutter/display_list/display_list_color_filter.cc
 FILE: ../../../flutter/display_list/display_list_color_filter.h
@@ -3260,8 +3263,6 @@
 FILE: ../../../flutter/display_list/display_list_complexity_helper.h
 FILE: ../../../flutter/display_list/display_list_complexity_metal.cc
 FILE: ../../../flutter/display_list/display_list_complexity_metal.h
-FILE: ../../../flutter/display_list/display_list_dispatcher.cc
-FILE: ../../../flutter/display_list/display_list_dispatcher.h
 FILE: ../../../flutter/display_list/display_list_flags.cc
 FILE: ../../../flutter/display_list/display_list_flags.h
 FILE: ../../../flutter/display_list/display_list_image.cc
@@ -3290,9 +3291,18 @@
 FILE: ../../../flutter/display_list/display_list_utils.h
 FILE: ../../../flutter/display_list/display_list_vertices.cc
 FILE: ../../../flutter/display_list/display_list_vertices.h
+FILE: ../../../flutter/display_list/dl_canvas.cc
 FILE: ../../../flutter/display_list/dl_canvas.h
+FILE: ../../../flutter/display_list/dl_op_receiver.cc
+FILE: ../../../flutter/display_list/dl_op_receiver.h
 FILE: ../../../flutter/display_list/skia/dl_sk_canvas.cc
 FILE: ../../../flutter/display_list/skia/dl_sk_canvas.h
+FILE: ../../../flutter/display_list/skia/dl_sk_conversions.cc
+FILE: ../../../flutter/display_list/skia/dl_sk_conversions.h
+FILE: ../../../flutter/display_list/skia/dl_sk_dispatcher.cc
+FILE: ../../../flutter/display_list/skia/dl_sk_dispatcher.h
+FILE: ../../../flutter/display_list/skia/dl_sk_utils.cc
+FILE: ../../../flutter/display_list/skia/dl_sk_utils.h
 FILE: ../../../flutter/display_list/types.h
 FILE: ../../../flutter/flow/compositor_context.cc
 FILE: ../../../flutter/flow/compositor_context.h
diff --git a/display_list/BUILD.gn b/display_list/BUILD.gn
index 7af433a..797e4cc 100644
--- a/display_list/BUILD.gn
+++ b/display_list/BUILD.gn
@@ -24,8 +24,6 @@
     "display_list_blend_mode.h",
     "display_list_builder.cc",
     "display_list_builder.h",
-    "display_list_canvas_dispatcher.cc",
-    "display_list_canvas_dispatcher.h",
     "display_list_color.h",
     "display_list_color_filter.cc",
     "display_list_color_filter.h",
@@ -37,8 +35,6 @@
     "display_list_complexity_gl.h",
     "display_list_complexity_metal.cc",
     "display_list_complexity_metal.h",
-    "display_list_dispatcher.cc",
-    "display_list_dispatcher.h",
     "display_list_flags.cc",
     "display_list_flags.h",
     "display_list_image.cc",
@@ -67,9 +63,18 @@
     "display_list_utils.h",
     "display_list_vertices.cc",
     "display_list_vertices.h",
+    "dl_canvas.cc",
     "dl_canvas.h",
+    "dl_op_receiver.cc",
+    "dl_op_receiver.h",
     "skia/dl_sk_canvas.cc",
     "skia/dl_sk_canvas.h",
+    "skia/dl_sk_conversions.cc",
+    "skia/dl_sk_conversions.h",
+    "skia/dl_sk_dispatcher.cc",
+    "skia/dl_sk_dispatcher.h",
+    "skia/dl_sk_utils.cc",
+    "skia/dl_sk_utils.h",
     "types.h",
   ]
 
@@ -99,7 +104,6 @@
       "display_list_color_source_unittests.cc",
       "display_list_color_unittests.cc",
       "display_list_complexity_unittests.cc",
-      "display_list_enum_unittests.cc",
       "display_list_image_filter_unittests.cc",
       "display_list_mask_filter_unittests.cc",
       "display_list_matrix_clip_tracker_unittests.cc",
@@ -107,8 +111,9 @@
       "display_list_path_effect_unittests.cc",
       "display_list_rtree_unittests.cc",
       "display_list_unittests.cc",
-      "display_list_utils_unittests.cc",
       "display_list_vertices_unittests.cc",
+      "skia/dl_sk_conversions_unittests.cc",
+      "skia/dl_sk_utils_unittests.cc",
     ]
 
     deps = [
@@ -136,7 +141,7 @@
   executable("display_list_rendertests") {
     testonly = true
 
-    sources = [ "display_list_canvas_unittests.cc" ]
+    sources = [ "dl_rendering_unittests.cc" ]
 
     deps = [
       ":display_list",
diff --git a/display_list/display_list.cc b/display_list/display_list.cc
index defde8b..5ac4ea5 100644
--- a/display_list/display_list.cc
+++ b/display_list/display_list.cc
@@ -6,7 +6,6 @@
 
 #include "flutter/display_list/display_list.h"
 #include "flutter/display_list/display_list_builder.h"
-#include "flutter/display_list/display_list_canvas_dispatcher.h"
 #include "flutter/display_list/display_list_ops.h"
 #include "flutter/fml/trace_event.h"
 
@@ -134,38 +133,40 @@
   std::vector<int>::const_iterator end_;
 };
 
-void DisplayList::Dispatch(Dispatcher& ctx) const {
+void DisplayList::Dispatch(DlOpReceiver& receiver) const {
   uint8_t* ptr = storage_.get();
-  Dispatch(ctx, ptr, ptr + byte_count_, NopCuller::instance);
+  Dispatch(receiver, ptr, ptr + byte_count_, NopCuller::instance);
 }
-void DisplayList::Dispatch(Dispatcher& ctx, const SkRect& cull_rect) const {
+
+void DisplayList::Dispatch(DlOpReceiver& receiver,
+                           const SkRect& cull_rect) const {
   if (cull_rect.isEmpty()) {
     return;
   }
   if (cull_rect.contains(bounds())) {
-    Dispatch(ctx);
+    Dispatch(receiver);
     return;
   }
   const DlRTree* rtree = this->rtree().get();
   FML_DCHECK(rtree != nullptr);
   if (rtree == nullptr) {
     FML_LOG(ERROR) << "dispatched with culling rect on DL with no rtree";
-    Dispatch(ctx);
+    Dispatch(receiver);
     return;
   }
   uint8_t* ptr = storage_.get();
   std::vector<int> rect_indices;
   rtree->search(cull_rect, &rect_indices);
   VectorCuller culler(rtree, rect_indices);
-  Dispatch(ctx, ptr, ptr + byte_count_, culler);
+  Dispatch(receiver, ptr, ptr + byte_count_, culler);
 }
 
-void DisplayList::Dispatch(Dispatcher& dispatcher,
+void DisplayList::Dispatch(DlOpReceiver& receiver,
                            uint8_t* ptr,
                            uint8_t* end,
                            Culler& culler) const {
   DispatchContext context = {
-      .dispatcher = dispatcher,
+      .receiver = receiver,
       .cur_index = 0,
       // next_render_index will be initialized by culler.init()
       .next_restore_index = std::numeric_limits<int>::max(),
@@ -294,27 +295,6 @@
   return true;
 }
 
-void DisplayList::RenderTo(DisplayListBuilder* builder) const {
-  if (!builder) {
-    return;
-  }
-  if (has_rtree()) {
-    Dispatch(builder->asDispatcher(), builder->GetLocalClipBounds());
-  } else {
-    Dispatch(builder->asDispatcher());
-  }
-}
-
-void DisplayList::RenderTo(SkCanvas* canvas, SkScalar opacity) const {
-  FML_DCHECK(can_apply_group_opacity() || opacity >= SK_Scalar1);
-  DisplayListCanvasDispatcher dispatcher(canvas, opacity);
-  if (has_rtree()) {
-    Dispatch(dispatcher, canvas->getLocalClipBounds());
-  } else {
-    Dispatch(dispatcher);
-  }
-}
-
 bool DisplayList::Equals(const DisplayList* other) const {
   if (this == other) {
     return true;
diff --git a/display_list/display_list.h b/display_list/display_list.h
index dde2676..a66040a 100644
--- a/display_list/display_list.h
+++ b/display_list/display_list.h
@@ -18,41 +18,36 @@
 //
 // This file contains the definitions for:
 // DisplayList: the base class that holds the information about the
-//              sequence of operations and can dispatch them to a Dispatcher
-// Dispatcher: a pure virtual interface which can be implemented to field
-//             the requests for purposes such as sending them to an SkCanvas
-//             or detecting various rendering optimization scenarios
-// DisplayListBuilder: a class for constructing a DisplayList from the same
-//                     calls defined in the Dispatcher
+//              sequence of operations and can dispatch them to a DlOpReceiver
+// DlOpReceiver: a pure virtual interface which can be implemented to field
+//               the requests for purposes such as sending them to an SkCanvas
+//               or detecting various rendering optimization scenarios
+// DisplayListBuilder: a class for constructing a DisplayList from DlCanvas
+//                     method calls and which can act as a DlOpReceiver as well
 //
 // Other files include various class definitions for dealing with display
 // lists, such as:
-// display_list_canvas.h: classes to interact between SkCanvas and DisplayList
-//                        (SkCanvas->DisplayList adapter and vice versa)
+// skia/dl_sk_*.h: classes to interact between SkCanvas and DisplayList
+//                 (SkCanvas->DisplayList adapter and vice versa)
 //
 // display_list_utils.h: various utility classes to ease implementing
-//                       a Dispatcher, including NOP implementations of
+//                       a DlOpReceiver, including NOP implementations of
 //                       the attribute, clip, and transform methods,
 //                       classes to track attributes, clips, and transforms
 //                       and a class to compute the bounds of a DisplayList
-//                       Any class implementing Dispatcher can inherit from
+//                       Any class implementing DlOpReceiver can inherit from
 //                       these utility classes to simplify its creation
 //
 // The Flutter DisplayList mechanism is used in a similar manner to the Skia
-// SkPicture mechanism. The primary means of communication into and out
-// of the DisplayList is through the Dispatcher virtual class which
-// provides a nearly 1:1 translation between the records of the DisplayList
-// to method calls.
+// SkPicture mechanism.
 //
-// A DisplayList must be created using a DisplayListBuilder using either its
-// stateful methods inherited from Dispatcher, or from its stateless methods
-// inherited from DlCanvas.
+// A DisplayList must be created using a DisplayListBuilder using its stateless
+// methods inherited from DlCanvas.
 //
-// A DisplayList can be read back by implementing the Dispatcher virtual
+// A DisplayList can be read back by implementing the DlOpReceiver virtual
 // methods (with help from some of the classes in the utils file) and
-// passing an instance to the dispatch() method, or it can be rendered
-// to Skia using a DisplayListCanvasDispatcher or simply by passing an
-// SkCanvas pointer to its renderTo() method.
+// passing an instance to the Dispatch() method, or it can be rendered
+// to Skia using a DlSkCanvasDispatcher.
 //
 // The mechanism is inspired by the SkLiteDL class that is not directly
 // supported by Skia, but has been recommended as a basis for custom
@@ -155,7 +150,7 @@
 };
 #undef DL_OP_TO_ENUM_VALUE
 
-class Dispatcher;
+class DlOpReceiver;
 class DisplayListBuilder;
 
 class SaveLayerOptions {
@@ -231,7 +226,7 @@
 class Culler;
 
 // The base class that contains a sequence of rendering operations
-// for dispatch to a Dispatcher. These objects must be instantiated
+// for dispatch to a DlOpReceiver. These objects must be instantiated
 // through an instance of DisplayListBuilder::build().
 class DisplayList : public SkRefCnt {
  public:
@@ -239,12 +234,8 @@
 
   ~DisplayList();
 
-  void Dispatch(Dispatcher& ctx) const;
-  void Dispatch(Dispatcher& ctx, const SkRect& cull_rect) const;
-
-  void RenderTo(DisplayListBuilder* builder) const;
-
-  void RenderTo(SkCanvas* canvas, SkScalar opacity = SK_Scalar1) const;
+  void Dispatch(DlOpReceiver& ctx) const;
+  void Dispatch(DlOpReceiver& ctx, const SkRect& cull_rect) const;
 
   // From historical behavior, SkPicture always included nested bytes,
   // but nested ops are only included if requested. The defaults used
@@ -300,7 +291,7 @@
   const bool can_apply_group_opacity_;
   const sk_sp<const DlRTree> rtree_;
 
-  void Dispatch(Dispatcher& ctx,
+  void Dispatch(DlOpReceiver& ctx,
                 uint8_t* ptr,
                 uint8_t* end,
                 Culler& culler) const;
diff --git a/display_list/display_list_attributes.h b/display_list/display_list_attributes.h
index 051e4ef..aa10863 100644
--- a/display_list/display_list_attributes.h
+++ b/display_list/display_list_attributes.h
@@ -36,10 +36,6 @@
 //     wrong type of instance.
 //     (eg. DlColorFilter::asBlend() or DlMaskFilter::asBlur())
 //
-// - Skiafiable:
-//     The classes override an |skia_object| method to easily obtain a Skia
-//     version of the attribute on demand.
-//
 // - Immutable:
 //     Neither the base class or any of the subclasses specify any mutation
 //     methods. Instances are often passed around as const as a reminder,
@@ -56,7 +52,7 @@
 //     compared using a |memcmp| when performing a |DisplayList::Equals|.
 //
 // - Passed by Pointer:
-//     The data shared via the |Dispatcher::set<Attribute>| calls are stored
+//     The data shared via the |DlOpReceiver::set<Attribute>| calls are stored
 //     in the buffer itself and so their lifetime is controlled by the
 //     DisplayList. That memory cannot be shared as by a |shared_ptr|
 //     because the memory may be freed outside the control of the shared
@@ -70,7 +66,7 @@
 // - Shared_Ptr-able:
 //     The classes support a method to return a |std::shared_ptr| version of
 //     themselves, safely instantiating a new copy of the object into a
-//     shared_ptr using |std::make_shared|. For those dispatcher objects
+//     shared_ptr using |std::make_shared|. For those receiver objects
 //     that may want to hold on to the contents of the object (typically
 //     in a |current_attribute_| field), they can obtain a shared_ptr
 //     copy safely and easily using the |shared| method.
@@ -79,11 +75,9 @@
 
 // |D| is the base type for the attribute
 //     (i.e. DlColorFilter, etc.)
-// |S| is the base type for the Skia version of the attribute
-//     (i.e. SkColorFilter, etc.)
 // |T| is the enum that describes the specific subclasses
 //     (i.e DlColorFilterType, etc.)
-template <class D, class S, typename T>
+template <class D, typename T>
 class DlAttribute {
  public:
   // Return the recognized specific type of the attribute.
@@ -98,9 +92,6 @@
   // version is not tied to the storage of this particular instance.
   virtual std::shared_ptr<D> shared() const = 0;
 
-  // Return an equivalent sk_sp<Skia> version of this object.
-  virtual sk_sp<S> skia_object() const = 0;
-
   // Perform a content aware |==| comparison of the Attribute.
   bool operator==(D const& other) const {
     return type() == other.type() && equals_(other);
diff --git a/display_list/display_list_benchmarks.cc b/display_list/display_list_benchmarks.cc
index d7f7638..b9644ec 100644
--- a/display_list/display_list_benchmarks.cc
+++ b/display_list/display_list_benchmarks.cc
@@ -5,6 +5,7 @@
 #include "flutter/display_list/display_list_benchmarks.h"
 #include "flutter/display_list/display_list_builder.h"
 #include "flutter/display_list/display_list_flags.h"
+#include "flutter/display_list/skia/dl_sk_canvas.h"
 
 #include "third_party/skia/include/core/SkPoint.h"
 #include "third_party/skia/include/core/SkSurface.h"
@@ -76,27 +77,27 @@
                  unsigned attributes) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawLineFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawLineFlags);
 
   size_t length = state.range(0);
 
   surface_provider->InitializeSurface(length, length);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   state.counters["DrawCallCount"] = kLinesToDraw;
   for (size_t i = 0; i < kLinesToDraw; i++) {
-    builder.drawLine(SkPoint::Make(i % length, 0),
-                     SkPoint::Make(length - i % length, length));
+    builder.DrawLine(SkPoint::Make(i % length, 0),
+                     SkPoint::Make(length - i % length, length), paint);
   }
 
   auto display_list = builder.Build();
 
   // We only want to time the actual rasterization.
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -114,15 +115,15 @@
                  unsigned attributes) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawRectFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawRectFlags);
 
   size_t length = state.range(0);
   size_t canvas_size = length * 2;
   surface_provider->InitializeSurface(canvas_size, canvas_size);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   // As rects have SkScalar dimensions, we want to ensure that we also
   // draw rects with non-integer position and size
@@ -131,7 +132,7 @@
 
   state.counters["DrawCallCount"] = kRectsToDraw;
   for (size_t i = 0; i < kRectsToDraw; i++) {
-    builder.drawRect(rect);
+    builder.DrawRect(rect, paint);
     rect.offset(offset, offset);
     if (rect.right() > canvas_size) {
       rect.offset(-canvas_size, 0);
@@ -145,7 +146,7 @@
 
   // We only want to time the actual rasterization.
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -163,22 +164,22 @@
                  unsigned attributes) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawOvalFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawOvalFlags);
 
   size_t length = state.range(0);
   size_t canvas_size = length * 2;
   surface_provider->InitializeSurface(canvas_size, canvas_size);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   SkRect rect = SkRect::MakeXYWH(0, 0, length * 1.5f, length);
   const SkScalar offset = 0.5f;
 
   state.counters["DrawCallCount"] = kOvalsToDraw;
   for (size_t i = 0; i < kOvalsToDraw; i++) {
-    builder.drawOval(rect);
+    builder.DrawOval(rect, paint);
     rect.offset(offset, offset);
     if (rect.right() > canvas_size) {
       rect.offset(-canvas_size, 0);
@@ -191,7 +192,7 @@
 
   // We only want to time the actual rasterization.
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -209,15 +210,15 @@
                    unsigned attributes) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawCircleFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawCircleFlags);
 
   size_t length = state.range(0);
   size_t canvas_size = length * 2;
   surface_provider->InitializeSurface(canvas_size, canvas_size);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   SkScalar radius = length / 2.0f;
   const SkScalar offset = 0.5f;
@@ -226,7 +227,7 @@
 
   state.counters["DrawCallCount"] = kCirclesToDraw;
   for (size_t i = 0; i < kCirclesToDraw; i++) {
-    builder.drawCircle(center, radius);
+    builder.DrawCircle(center, radius, paint);
     center.offset(offset, offset);
     if (center.x() + radius > canvas_size) {
       center.set(radius, center.y());
@@ -239,7 +240,7 @@
 
   // We only want to time the actual rasterization.
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -258,15 +259,15 @@
                   SkRRect::Type type) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawRRectFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawRRectFlags);
 
   size_t length = state.range(0);
   size_t canvas_size = length * 2;
   surface_provider->InitializeSurface(canvas_size, canvas_size);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   SkVector radii[4] = {};
   switch (type) {
@@ -304,7 +305,7 @@
 
   state.counters["DrawCallCount"] = kRRectsToDraw;
   for (size_t i = 0; i < kRRectsToDraw; i++) {
-    builder.drawRRect(rrect);
+    builder.DrawRRect(rrect, paint);
     rrect.offset(offset, offset);
     if (rrect.rect().right() > canvas_size) {
       rrect.offset(-canvas_size, 0);
@@ -317,7 +318,7 @@
 
   // We only want to time the actual rasterization.
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -339,15 +340,15 @@
                    SkRRect::Type type) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawDRRectFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawDRRectFlags);
 
   size_t length = state.range(0);
   size_t canvas_size = length * 2;
   surface_provider->InitializeSurface(canvas_size, canvas_size);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   SkVector radii[4] = {};
   switch (type) {
@@ -386,7 +387,7 @@
   state.counters["DrawCallCount"] = kDRRectsToDraw;
   for (size_t i = 0; i < kDRRectsToDraw; i++) {
     rrect.inset(0.1f * length, 0.1f * length, &rrect_2);
-    builder.drawDRRect(rrect, rrect_2);
+    builder.DrawDRRect(rrect, rrect_2, paint);
     rrect.offset(offset, offset);
     if (rrect.rect().right() > canvas_size) {
       rrect.offset(-canvas_size, 0);
@@ -399,7 +400,7 @@
 
   // We only want to time the actual rasterization.
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -413,8 +414,8 @@
                 unsigned attributes) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawArcNoCenterFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state,
                      DisplayListOpFlags::kDrawArcNoCenterFlags);
 
@@ -422,7 +423,7 @@
   size_t canvas_size = length * 2;
   surface_provider->InitializeSurface(canvas_size, canvas_size);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   SkScalar starting_angle = 0.0f;
   SkScalar offset = 0.5f;
@@ -436,7 +437,7 @@
   state.counters["DrawCallCount"] = kArcSweepSetsToDraw * segment_sweeps.size();
   for (size_t i = 0; i < kArcSweepSetsToDraw; i++) {
     for (SkScalar sweep : segment_sweeps) {
-      builder.drawArc(bounds, starting_angle, sweep, false);
+      builder.DrawArc(bounds, starting_angle, sweep, false, paint);
       starting_angle += sweep + 5.0f;
     }
     bounds.offset(offset, offset);
@@ -452,7 +453,7 @@
 
   // We only want to time the actual rasterization.
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -629,14 +630,14 @@
                  SkPath::Verb type) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawPathFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawPathFlags);
 
   size_t length = kFixedCanvasSize;
   surface_provider->InitializeSurface(length, length);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   SkPath path;
 
@@ -650,12 +651,12 @@
   state.counters["VerbCount"] = path.countVerbs();
   state.counters["DrawCallCount"] = 1;
 
-  builder.drawPath(path);
+  builder.DrawPath(path, paint);
   auto display_list = builder.Build();
 
   // We only want to time the actual rasterization.
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -767,14 +768,14 @@
                      DlVertexMode mode) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawVerticesFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawVerticesFlags);
 
   size_t length = kFixedCanvasSize;
   surface_provider->InitializeSurface(length, length);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   SkPoint center = SkPoint::Make(length / 2.0f, length / 2.0f);
 
@@ -791,7 +792,7 @@
     std::shared_ptr<DlVertices> vertices =
         GetTestVertices(p, radius, 50, mode, vertex_count);
     total_vertex_count += vertex_count;
-    builder.drawVertices(vertices, DlBlendMode::kSrc);
+    builder.DrawVertices(vertices.get(), DlBlendMode::kSrc, paint);
   }
 
   state.counters["VertexCount"] = total_vertex_count;
@@ -801,7 +802,7 @@
 
   // We only want to time the actual rasterization.
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -863,26 +864,18 @@
                    DlCanvas::PointMode mode) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  SkPaint paint;
+  DlPaint paint = GetPaintForRun(attributes);
+
   switch (mode) {
     case DlCanvas::PointMode::kPoints:
-      builder.SetAttributesFromPaint(
-          GetPaintForRun(attributes),
-          DisplayListOpFlags::kDrawPointsAsPointsFlags);
       AnnotateAttributes(attributes, state,
                          DisplayListOpFlags::kDrawPointsAsPointsFlags);
       break;
     case DlCanvas::PointMode::kLines:
-      builder.SetAttributesFromPaint(
-          GetPaintForRun(attributes),
-          DisplayListOpFlags::kDrawPointsAsLinesFlags);
       AnnotateAttributes(attributes, state,
                          DisplayListOpFlags::kDrawPointsAsLinesFlags);
       break;
     case DlCanvas::PointMode::kPolygon:
-      builder.SetAttributesFromPaint(
-          GetPaintForRun(attributes),
-          DisplayListOpFlags::kDrawPointsAsPolygonFlags);
       AnnotateAttributes(attributes, state,
                          DisplayListOpFlags::kDrawPointsAsPolygonFlags);
       break;
@@ -891,7 +884,7 @@
   size_t length = kFixedCanvasSize;
   surface_provider->InitializeSurface(length, length);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   size_t point_count = state.range(0);
   state.SetComplexityN(point_count);
@@ -900,12 +893,12 @@
 
   std::vector<SkPoint> points =
       GetTestPoints(point_count, SkISize::Make(length, length));
-  builder.drawPoints(mode, points.size(), points.data());
+  builder.DrawPoints(mode, points.size(), points.data(), paint);
 
   auto display_list = builder.Build();
 
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -933,8 +926,8 @@
                   bool upload_bitmap) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawImageWithPaintFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state,
                      DisplayListOpFlags::kDrawImageWithPaintFlags);
 
@@ -942,7 +935,7 @@
   size_t canvas_size = 2 * bitmap_size;
   surface_provider->InitializeSurface(canvas_size, canvas_size);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   sk_sp<SkImage> image;
   std::shared_ptr<DlSurfaceInstance> offscreen_instance;
@@ -969,7 +962,7 @@
   for (size_t i = 0; i < kImagesToDraw; i++) {
     image = upload_bitmap ? ImageFromBitmapWithNewID(bitmap)
                           : offscreen->makeImageSnapshot();
-    builder.drawImage(DlImage::Make(image), dst, options, true);
+    builder.DrawImage(DlImage::Make(image), dst, options, &paint);
 
     dst.offset(offset, offset);
     if (dst.x() + bitmap_size > canvas_size) {
@@ -983,7 +976,7 @@
   auto display_list = builder.Build();
 
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -993,11 +986,11 @@
   surface_provider->Snapshot(filename);
 }
 
-std::string ConstraintToString(SkCanvas::SrcRectConstraint constraint) {
+std::string ConstraintToString(DlCanvas::SrcRectConstraint constraint) {
   switch (constraint) {
-    case SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint:
+    case DlCanvas::SrcRectConstraint::kStrict:
       return "Strict";
-    case SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint:
+    case DlCanvas::SrcRectConstraint::kFast:
       return "Fast";
     default:
       return "Unknown";
@@ -1012,13 +1005,12 @@
                       BackendType backend_type,
                       unsigned attributes,
                       DlImageSampling options,
-                      SkCanvas::SrcRectConstraint constraint,
+                      DlCanvas::SrcRectConstraint constraint,
                       bool upload_bitmap) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(
-      GetPaintForRun(attributes),
-      DisplayListOpFlags::kDrawImageRectWithPaintFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state,
                      DisplayListOpFlags::kDrawImageRectWithPaintFlags);
 
@@ -1026,7 +1018,7 @@
   size_t canvas_size = 2 * bitmap_size;
   surface_provider->InitializeSurface(canvas_size, canvas_size);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   sk_sp<SkImage> image;
   std::shared_ptr<DlSurfaceInstance> offscreen_instance;
@@ -1056,7 +1048,7 @@
   for (size_t i = 0; i < kImagesToDraw; i++) {
     image = upload_bitmap ? ImageFromBitmapWithNewID(bitmap)
                           : offscreen->makeImageSnapshot();
-    builder.drawImageRect(DlImage::Make(image), src, dst, options, true,
+    builder.DrawImageRect(DlImage::Make(image), src, dst, options, &paint,
                           constraint);
     dst.offset(offset, offset);
     if (dst.right() > canvas_size) {
@@ -1070,7 +1062,7 @@
   auto display_list = builder.Build();
 
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -1104,9 +1096,8 @@
                       bool upload_bitmap) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(
-      GetPaintForRun(attributes),
-      DisplayListOpFlags::kDrawImageNineWithPaintFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state,
                      DisplayListOpFlags::kDrawImageNineWithPaintFlags);
 
@@ -1114,7 +1105,7 @@
   size_t canvas_size = 2 * bitmap_size;
   surface_provider->InitializeSurface(canvas_size, canvas_size);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   SkIRect center = SkIRect::MakeXYWH(bitmap_size / 4, bitmap_size / 4,
                                      bitmap_size / 2, bitmap_size / 2);
@@ -1145,7 +1136,7 @@
   for (size_t i = 0; i < kImagesToDraw; i++) {
     image = upload_bitmap ? ImageFromBitmapWithNewID(bitmap)
                           : offscreen->makeImageSnapshot();
-    builder.drawImageNine(DlImage::Make(image), center, dst, filter, true);
+    builder.DrawImageNine(DlImage::Make(image), center, dst, filter, &paint);
     dst.offset(offset, offset);
     if (dst.right() > canvas_size) {
       dst.offsetTo(0, dst.y());
@@ -1158,7 +1149,7 @@
   auto display_list = builder.Build();
 
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -1181,15 +1172,15 @@
                      unsigned attributes) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawTextBlobFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawTextBlobFlags);
 
   size_t draw_calls = state.range(0);
   size_t canvas_size = kFixedCanvasSize;
   surface_provider->InitializeSurface(canvas_size, canvas_size);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   state.counters["DrawCallCount_Varies"] = draw_calls;
   state.counters["GlyphCount"] = draw_calls;
@@ -1198,13 +1189,13 @@
   for (size_t i = 0; i < draw_calls; i++) {
     character[0] = 'A' + (i % 26);
     auto blob = SkTextBlob::MakeFromString(character, SkFont());
-    builder.drawTextBlob(blob, 50.0f, 50.0f);
+    builder.DrawTextBlob(blob, 50.0f, 50.0f, paint);
   }
 
   auto display_list = builder.Build();
 
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -1228,14 +1219,14 @@
                    SkPath::Verb type) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kDrawShadowFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state, DisplayListOpFlags::kDrawShadowFlags);
 
   size_t length = kFixedCanvasSize;
   surface_provider->InitializeSurface(length, length);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   SkPath path;
 
@@ -1264,12 +1255,12 @@
 
   // We can hardcode dpr to 1.0f as we're varying elevation, and dpr is only
   // ever used in conjunction with elevation.
-  builder.drawShadow(path, SK_ColorBLUE, elevation, transparent_occluder, 1.0f);
+  builder.DrawShadow(path, SK_ColorBLUE, elevation, transparent_occluder, 1.0f);
   auto display_list = builder.Build();
 
   // We only want to time the actual rasterization.
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
@@ -1292,14 +1283,14 @@
                   size_t save_depth) {
   auto surface_provider = DlSurfaceProvider::Create(backend_type);
   DisplayListBuilder builder;
-  builder.SetAttributesFromPaint(GetPaintForRun(attributes),
-                                 DisplayListOpFlags::kSaveLayerFlags);
+  DlPaint paint = GetPaintForRun(attributes);
+
   AnnotateAttributes(attributes, state, DisplayListOpFlags::kSaveLayerFlags);
 
   size_t length = kFixedCanvasSize;
   surface_provider->InitializeSurface(length, length);
   auto surface = surface_provider->GetPrimarySurface()->sk_surface();
-  auto canvas = surface->getCanvas();
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
 
   size_t save_layer_calls = state.range(0);
 
@@ -1311,19 +1302,19 @@
   state.counters["DrawCallCount_Varies"] = save_layer_calls * save_depth;
   for (size_t i = 0; i < save_layer_calls; i++) {
     for (size_t j = 0; j < save_depth; j++) {
-      builder.saveLayer(nullptr, false);
-      builder.drawRect(rect1);
-      builder.drawRect(rect2);
+      builder.SaveLayer(nullptr, nullptr);
+      builder.DrawRect(rect1, paint);
+      builder.DrawRect(rect2, paint);
     }
     for (size_t j = 0; j < save_depth; j++) {
-      builder.restore();
+      builder.Restore();
     }
   }
   auto display_list = builder.Build();
 
   // We only want to time the actual rasterization.
   for ([[maybe_unused]] auto _ : state) {
-    display_list->RenderTo(canvas);
+    canvas.DrawDisplayList(display_list);
     surface->flushAndSubmit(true);
   }
 
diff --git a/display_list/display_list_benchmarks.h b/display_list/display_list_benchmarks.h
index e6df302..4d02664 100644
--- a/display_list/display_list_benchmarks.h
+++ b/display_list/display_list_benchmarks.h
@@ -78,7 +78,7 @@
                       BackendType backend_type,
                       unsigned attributes,
                       DlImageSampling options,
-                      SkCanvas::SrcRectConstraint constraint,
+                      DlCanvas::SrcRectConstraint constraint,
                       bool upload_bitmap);
 void BM_DrawImageNine(benchmark::State& state,
                       BackendType backend_type,
@@ -343,8 +343,8 @@
       BM_DrawImageRect, Texture/Strict/BACKEND,                         \
       BackendType::k##BACKEND##_Backend,                                \
       ATTRIBUTES,                                                       \
-      DlImageSampling::kNearestNeighbor,                                              \
-      SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint, false)    \
+      DlImageSampling::kNearestNeighbor,                                \
+      DlCanvas::SrcRectConstraint::kStrict, false)                      \
       ->RangeMultiplier(2)                                              \
       ->Range(32, 256)                                                  \
       ->UseRealTime()                                                   \
@@ -354,8 +354,8 @@
       BM_DrawImageRect, Texture/Fast/BACKEND,                           \
       BackendType::k##BACKEND##_Backend,                                \
       ATTRIBUTES,                                                       \
-      DlImageSampling::kNearestNeighbor,                                              \
-      SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint, false)      \
+      DlImageSampling::kNearestNeighbor,                                \
+      DlCanvas::SrcRectConstraint::kFast, false)                        \
       ->RangeMultiplier(2)                                              \
       ->Range(32, 256)                                                  \
       ->UseRealTime()                                                   \
@@ -365,8 +365,8 @@
       BM_DrawImageRect, Upload/Strict/BACKEND,                          \
       BackendType::k##BACKEND##_Backend,                                \
       ATTRIBUTES,                                                       \
-      DlImageSampling::kNearestNeighbor,                                              \
-      SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint, true)     \
+      DlImageSampling::kNearestNeighbor,                                \
+      DlCanvas::SrcRectConstraint::kStrict, true)                       \
       ->RangeMultiplier(2)                                              \
       ->Range(32, 256)                                                  \
       ->UseRealTime()                                                   \
@@ -376,8 +376,8 @@
       BM_DrawImageRect, Upload/Fast/BACKEND,                            \
       BackendType::k##BACKEND##_Backend,                                \
       ATTRIBUTES,                                                       \
-      DlImageSampling::kNearestNeighbor,                                              \
-      SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint, true)       \
+      DlImageSampling::kNearestNeighbor,                                \
+      DlCanvas::SrcRectConstraint::kFast, true)                         \
       ->RangeMultiplier(2)                                              \
       ->Range(32, 256)                                                  \
       ->UseRealTime()                                                   \
diff --git a/display_list/display_list_blend_mode.h b/display_list/display_list_blend_mode.h
index b1cdbee..2b5daa9 100644
--- a/display_list/display_list_blend_mode.h
+++ b/display_list/display_list_blend_mode.h
@@ -66,14 +66,6 @@
   kDefaultMode = kSrcOver,
 };
 
-inline DlBlendMode ToDl(SkBlendMode mode) {
-  return static_cast<DlBlendMode>(mode);
-}
-
-inline SkBlendMode ToSk(DlBlendMode mode) {
-  return static_cast<SkBlendMode>(mode);
-}
-
 }  // namespace flutter
 
 #endif  // FLUTTER_DISPLAY_LIST_DISPLAY_LIST_BLEND_MODE_H_
diff --git a/display_list/display_list_builder.cc b/display_list/display_list_builder.cc
index 30ca37d..9ab9db3 100644
--- a/display_list/display_list_builder.cc
+++ b/display_list/display_list_builder.cc
@@ -6,7 +6,6 @@
 
 #include "flutter/display_list/display_list.h"
 #include "flutter/display_list/display_list_blend_mode.h"
-#include "flutter/display_list/display_list_canvas_dispatcher.h"
 #include "flutter/display_list/display_list_color_source.h"
 #include "flutter/display_list/display_list_ops.h"
 #include "fml/logging.h"
@@ -250,8 +249,8 @@
         new (pod) DlMatrixImageFilter(matrix_filter);
         break;
       }
-      case DlImageFilterType::kComposeFilter:
-      case DlImageFilterType::kLocalMatrixFilter:
+      case DlImageFilterType::kCompose:
+      case DlImageFilterType::kLocalMatrix:
       case DlImageFilterType::kColorFilter: {
         Push<SetSharedImageFilterOp>(0, 0, filter);
         break;
@@ -843,7 +842,7 @@
   }
 
   void* data_ptr;
-  FML_DCHECK(count < Dispatcher::kMaxDrawPointsCount);
+  FML_DCHECK(count < DlOpReceiver::kMaxDrawPointsCount);
   int bytes = count * sizeof(SkPoint);
   RectBoundsAccumulator ptBounds;
   for (size_t i = 0; i < count; i++) {
@@ -947,7 +946,7 @@
                                        const SkRect& dst,
                                        DlImageSampling sampling,
                                        bool render_with_attributes,
-                                       SkCanvas::SrcRectConstraint constraint) {
+                                       SrcRectConstraint constraint) {
   Push<DrawImageRectOp>(0, 1, image, src, dst, sampling, render_with_attributes,
                         constraint);
   CheckLayerOpacityCompatibility(render_with_attributes);
@@ -961,10 +960,7 @@
                                        const SkRect& dst,
                                        DlImageSampling sampling,
                                        const DlPaint* paint,
-                                       bool enforce_src_edges) {
-  SkCanvas::SrcRectConstraint constraint =
-      enforce_src_edges ? SkCanvas::kStrict_SrcRectConstraint
-                        : SkCanvas::kFast_SrcRectConstraint;
+                                       SrcRectConstraint constraint) {
   if (paint != nullptr) {
     SetAttributesFromPaint(*paint,
                            DisplayListOpFlags::kDrawImageRectWithPaintFlags);
@@ -1077,18 +1073,12 @@
 void DisplayListBuilder::DrawDisplayList(const sk_sp<DisplayList> display_list,
                                          SkScalar opacity) {
   DlPaint current_paint = current_;
-  if (opacity < SK_Scalar1) {
-    SaveLayer(&display_list->bounds(), &DlPaint().setOpacity(opacity));
-  }
-  Push<DrawDisplayListOp>(0, 1, display_list);
-  if (opacity < SK_Scalar1) {
-    Restore();
-    // Not really necessary if the developer is interacting with us via
-    // our attribute-state-less DlCanvas methods, but this avoids surprises
-    // for those who may have been using the stateful Dispatcher methods.
-    SetAttributesFromPaint(current_paint,
-                           DisplayListOpFlags::kSaveLayerWithPaintFlags);
-  }
+  Push<DrawDisplayListOp>(0, 1, display_list, opacity);
+  // Not really necessary if the developer is interacting with us via
+  // our attribute-state-less DlCanvas methods, but this avoids surprises
+  // for those who may have been using the stateful Dispatcher methods.
+  SetAttributesFromPaint(current_paint,
+                         DisplayListOpFlags::kSaveLayerWithPaintFlags);
 
   const SkRect bounds = display_list->bounds();
   switch (accumulator()->type()) {
@@ -1147,8 +1137,8 @@
       ? Push<DrawShadowTransparentOccluderOp>(0, 1, path, color, elevation, dpr)
       : Push<DrawShadowOp>(0, 1, path, color, elevation, dpr);
 
-  SkRect shadow_bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
-      path, elevation, dpr, GetTransform());
+  SkRect shadow_bounds =
+      DlCanvas::ComputeShadowBounds(path, elevation, dpr, GetTransform());
   AccumulateOpBounds(shadow_bounds, kDrawShadowFlags);
   UpdateLayerOpacityCompatibility(false);
 }
@@ -1195,7 +1185,7 @@
         pad = std::max(pad, SK_ScalarSqrt2);
       }
       SkScalar min_stroke_width = 0.01;
-      pad *= std::max(getStrokeWidth() * 0.5f, min_stroke_width);
+      pad *= std::max(current_.getStrokeWidth() * 0.5f, min_stroke_width);
       bounds.outset(pad, pad);
     }
   }
@@ -1264,7 +1254,7 @@
   // For example, DstIn is used by masking layers.
   // https://code.google.com/p/skia/issues/detail?id=1291
   // https://crbug.com/401593
-  switch (getBlendMode()) {
+  switch (current_.getBlendMode()) {
     // For each of the following transfer modes, if the source
     // alpha is zero (our transparent black), the resulting
     // blended pixel is not necessarily equal to the original
diff --git a/display_list/display_list_builder.h b/display_list/display_list_builder.h
index 58e20f1..4e85207 100644
--- a/display_list/display_list_builder.h
+++ b/display_list/display_list_builder.h
@@ -8,7 +8,6 @@
 #include "flutter/display_list/display_list.h"
 #include "flutter/display_list/display_list_blend_mode.h"
 #include "flutter/display_list/display_list_comparable.h"
-#include "flutter/display_list/display_list_dispatcher.h"
 #include "flutter/display_list/display_list_flags.h"
 #include "flutter/display_list/display_list_image.h"
 #include "flutter/display_list/display_list_matrix_clip_tracker.h"
@@ -17,17 +16,18 @@
 #include "flutter/display_list/display_list_sampling_options.h"
 #include "flutter/display_list/display_list_utils.h"
 #include "flutter/display_list/dl_canvas.h"
+#include "flutter/display_list/dl_op_receiver.h"
 #include "flutter/display_list/types.h"
 #include "flutter/fml/macros.h"
 
 namespace flutter {
 
 // The primary class used to build a display list. The list of methods
-// here matches the list of methods invoked on a |Dispatcher| combined
+// here matches the list of methods invoked on a |DlOpReceiver| combined
 // with the list of methods invoked on a |DlCanvas|.
 class DisplayListBuilder final : public virtual DlCanvas,
                                  public SkRefCnt,
-                                 virtual Dispatcher,
+                                 virtual DlOpReceiver,
                                  DisplayListOpFlags {
  public:
   static constexpr SkRect kMaxCullRect =
@@ -39,168 +39,331 @@
   explicit DisplayListBuilder(const SkRect& cull_rect = kMaxCullRect,
                               bool prepare_rtree = false);
 
-  SkISize GetBaseLayerSize() const override;
-  SkImageInfo GetImageInfo() const override;
-
   ~DisplayListBuilder();
 
-  Dispatcher& asDispatcher() { return *this; }
+  // |DlCanvas|
+  SkISize GetBaseLayerSize() const override;
+  // |DlCanvas|
+  SkImageInfo GetImageInfo() const override;
 
+  // |DlCanvas|
+  void Save() override;
+
+  // |DlCanvas|
+  void SaveLayer(const SkRect* bounds,
+                 const DlPaint* paint = nullptr,
+                 const DlImageFilter* backdrop = nullptr) override;
+  // |DlCanvas|
+  void Restore() override;
+  // |DlCanvas|
+  int GetSaveCount() const override { return layer_stack_.size(); }
+  // |DlCanvas|
+  void RestoreToCount(int restore_count) override;
+
+  // |DlCanvas|
+  void Translate(SkScalar tx, SkScalar ty) override;
+  // |DlCanvas|
+  void Scale(SkScalar sx, SkScalar sy) override;
+  // |DlCanvas|
+  void Rotate(SkScalar degrees) override;
+  // |DlCanvas|
+  void Skew(SkScalar sx, SkScalar sy) override;
+
+  // clang-format off
+  // 2x3 2D affine subset of a 4x4 transform in row major order
+  // |DlCanvas|
+  void Transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt,
+                         SkScalar myx, SkScalar myy, SkScalar myt) override;
+  // full 4x4 transform in row major order
+  // |DlCanvas|
+  void TransformFullPerspective(
+      SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
+      SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
+      SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
+      SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) override;
+  // clang-format on
+  // |DlCanvas|
+  void TransformReset() override;
+  // |DlCanvas|
+  void Transform(const SkMatrix* matrix) override;
+  // |DlCanvas|
+  void Transform(const SkM44* matrix44) override;
+  // |DlCanvas|
+  void SetTransform(const SkMatrix* matrix) override {
+    TransformReset();
+    Transform(matrix);
+  }
+  // |DlCanvas|
+  void SetTransform(const SkM44* matrix44) override {
+    TransformReset();
+    Transform(matrix44);
+  }
+  using DlCanvas::Transform;
+
+  /// Returns the 4x4 full perspective transform representing all transform
+  /// operations executed so far in this DisplayList within the enclosing
+  /// save stack.
+  // |DlCanvas|
+  SkM44 GetTransformFullPerspective() const override {
+    return tracker_.matrix_4x4();
+  }
+  /// Returns the 3x3 partial perspective transform representing all transform
+  /// operations executed so far in this DisplayList within the enclosing
+  /// save stack.
+  // |DlCanvas|
+  SkMatrix GetTransform() const override { return tracker_.matrix_3x3(); }
+
+  // |DlCanvas|
+  void ClipRect(const SkRect& rect, ClipOp clip_op, bool is_aa) override;
+  // |DlCanvas|
+  void ClipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) override;
+  // |DlCanvas|
+  void ClipPath(const SkPath& path, ClipOp clip_op, bool is_aa) override;
+
+  /// Conservative estimate of the bounds of all outstanding clip operations
+  /// measured in the coordinate space within which this DisplayList will
+  /// be rendered.
+  // |DlCanvas|
+  SkRect GetDestinationClipBounds() const override {
+    return tracker_.device_cull_rect();
+  }
+  /// Conservative estimate of the bounds of all outstanding clip operations
+  /// transformed into the local coordinate space in which currently
+  /// recorded rendering operations are interpreted.
+  // |DlCanvas|
+  SkRect GetLocalClipBounds() const override {
+    return tracker_.local_cull_rect();
+  }
+
+  /// Return true iff the supplied bounds are easily shown to be outside
+  /// of the current clip bounds. This method may conservatively return
+  /// false if it cannot make the determination.
+  // |DlCanvas|
+  bool QuickReject(const SkRect& bounds) const override;
+
+  // |DlCanvas|
+  void DrawPaint(const DlPaint& paint) override;
+  // |DlCanvas|
+  void DrawColor(DlColor color, DlBlendMode mode) override;
+  // |DlCanvas|
+  void DrawLine(const SkPoint& p0,
+                const SkPoint& p1,
+                const DlPaint& paint) override;
+  // |DlCanvas|
+  void DrawRect(const SkRect& rect, const DlPaint& paint) override;
+  // |DlCanvas|
+  void DrawOval(const SkRect& bounds, const DlPaint& paint) override;
+  // |DlCanvas|
+  void DrawCircle(const SkPoint& center,
+                  SkScalar radius,
+                  const DlPaint& paint) override;
+  // |DlCanvas|
+  void DrawRRect(const SkRRect& rrect, const DlPaint& paint) override;
+  // |DlCanvas|
+  void DrawDRRect(const SkRRect& outer,
+                  const SkRRect& inner,
+                  const DlPaint& paint) override;
+  // |DlCanvas|
+  void DrawPath(const SkPath& path, const DlPaint& paint) override;
+  // |DlCanvas|
+  void DrawArc(const SkRect& bounds,
+               SkScalar start,
+               SkScalar sweep,
+               bool useCenter,
+               const DlPaint& paint) override;
+  // |DlCanvas|
+  void DrawPoints(PointMode mode,
+                  uint32_t count,
+                  const SkPoint pts[],
+                  const DlPaint& paint) override;
+  // |DlCanvas|
+  void DrawVertices(const DlVertices* vertices,
+                    DlBlendMode mode,
+                    const DlPaint& paint) override;
+  using DlCanvas::DrawVertices;
+  // |DlCanvas|
+  void DrawImage(const sk_sp<DlImage>& image,
+                 const SkPoint point,
+                 DlImageSampling sampling,
+                 const DlPaint* paint = nullptr) override;
+  // |DlCanvas|
+  void DrawImageRect(
+      const sk_sp<DlImage>& image,
+      const SkRect& src,
+      const SkRect& dst,
+      DlImageSampling sampling,
+      const DlPaint* paint = nullptr,
+      SrcRectConstraint constraint = SrcRectConstraint::kFast) override;
+  // |DlCanvas|
+  void DrawImageNine(const sk_sp<DlImage>& image,
+                     const SkIRect& center,
+                     const SkRect& dst,
+                     DlFilterMode filter,
+                     const DlPaint* paint = nullptr) override;
+  // |DlCanvas|
+  void DrawAtlas(const sk_sp<DlImage>& atlas,
+                 const SkRSXform xform[],
+                 const SkRect tex[],
+                 const DlColor colors[],
+                 int count,
+                 DlBlendMode mode,
+                 DlImageSampling sampling,
+                 const SkRect* cullRect,
+                 const DlPaint* paint = nullptr) override;
+  // |DlCanvas|
+  void DrawDisplayList(const sk_sp<DisplayList> display_list,
+                       SkScalar opacity = SK_Scalar1) override;
+  // |DlCanvas|
+  void DrawTextBlob(const sk_sp<SkTextBlob>& blob,
+                    SkScalar x,
+                    SkScalar y,
+                    const DlPaint& paint) override;
+  // |DlCanvas|
+  void DrawShadow(const SkPath& path,
+                  const DlColor color,
+                  const SkScalar elevation,
+                  bool transparent_occluder,
+                  SkScalar dpr) override;
+
+  // |DlCanvas|
+  void Flush() override {}
+
+  sk_sp<DisplayList> Build();
+
+ private:
+  // This method exposes the internal stateful DlOpReceiver implementation
+  // of the DisplayListBuilder, primarily for testing purposes. Its use
+  // is obsolete and forbidden in every other case and is only shared to a
+  // pair of "friend" accessors in the benchmark/unittest files.
+  DlOpReceiver& asReceiver() { return *this; }
+
+  friend DlOpReceiver& DisplayListBuilderBenchmarkAccessor(
+      DisplayListBuilder& builder);
+  friend DlOpReceiver& DisplayListBuilderTestingAccessor(
+      DisplayListBuilder& builder);
+
+  void SetAttributesFromPaint(const DlPaint& paint,
+                              const DisplayListAttributeFlags flags);
+
+  // |DlOpReceiver|
   void setAntiAlias(bool aa) override {
     if (current_.isAntiAlias() != aa) {
       onSetAntiAlias(aa);
     }
   }
+  // |DlOpReceiver|
   void setDither(bool dither) override {
     if (current_.isDither() != dither) {
       onSetDither(dither);
     }
   }
+  // |DlOpReceiver|
   void setInvertColors(bool invert) override {
     if (current_.isInvertColors() != invert) {
       onSetInvertColors(invert);
     }
   }
+  // |DlOpReceiver|
   void setStrokeCap(DlStrokeCap cap) override {
     if (current_.getStrokeCap() != cap) {
       onSetStrokeCap(cap);
     }
   }
+  // |DlOpReceiver|
   void setStrokeJoin(DlStrokeJoin join) override {
     if (current_.getStrokeJoin() != join) {
       onSetStrokeJoin(join);
     }
   }
+  // |DlOpReceiver|
   void setStyle(DlDrawStyle style) override {
     if (current_.getDrawStyle() != style) {
       onSetStyle(style);
     }
   }
+  // |DlOpReceiver|
   void setStrokeWidth(float width) override {
     if (current_.getStrokeWidth() != width) {
       onSetStrokeWidth(width);
     }
   }
+  // |DlOpReceiver|
   void setStrokeMiter(float limit) override {
     if (current_.getStrokeMiter() != limit) {
       onSetStrokeMiter(limit);
     }
   }
+  // |DlOpReceiver|
   void setColor(DlColor color) override {
     if (current_.getColor() != color) {
       onSetColor(color);
     }
   }
+  // |DlOpReceiver|
   void setBlendMode(DlBlendMode mode) override {
     if (current_.getBlendMode() != mode) {
       onSetBlendMode(mode);
     }
   }
+  // |DlOpReceiver|
   void setColorSource(const DlColorSource* source) override {
     if (NotEquals(current_.getColorSource(), source)) {
       onSetColorSource(source);
     }
   }
+  // |DlOpReceiver|
   void setImageFilter(const DlImageFilter* filter) override {
     if (NotEquals(current_.getImageFilter(), filter)) {
       onSetImageFilter(filter);
     }
   }
+  // |DlOpReceiver|
   void setColorFilter(const DlColorFilter* filter) override {
     if (NotEquals(current_.getColorFilter(), filter)) {
       onSetColorFilter(filter);
     }
   }
+  // |DlOpReceiver|
   void setPathEffect(const DlPathEffect* effect) override {
     if (NotEquals(current_.getPathEffect(), effect)) {
       onSetPathEffect(effect);
     }
   }
+  // |DlOpReceiver|
   void setMaskFilter(const DlMaskFilter* filter) override {
     if (NotEquals(current_.getMaskFilter(), filter)) {
       onSetMaskFilter(filter);
     }
   }
 
-  bool isAntiAlias() const { return current_.isAntiAlias(); }
-  bool isDither() const { return current_.isDither(); }
-  DlDrawStyle getStyle() const { return current_.getDrawStyle(); }
-  DlColor getColor() const { return current_.getColor(); }
-  float getStrokeWidth() const { return current_.getStrokeWidth(); }
-  float getStrokeMiter() const { return current_.getStrokeMiter(); }
-  DlStrokeCap getStrokeCap() const { return current_.getStrokeCap(); }
-  DlStrokeJoin getStrokeJoin() const { return current_.getStrokeJoin(); }
-  std::shared_ptr<const DlColorSource> getColorSource() const {
-    return current_.getColorSource();
-  }
-  std::shared_ptr<const DlColorFilter> getColorFilter() const {
-    return current_.getColorFilter();
-  }
-  bool isInvertColors() const { return current_.isInvertColors(); }
-  DlBlendMode getBlendMode() const { return current_.getBlendMode(); }
-  std::shared_ptr<const DlPathEffect> getPathEffect() const {
-    return current_.getPathEffect();
-  }
-  std::shared_ptr<const DlMaskFilter> getMaskFilter() const {
-    return current_.getMaskFilter();
-  }
-  std::shared_ptr<const DlImageFilter> getImageFilter() const {
-    return current_.getImageFilter();
-  }
-
-  void Save() override;
+  // |DlOpReceiver|
   void save() override { Save(); }
-
   // Only the |renders_with_attributes()| option will be accepted here. Any
   // other flags will be ignored and calculated anew as the DisplayList is
   // built. Alternatively, use the |saveLayer(SkRect, bool)| method.
+  // |DlOpReceiver|
   void saveLayer(const SkRect* bounds,
                  const SaveLayerOptions options,
                  const DlImageFilter* backdrop) override;
-  // Convenience method with just a boolean to indicate whether the saveLayer
-  // should apply the rendering attributes.
-  void saveLayer(const SkRect* bounds, bool renders_with_attributes) {
-    saveLayer(bounds,
-              renders_with_attributes ? SaveLayerOptions::kWithAttributes
-                                      : SaveLayerOptions::kNoAttributes,
-              nullptr);
-  }
-  void SaveLayer(const SkRect* bounds,
-                 const DlPaint* paint = nullptr,
-                 const DlImageFilter* backdrop = nullptr) override;
-  void Restore() override;
+  // |DlOpReceiver|
   void restore() override { Restore(); }
-  int GetSaveCount() const override { return layer_stack_.size(); }
-  void RestoreToCount(int restore_count) override;
-  void restoreToCount(int restore_count) { RestoreToCount(restore_count); }
 
-  void Translate(SkScalar tx, SkScalar ty) override;
-  void Scale(SkScalar sx, SkScalar sy) override;
-  void Rotate(SkScalar degrees) override;
-  void Skew(SkScalar sx, SkScalar sy) override;
+  // |DlOpReceiver|
   void translate(SkScalar tx, SkScalar ty) override { Translate(tx, ty); }
+  // |DlOpReceiver|
   void scale(SkScalar sx, SkScalar sy) override { Scale(sx, sy); }
+  // |DlOpReceiver|
   void rotate(SkScalar degrees) override { Rotate(degrees); }
+  // |DlOpReceiver|
   void skew(SkScalar sx, SkScalar sy) override { Skew(sx, sy); }
 
-  void SetAttributesFromPaint(const DlPaint& paint,
-                              const DisplayListAttributeFlags flags);
-
   // clang-format off
-
-  // 2x3 2D affine subset of a 4x4 transform in row major order
-  void Transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt,
-                         SkScalar myx, SkScalar myy, SkScalar myt) override;
+  // |DlOpReceiver|
   void transform2DAffine(SkScalar mxx, SkScalar mxy, SkScalar mxt,
                          SkScalar myx, SkScalar myy, SkScalar myt) override {
     Transform2DAffine(mxx, mxy, mxt, myx, myy, myt);
   }
-  // full 4x4 transform in row major order
-  void TransformFullPerspective(
-      SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
-      SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
-      SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
-      SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) override;
+  // |DlOpReceiver|
   void transformFullPerspective(
       SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
       SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
@@ -211,148 +374,73 @@
                              mzx, mzy, mzz, mzt,
                              mwx, mwy, mwz, mwt);
   }
-  // clang-format on
-  void TransformReset() override;
-  void Transform(const SkMatrix* matrix) override;
-  void Transform(const SkM44* matrix44) override;
-  void SetTransform(const SkMatrix* matrix) override {
-    TransformReset();
-    Transform(matrix);
-  }
-  void SetTransform(const SkM44* matrix44) override {
-    TransformReset();
-    Transform(matrix44);
-  }
-  using DlCanvas::Transform;
+  // clang-format off
+  // |DlOpReceiver|
   void transformReset() override { TransformReset(); }
-  void transform(const SkMatrix* matrix) { Transform(matrix); }
-  void transform(const SkM44* matrix44) { Transform(matrix44); }
-  void transform(const SkMatrix& matrix) { Transform(&matrix); }
-  void transform(const SkM44& matrix44) { Transform(&matrix44); }
 
-  /// Returns the 4x4 full perspective transform representing all transform
-  /// operations executed so far in this DisplayList within the enclosing
-  /// save stack.
-  SkM44 GetTransformFullPerspective() const override {
-    return tracker_.matrix_4x4();
-  }
-  /// Returns the 3x3 partial perspective transform representing all transform
-  /// operations executed so far in this DisplayList within the enclosing
-  /// save stack.
-  SkMatrix GetTransform() const override { return tracker_.matrix_3x3(); }
-
-  void ClipRect(const SkRect& rect, ClipOp clip_op, bool is_aa) override;
-  void ClipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) override;
-  void ClipPath(const SkPath& path, ClipOp clip_op, bool is_aa) override;
+  // |DlOpReceiver|
   void clipRect(const SkRect& rect, ClipOp clip_op, bool is_aa) override {
     ClipRect(rect, clip_op, is_aa);
   }
+  // |DlOpReceiver|
   void clipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) override {
     ClipRRect(rrect, clip_op, is_aa);
   }
+  // |DlOpReceiver|
   void clipPath(const SkPath& path, ClipOp clip_op, bool is_aa) override {
     ClipPath(path, clip_op, is_aa);
   }
 
-  /// Conservative estimate of the bounds of all outstanding clip operations
-  /// measured in the coordinate space within which this DisplayList will
-  /// be rendered.
-  SkRect GetDestinationClipBounds() const override {
-    return tracker_.device_cull_rect();
-  }
-  /// Conservative estimate of the bounds of all outstanding clip operations
-  /// transformed into the local coordinate space in which currently
-  /// recorded rendering operations are interpreted.
-  SkRect GetLocalClipBounds() const override {
-    return tracker_.local_cull_rect();
-  }
-
-  /// Return true iff the supplied bounds are easily shown to be outside
-  /// of the current clip bounds. This method may conservatively return
-  /// false if it cannot make the determination.
-  bool QuickReject(const SkRect& bounds) const override;
-
+  // |DlOpReceiver|
   void drawPaint() override;
-  void DrawPaint(const DlPaint& paint) override;
-  void DrawColor(DlColor color, DlBlendMode mode) override;
+  // |DlOpReceiver|
   void drawColor(DlColor color, DlBlendMode mode) override {
     DrawColor(color, mode);
   }
+  // |DlOpReceiver|
   void drawLine(const SkPoint& p0, const SkPoint& p1) override;
-  void DrawLine(const SkPoint& p0,
-                const SkPoint& p1,
-                const DlPaint& paint) override;
+  // |DlOpReceiver|
   void drawRect(const SkRect& rect) override;
-  void DrawRect(const SkRect& rect, const DlPaint& paint) override;
+  // |DlOpReceiver|
   void drawOval(const SkRect& bounds) override;
-  void DrawOval(const SkRect& bounds, const DlPaint& paint) override;
+  // |DlOpReceiver|
   void drawCircle(const SkPoint& center, SkScalar radius) override;
-  void DrawCircle(const SkPoint& center,
-                  SkScalar radius,
-                  const DlPaint& paint) override;
+  // |DlOpReceiver|
   void drawRRect(const SkRRect& rrect) override;
-  void DrawRRect(const SkRRect& rrect, const DlPaint& paint) override;
+  // |DlOpReceiver|
   void drawDRRect(const SkRRect& outer, const SkRRect& inner) override;
-  void DrawDRRect(const SkRRect& outer,
-                  const SkRRect& inner,
-                  const DlPaint& paint) override;
+  // |DlOpReceiver|
   void drawPath(const SkPath& path) override;
-  void DrawPath(const SkPath& path, const DlPaint& paint) override;
+  // |DlOpReceiver|
   void drawArc(const SkRect& bounds,
                SkScalar start,
                SkScalar sweep,
                bool useCenter) override;
-  void DrawArc(const SkRect& bounds,
-               SkScalar start,
-               SkScalar sweep,
-               bool useCenter,
-               const DlPaint& paint) override;
+  // |DlOpReceiver|
   void drawPoints(PointMode mode, uint32_t count, const SkPoint pts[]) override;
-  void DrawPoints(PointMode mode,
-                  uint32_t count,
-                  const SkPoint pts[],
-                  const DlPaint& paint) override;
+  // |DlOpReceiver|
   void drawVertices(const DlVertices* vertices, DlBlendMode mode) override;
-  void drawVertices(const std::shared_ptr<const DlVertices> vertices,
-                    DlBlendMode mode) {
-    drawVertices(vertices.get(), mode);
-  }
-  void DrawVertices(const DlVertices* vertices,
-                    DlBlendMode mode,
-                    const DlPaint& paint) override;
-  using DlCanvas::DrawVertices;
+
+  // |DlOpReceiver|
   void drawImage(const sk_sp<DlImage> image,
                  const SkPoint point,
                  DlImageSampling sampling,
                  bool render_with_attributes) override;
-  void DrawImage(const sk_sp<DlImage>& image,
-                 const SkPoint point,
-                 DlImageSampling sampling,
-                 const DlPaint* paint = nullptr) override;
+  // |DlOpReceiver|
   void drawImageRect(
       const sk_sp<DlImage> image,
       const SkRect& src,
       const SkRect& dst,
       DlImageSampling sampling,
       bool render_with_attributes,
-      SkCanvas::SrcRectConstraint constraint =
-          SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint) override;
-  void DrawImageRect(const sk_sp<DlImage>& image,
-                     const SkRect& src,
-                     const SkRect& dst,
-                     DlImageSampling sampling,
-                     const DlPaint* paint = nullptr,
-                     bool enforce_src_edges = false) override;
+      SrcRectConstraint constraint = SrcRectConstraint::kFast) override;
+  // |DlOpReceiver|
   void drawImageNine(const sk_sp<DlImage> image,
                      const SkIRect& center,
                      const SkRect& dst,
                      DlFilterMode filter,
                      bool render_with_attributes) override;
-  void DrawImageNine(const sk_sp<DlImage>& image,
-                     const SkIRect& center,
-                     const SkRect& dst,
-                     DlFilterMode filter,
-                     const DlPaint* paint = nullptr) override;
+  // |DlOpReceiver|
   void drawAtlas(const sk_sp<DlImage> atlas,
                  const SkRSXform xform[],
                  const SkRect tex[],
@@ -362,32 +450,17 @@
                  DlImageSampling sampling,
                  const SkRect* cullRect,
                  bool render_with_attributes) override;
-  void DrawAtlas(const sk_sp<DlImage>& atlas,
-                 const SkRSXform xform[],
-                 const SkRect tex[],
-                 const DlColor colors[],
-                 int count,
-                 DlBlendMode mode,
-                 DlImageSampling sampling,
-                 const SkRect* cullRect,
-                 const DlPaint* paint = nullptr) override;
-  void DrawDisplayList(const sk_sp<DisplayList> display_list,
-                       SkScalar opacity) override;
-  void drawDisplayList(const sk_sp<DisplayList> display_list) override {
-    DrawDisplayList(display_list, SK_Scalar1);
+
+  // |DlOpReceiver|
+  void drawDisplayList(const sk_sp<DisplayList> display_list,
+                       SkScalar opacity) override {
+    DrawDisplayList(display_list, opacity);
   }
+  // |DlOpReceiver|
   void drawTextBlob(const sk_sp<SkTextBlob> blob,
                     SkScalar x,
                     SkScalar y) override;
-  void DrawTextBlob(const sk_sp<SkTextBlob>& blob,
-                    SkScalar x,
-                    SkScalar y,
-                    const DlPaint& paint) override;
-  void DrawShadow(const SkPath& path,
-                  const DlColor color,
-                  const SkScalar elevation,
-                  bool transparent_occluder,
-                  SkScalar dpr) override;
+  // |DlOpReceiver|
   void drawShadow(const SkPath& path,
                   const DlColor color,
                   const SkScalar elevation,
@@ -396,11 +469,6 @@
     DrawShadow(path, color, elevation, transparent_occluder, dpr);
   }
 
-  void Flush() override {}
-
-  sk_sp<DisplayList> Build();
-
- private:
   void checkForDeferredSave();
 
   DisplayListStorage storage_;
diff --git a/display_list/display_list_builder_benchmarks.cc b/display_list/display_list_builder_benchmarks.cc
index 7405901..87f4bfc 100644
--- a/display_list/display_list_builder_benchmarks.cc
+++ b/display_list/display_list_builder_benchmarks.cc
@@ -6,6 +6,11 @@
 #include "flutter/display_list/testing/dl_test_snippets.h"
 
 namespace flutter {
+
+DlOpReceiver& DisplayListBuilderBenchmarkAccessor(DisplayListBuilder& builder) {
+  return builder.asReceiver();
+}
+
 namespace {
 
 static std::vector<testing::DisplayListInvocationGroup> allRenderingOps =
@@ -19,10 +24,11 @@
 };
 
 static void InvokeAllRenderingOps(DisplayListBuilder& builder) {
+  DlOpReceiver& receiver = DisplayListBuilderBenchmarkAccessor(builder);
   for (auto& group : allRenderingOps) {
     for (size_t i = 0; i < group.variants.size(); i++) {
       auto& invocation = group.variants[i];
-      invocation.Invoke(builder);
+      invocation.Invoke(receiver);
     }
   }
 }
@@ -69,8 +75,8 @@
   bool prepare_rtree = NeedPrepareRTree(type);
   while (state.KeepRunning()) {
     DisplayListBuilder builder(prepare_rtree);
-    builder.scale(3.5, 3.5);
-    builder.translate(10.3, 6.9);
+    builder.Scale(3.5, 3.5);
+    builder.Translate(10.3, 6.9);
     InvokeAllRenderingOps(builder);
     Complete(builder, type);
   }
@@ -82,7 +88,7 @@
   bool prepare_rtree = NeedPrepareRTree(type);
   while (state.KeepRunning()) {
     DisplayListBuilder builder(prepare_rtree);
-    builder.transformFullPerspective(0, 1, 0, 12, 1, 0, 0, 33, 3, 2, 5, 29, 0,
+    builder.TransformFullPerspective(0, 1, 0, 12, 1, 0, 0, 33, 3, 2, 5, 29, 0,
                                      0, 0, 12);
     InvokeAllRenderingOps(builder);
     Complete(builder, type);
@@ -96,7 +102,7 @@
   bool prepare_rtree = NeedPrepareRTree(type);
   while (state.KeepRunning()) {
     DisplayListBuilder builder(prepare_rtree);
-    builder.clipRect(clip_bounds, DlCanvas::ClipOp::kIntersect, true);
+    builder.ClipRect(clip_bounds, DlCanvas::ClipOp::kIntersect, true);
     InvokeAllRenderingOps(builder);
     Complete(builder, type);
   }
@@ -108,12 +114,13 @@
   bool prepare_rtree = NeedPrepareRTree(type);
   while (state.KeepRunning()) {
     DisplayListBuilder builder(prepare_rtree);
+    DlOpReceiver& receiver = DisplayListBuilderBenchmarkAccessor(builder);
     for (auto& group : allRenderingOps) {
       for (size_t i = 0; i < group.variants.size(); i++) {
         auto& invocation = group.variants[i];
-        builder.saveLayer(nullptr, false);
-        invocation.Invoke(builder);
-        builder.restore();
+        builder.SaveLayer(nullptr, nullptr);
+        invocation.Invoke(receiver);
+        builder.Restore();
       }
     }
     Complete(builder, type);
@@ -129,11 +136,12 @@
   bool prepare_rtree = NeedPrepareRTree(type);
   while (state.KeepRunning()) {
     DisplayListBuilder builder(prepare_rtree);
+    DlOpReceiver& receiver = DisplayListBuilderBenchmarkAccessor(builder);
     for (auto& group : allRenderingOps) {
       for (size_t i = 0; i < group.variants.size(); i++) {
         auto& invocation = group.variants[i];
         builder.SaveLayer(&layer_bounds, &layer_paint);
-        invocation.Invoke(builder);
+        invocation.Invoke(receiver);
         builder.Restore();
       }
     }
diff --git a/display_list/display_list_canvas_dispatcher.cc b/display_list/display_list_canvas_dispatcher.cc
deleted file mode 100644
index 34dbe83..0000000
--- a/display_list/display_list_canvas_dispatcher.cc
+++ /dev/null
@@ -1,305 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "flutter/display_list/display_list_canvas_dispatcher.h"
-
-#include "flutter/display_list/display_list_blend_mode.h"
-#include "flutter/fml/trace_event.h"
-
-namespace flutter {
-
-const SkScalar kLightHeight = 600;
-const SkScalar kLightRadius = 800;
-
-static SkClipOp ToSk(DlCanvas::ClipOp op) {
-  return static_cast<SkClipOp>(op);
-}
-
-static SkCanvas::PointMode ToSk(DlCanvas::PointMode mode) {
-  return static_cast<SkCanvas::PointMode>(mode);
-}
-
-const SkPaint* DisplayListCanvasDispatcher::safe_paint(bool use_attributes) {
-  if (use_attributes) {
-    // The accumulated SkPaint object will already have incorporated
-    // any attribute overrides.
-    return &paint();
-  } else if (has_opacity()) {
-    temp_paint_.setAlphaf(opacity());
-    return &temp_paint_;
-  } else {
-    return nullptr;
-  }
-}
-
-void DisplayListCanvasDispatcher::save() {
-  canvas_->save();
-  // save has no impact on attributes, but it needs to register a record
-  // on the restore stack so that the eventual call to restore() will
-  // know what to do at that time. We could annotate the restore record
-  // with a flag that the record came from a save call, but it is simpler
-  // to just pass in the current opacity value as the value to be used by
-  // the children and let the utility calls notice that it didn't change.
-  save_opacity(opacity());
-}
-void DisplayListCanvasDispatcher::restore() {
-  canvas_->restore();
-  restore_opacity();
-}
-void DisplayListCanvasDispatcher::saveLayer(const SkRect* bounds,
-                                            const SaveLayerOptions options,
-                                            const DlImageFilter* backdrop) {
-  if (bounds == nullptr && options.can_distribute_opacity() &&
-      backdrop == nullptr) {
-    // We know that:
-    // - no bounds is needed for clipping here
-    // - no backdrop filter is used to initialize the layer
-    // - the current attributes only have an alpha
-    // - the children are compatible with individually rendering with
-    //   an inherited opacity
-    // Therefore we can just use a save instead of a saveLayer and pass the
-    // intended opacity to the children.
-    canvas_->save();
-    // If the saveLayer does not use attributes, the children should continue
-    // to render with the inherited opacity unmodified. If attributes are to
-    // be applied, the children should render with the combination of the
-    // inherited opacity combined with the alpha from the current color.
-    save_opacity(options.renders_with_attributes() ? combined_opacity()
-                                                   : opacity());
-  } else {
-    TRACE_EVENT0("flutter", "Canvas::saveLayer");
-    const SkPaint* paint = safe_paint(options.renders_with_attributes());
-    const sk_sp<SkImageFilter> sk_backdrop =
-        backdrop ? backdrop->skia_object() : nullptr;
-    canvas_->saveLayer(
-        SkCanvas::SaveLayerRec(bounds, paint, sk_backdrop.get(), 0));
-    // saveLayer will apply the current opacity on behalf of the children
-    // so they will inherit an opaque opacity.
-    save_opacity(SK_Scalar1);
-  }
-}
-
-void DisplayListCanvasDispatcher::translate(SkScalar tx, SkScalar ty) {
-  canvas_->translate(tx, ty);
-}
-void DisplayListCanvasDispatcher::scale(SkScalar sx, SkScalar sy) {
-  canvas_->scale(sx, sy);
-}
-void DisplayListCanvasDispatcher::rotate(SkScalar degrees) {
-  canvas_->rotate(degrees);
-}
-void DisplayListCanvasDispatcher::skew(SkScalar sx, SkScalar sy) {
-  canvas_->skew(sx, sy);
-}
-// clang-format off
-// 2x3 2D affine subset of a 4x4 transform in row major order
-void DisplayListCanvasDispatcher::transform2DAffine(
-    SkScalar mxx, SkScalar mxy, SkScalar mxt,
-    SkScalar myx, SkScalar myy, SkScalar myt) {
-  // Internally concat(SkMatrix) gets redirected to concat(SkM44)
-  // so we just jump directly to the SkM44 version
-  canvas_->concat(SkM44(mxx, mxy, 0, mxt,
-                        myx, myy, 0, myt,
-                         0,   0,  1,  0,
-                         0,   0,  0,  1));
-}
-// full 4x4 transform in row major order
-void DisplayListCanvasDispatcher::transformFullPerspective(
-    SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
-    SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
-    SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
-    SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) {
-  canvas_->concat(SkM44(mxx, mxy, mxz, mxt,
-                        myx, myy, myz, myt,
-                        mzx, mzy, mzz, mzt,
-                        mwx, mwy, mwz, mwt));
-}
-// clang-format on
-void DisplayListCanvasDispatcher::transformReset() {
-  canvas_->setMatrix(original_transform_);
-}
-
-void DisplayListCanvasDispatcher::clipRect(const SkRect& rect,
-                                           ClipOp clip_op,
-                                           bool is_aa) {
-  canvas_->clipRect(rect, ToSk(clip_op), is_aa);
-}
-void DisplayListCanvasDispatcher::clipRRect(const SkRRect& rrect,
-                                            ClipOp clip_op,
-                                            bool is_aa) {
-  canvas_->clipRRect(rrect, ToSk(clip_op), is_aa);
-}
-void DisplayListCanvasDispatcher::clipPath(const SkPath& path,
-                                           ClipOp clip_op,
-                                           bool is_aa) {
-  canvas_->clipPath(path, ToSk(clip_op), is_aa);
-}
-
-void DisplayListCanvasDispatcher::drawPaint() {
-  const SkPaint& sk_paint = paint();
-  SkImageFilter* filter = sk_paint.getImageFilter();
-  if (filter && !filter->asColorFilter(nullptr)) {
-    // drawPaint does an implicit saveLayer if an SkImageFilter is
-    // present that cannot be replaced by an SkColorFilter.
-    TRACE_EVENT0("flutter", "Canvas::saveLayer");
-  }
-  canvas_->drawPaint(sk_paint);
-}
-void DisplayListCanvasDispatcher::drawColor(DlColor color, DlBlendMode mode) {
-  // SkCanvas::drawColor(SkColor) does the following conversion anyway
-  // We do it here manually to increase precision on applying opacity
-  SkColor4f color4f = SkColor4f::FromColor(color);
-  color4f.fA *= opacity();
-  canvas_->drawColor(color4f, ToSk(mode));
-}
-void DisplayListCanvasDispatcher::drawLine(const SkPoint& p0,
-                                           const SkPoint& p1) {
-  canvas_->drawLine(p0, p1, paint());
-}
-void DisplayListCanvasDispatcher::drawRect(const SkRect& rect) {
-  canvas_->drawRect(rect, paint());
-}
-void DisplayListCanvasDispatcher::drawOval(const SkRect& bounds) {
-  canvas_->drawOval(bounds, paint());
-}
-void DisplayListCanvasDispatcher::drawCircle(const SkPoint& center,
-                                             SkScalar radius) {
-  canvas_->drawCircle(center, radius, paint());
-}
-void DisplayListCanvasDispatcher::drawRRect(const SkRRect& rrect) {
-  canvas_->drawRRect(rrect, paint());
-}
-void DisplayListCanvasDispatcher::drawDRRect(const SkRRect& outer,
-                                             const SkRRect& inner) {
-  canvas_->drawDRRect(outer, inner, paint());
-}
-void DisplayListCanvasDispatcher::drawPath(const SkPath& path) {
-  canvas_->drawPath(path, paint());
-}
-void DisplayListCanvasDispatcher::drawArc(const SkRect& bounds,
-                                          SkScalar start,
-                                          SkScalar sweep,
-                                          bool useCenter) {
-  canvas_->drawArc(bounds, start, sweep, useCenter, paint());
-}
-void DisplayListCanvasDispatcher::drawPoints(PointMode mode,
-                                             uint32_t count,
-                                             const SkPoint pts[]) {
-  canvas_->drawPoints(ToSk(mode), count, pts, paint());
-}
-void DisplayListCanvasDispatcher::drawVertices(const DlVertices* vertices,
-                                               DlBlendMode mode) {
-  canvas_->drawVertices(vertices->skia_object(), ToSk(mode), paint());
-}
-void DisplayListCanvasDispatcher::drawImage(const sk_sp<DlImage> image,
-                                            const SkPoint point,
-                                            DlImageSampling sampling,
-                                            bool render_with_attributes) {
-  canvas_->drawImage(image ? image->skia_image() : nullptr, point.fX, point.fY,
-                     ToSk(sampling), safe_paint(render_with_attributes));
-}
-void DisplayListCanvasDispatcher::drawImageRect(
-    const sk_sp<DlImage> image,
-    const SkRect& src,
-    const SkRect& dst,
-    DlImageSampling sampling,
-    bool render_with_attributes,
-    SkCanvas::SrcRectConstraint constraint) {
-  canvas_->drawImageRect(image ? image->skia_image() : nullptr, src, dst,
-                         ToSk(sampling), safe_paint(render_with_attributes),
-                         constraint);
-}
-void DisplayListCanvasDispatcher::drawImageNine(const sk_sp<DlImage> image,
-                                                const SkIRect& center,
-                                                const SkRect& dst,
-                                                DlFilterMode filter,
-                                                bool render_with_attributes) {
-  if (!image) {
-    return;
-  }
-  auto skia_image = image->skia_image();
-  if (!skia_image) {
-    return;
-  }
-  canvas_->drawImageNine(skia_image.get(), center, dst, ToSk(filter),
-                         safe_paint(render_with_attributes));
-}
-void DisplayListCanvasDispatcher::drawAtlas(const sk_sp<DlImage> atlas,
-                                            const SkRSXform xform[],
-                                            const SkRect tex[],
-                                            const DlColor colors[],
-                                            int count,
-                                            DlBlendMode mode,
-                                            DlImageSampling sampling,
-                                            const SkRect* cullRect,
-                                            bool render_with_attributes) {
-  if (!atlas) {
-    return;
-  }
-  auto skia_atlas = atlas->skia_image();
-  if (!skia_atlas) {
-    return;
-  }
-  const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors);
-  canvas_->drawAtlas(skia_atlas.get(), xform, tex, sk_colors, count, ToSk(mode),
-                     ToSk(sampling), cullRect,
-                     safe_paint(render_with_attributes));
-}
-void DisplayListCanvasDispatcher::drawDisplayList(
-    const sk_sp<DisplayList> display_list) {
-  int save_count = canvas_->save();
-  display_list->RenderTo(canvas_, opacity());
-  canvas_->restoreToCount(save_count);
-}
-void DisplayListCanvasDispatcher::drawTextBlob(const sk_sp<SkTextBlob> blob,
-                                               SkScalar x,
-                                               SkScalar y) {
-  canvas_->drawTextBlob(blob, x, y, paint());
-}
-
-SkRect DisplayListCanvasDispatcher::ComputeShadowBounds(const SkPath& path,
-                                                        float elevation,
-                                                        SkScalar dpr,
-                                                        const SkMatrix& ctm) {
-  SkRect shadow_bounds(path.getBounds());
-  SkShadowUtils::GetLocalBounds(
-      ctm, path, SkPoint3::Make(0, 0, dpr * elevation),
-      SkPoint3::Make(0, -1, 1), kLightRadius / kLightHeight,
-      SkShadowFlags::kDirectionalLight_ShadowFlag, &shadow_bounds);
-  return shadow_bounds;
-}
-
-void DisplayListCanvasDispatcher::DrawShadow(SkCanvas* canvas,
-                                             const SkPath& path,
-                                             DlColor color,
-                                             float elevation,
-                                             bool transparentOccluder,
-                                             SkScalar dpr) {
-  const SkScalar kAmbientAlpha = 0.039f;
-  const SkScalar kSpotAlpha = 0.25f;
-
-  uint32_t flags = transparentOccluder
-                       ? SkShadowFlags::kTransparentOccluder_ShadowFlag
-                       : SkShadowFlags::kNone_ShadowFlag;
-  flags |= SkShadowFlags::kDirectionalLight_ShadowFlag;
-  SkColor in_ambient = SkColorSetA(color, kAmbientAlpha * SkColorGetA(color));
-  SkColor in_spot = SkColorSetA(color, kSpotAlpha * SkColorGetA(color));
-  SkColor ambient_color, spot_color;
-  SkShadowUtils::ComputeTonalColors(in_ambient, in_spot, &ambient_color,
-                                    &spot_color);
-  SkShadowUtils::DrawShadow(canvas, path, SkPoint3::Make(0, 0, dpr * elevation),
-                            SkPoint3::Make(0, -1, 1),
-                            kLightRadius / kLightHeight, ambient_color,
-                            spot_color, flags);
-}
-
-void DisplayListCanvasDispatcher::drawShadow(const SkPath& path,
-                                             const DlColor color,
-                                             const SkScalar elevation,
-                                             bool transparent_occluder,
-                                             SkScalar dpr) {
-  DrawShadow(canvas_, path, color, elevation, transparent_occluder, dpr);
-}
-
-}  // namespace flutter
diff --git a/display_list/display_list_color.h b/display_list/display_list_color.h
index d84ccd4..855df42 100644
--- a/display_list/display_list_color.h
+++ b/display_list/display_list_color.h
@@ -35,6 +35,7 @@
   uint32_t argb;
 
   bool isOpaque() const { return getAlpha() == 0xFF; }
+  bool isTransparent() const { return getAlpha() == 0; }
 
   int getAlpha() const { return argb >> 24; }
   int getRed() const { return (argb >> 16) & 0xFF; }
diff --git a/display_list/display_list_color_filter.cc b/display_list/display_list_color_filter.cc
index 6f46b02..778214e 100644
--- a/display_list/display_list_color_filter.cc
+++ b/display_list/display_list_color_filter.cc
@@ -8,16 +8,193 @@
 
 namespace flutter {
 
+std::shared_ptr<DlColorFilter> DlBlendColorFilter::Make(DlColor color,
+                                                        DlBlendMode mode) {
+  switch (mode) {
+    case DlBlendMode::kDst: {
+      return nullptr;
+    }
+    case DlBlendMode::kSrcOver: {
+      if (color.isTransparent()) {
+        return nullptr;
+      }
+      if (color.isOpaque()) {
+        mode = DlBlendMode::kSrc;
+      }
+      break;
+    }
+    case DlBlendMode::kDstOver:
+    case DlBlendMode::kDstOut:
+    case DlBlendMode::kSrcATop:
+    case DlBlendMode::kXor:
+    case DlBlendMode::kDarken: {
+      if (color.isTransparent()) {
+        return nullptr;
+      }
+      break;
+    }
+    case DlBlendMode::kDstIn: {
+      if (color.isOpaque()) {
+        return nullptr;
+      }
+      break;
+    }
+    default:
+      break;
+  }
+  return std::make_shared<DlBlendColorFilter>(color, mode);
+}
+
+bool DlBlendColorFilter::modifies_transparent_black() const {
+  switch (mode_) {
+    // These modes all act like kSrc when the dest is all 0s.
+    // So they modify transparent black when the src color is
+    // not transparent.
+    case DlBlendMode::kSrc:
+    case DlBlendMode::kSrcOver:
+    case DlBlendMode::kDstOver:
+    case DlBlendMode::kSrcOut:
+    case DlBlendMode::kDstATop:
+    case DlBlendMode::kXor:
+    case DlBlendMode::kPlus:
+    case DlBlendMode::kScreen:
+    case DlBlendMode::kOverlay:
+    case DlBlendMode::kDarken:
+    case DlBlendMode::kLighten:
+    case DlBlendMode::kColorDodge:
+    case DlBlendMode::kColorBurn:
+    case DlBlendMode::kHardLight:
+    case DlBlendMode::kSoftLight:
+    case DlBlendMode::kDifference:
+    case DlBlendMode::kExclusion:
+    case DlBlendMode::kMultiply:
+    case DlBlendMode::kHue:
+    case DlBlendMode::kSaturation:
+    case DlBlendMode::kColor:
+    case DlBlendMode::kLuminosity:
+      return !color_.isTransparent();
+
+    // These modes are all like kDst when the dest is all 0s.
+    // So they never modify transparent black.
+    case DlBlendMode::kClear:
+    case DlBlendMode::kDst:
+    case DlBlendMode::kSrcIn:
+    case DlBlendMode::kDstIn:
+    case DlBlendMode::kDstOut:
+    case DlBlendMode::kSrcATop:
+    case DlBlendMode::kModulate:
+      return false;
+  }
+}
+
+bool DlBlendColorFilter::can_commute_with_opacity() const {
+  switch (mode_) {
+    case DlBlendMode::kClear:
+    case DlBlendMode::kDst:
+    case DlBlendMode::kSrcIn:
+    case DlBlendMode::kDstIn:
+    case DlBlendMode::kDstOut:
+    case DlBlendMode::kSrcATop:
+    case DlBlendMode::kModulate:
+      return true;
+
+    case DlBlendMode::kSrc:
+    case DlBlendMode::kSrcOver:
+    case DlBlendMode::kDstOver:
+    case DlBlendMode::kSrcOut:
+    case DlBlendMode::kDstATop:
+    case DlBlendMode::kXor:
+    case DlBlendMode::kPlus:
+    case DlBlendMode::kScreen:
+    case DlBlendMode::kOverlay:
+    case DlBlendMode::kDarken:
+    case DlBlendMode::kLighten:
+    case DlBlendMode::kColorDodge:
+    case DlBlendMode::kColorBurn:
+    case DlBlendMode::kHardLight:
+    case DlBlendMode::kSoftLight:
+    case DlBlendMode::kDifference:
+    case DlBlendMode::kExclusion:
+    case DlBlendMode::kMultiply:
+    case DlBlendMode::kHue:
+    case DlBlendMode::kSaturation:
+    case DlBlendMode::kColor:
+    case DlBlendMode::kLuminosity:
+      return color_.isTransparent();
+  }
+}
+
+std::shared_ptr<DlColorFilter> DlMatrixColorFilter::Make(
+    const float matrix[20]) {
+  float product = 0;
+  for (int i = 0; i < 20; i++) {
+    product *= matrix[i];
+  }
+  // If any of the elements of the matrix are infinity or NaN, then
+  // |product| will be NaN, otherwise 0.
+  if (product == 0) {
+    return std::make_shared<DlMatrixColorFilter>(matrix);
+  }
+  return nullptr;
+}
+
+bool DlMatrixColorFilter::modifies_transparent_black() const {
+  // Values are considered in non-premultiplied form when the matrix is
+  // applied, but we only care about this answer for whether it leaves
+  // an incoming color with a transparent alpha as transparent on output.
+  // Thus, we only need to consider the alpha part of the matrix equation,
+  // which is the last row. Since the incoming alpha value is 0, the last
+  // equation ends up becoming A' = matrix_[19]. Negative results will be
+  // clamped to the range [0,1] so we only care about positive values.
+  // Non-finite values are clamped to a zero alpha.
+  return (SkScalarIsFinite(matrix_[19]) && matrix_[19] > 0);
+}
+
+bool DlMatrixColorFilter::can_commute_with_opacity() const {
+  // We need to check if:
+  //   filter(color) * opacity == filter(color * opacity).
+  //
+  // filter(RGBA) = R' = [ R*m[ 0] + G*m[ 1] + B*m[ 2] + A*m[ 3] + m[ 4] ]
+  //                G' = [ R*m[ 5] + G*m[ 6] + B*m[ 7] + A*m[ 8] + m[ 9] ]
+  //                B' = [ R*m[10] + G*m[11] + B*m[12] + A*m[13] + m[14] ]
+  //                A' = [ R*m[15] + G*m[16] + B*m[17] + A*m[18] + m[19] ]
+  //
+  // Applying the opacity only affects the alpha value since the operations
+  // are performed on non-premultiplied colors. (If the data is stored in
+  // premultiplied form, though, there may be rounding errors due to
+  // premul->unpremul->premul conversions.)
+
+  // We test for the successful cases and return false if they fail so that
+  // we fail and return false if any matrix values are NaN.
+
+  // If any of the alpha column are non-zero then the prior alpha affects
+  // the result color, so applying opacity before the filter will change
+  // the incoming alpha and therefore the colors that are produced.
+  if (!(matrix_[3] == 0 &&    // A does not affect R'
+        matrix_[8] == 0 &&    // A does not affect G'
+        matrix_[13] == 0)) {  // A does not affect B'
+    return false;
+  }
+
+  // Similarly, if any of the alpha row are non-zero then the prior colors
+  // affect the result alpha in a way that prevents opacity from commuting
+  // through the filter operation.
+  if (!(matrix_[15] == 0 &&   // R does not affect A'
+        matrix_[16] == 0 &&   // G does not affect A'
+        matrix_[17] == 0 &&   // B does not affect A'
+        matrix_[19] == 0)) {  // A' is not offset by an absolute value
+    return false;
+  }
+
+  return true;
+}
+
 const std::shared_ptr<DlSrgbToLinearGammaColorFilter>
     DlSrgbToLinearGammaColorFilter::instance =
         std::make_shared<DlSrgbToLinearGammaColorFilter>();
-const sk_sp<SkColorFilter> DlSrgbToLinearGammaColorFilter::sk_filter_ =
-    SkColorFilters::SRGBToLinearGamma();
 
 const std::shared_ptr<DlLinearToSrgbGammaColorFilter>
     DlLinearToSrgbGammaColorFilter::instance =
         std::make_shared<DlLinearToSrgbGammaColorFilter>();
-const sk_sp<SkColorFilter> DlLinearToSrgbGammaColorFilter::sk_filter_ =
-    SkColorFilters::LinearToSRGBGamma();
 
 }  // namespace flutter
diff --git a/display_list/display_list_color_filter.h b/display_list/display_list_color_filter.h
index 240f361..b02bad4 100644
--- a/display_list/display_list_color_filter.h
+++ b/display_list/display_list_color_filter.h
@@ -28,8 +28,7 @@
   kLinearToSrgbGamma,
 };
 
-class DlColorFilter
-    : public DlAttribute<DlColorFilter, SkColorFilter, DlColorFilterType> {
+class DlColorFilter : public DlAttribute<DlColorFilter, DlColorFilterType> {
  public:
   // Return a boolean indicating whether the color filtering operation will
   // modify transparent black. This is typically used to determine if applying
@@ -68,23 +67,18 @@
   DlBlendColorFilter(const DlBlendColorFilter* filter)
       : DlBlendColorFilter(filter->color_, filter->mode_) {}
 
+  static std::shared_ptr<DlColorFilter> Make(DlColor color, DlBlendMode mode);
+
   DlColorFilterType type() const override { return DlColorFilterType::kBlend; }
   size_t size() const override { return sizeof(*this); }
-  bool modifies_transparent_black() const override {
-    // Look at blend and color to make a faster determination?
-    sk_sp<SkColorFilter> sk_filter = skia_object();
-    return sk_filter &&
-           sk_filter->filterColor(SK_ColorTRANSPARENT) != SK_ColorTRANSPARENT;
-  }
+
+  bool modifies_transparent_black() const override;
+  bool can_commute_with_opacity() const override;
 
   std::shared_ptr<DlColorFilter> shared() const override {
     return std::make_shared<DlBlendColorFilter>(this);
   }
 
-  sk_sp<SkColorFilter> skia_object() const override {
-    return SkColorFilters::Blend(color_, ToSk(mode_));
-  }
-
   const DlBlendColorFilter* asBlend() const override { return this; }
 
   DlColor color() const { return color_; }
@@ -114,6 +108,10 @@
 //
 // The resulting color [oR,oG,oB,oA] is then clamped to the range of
 // valid pixel components before storing in the output.
+//
+// The incoming and outgoing [iR,iG,iB,iA] and [oR,oG,oB,oA] are
+// considered to be non-premultiplied. When working on premultiplied
+// pixel data, the necessary pre<->non-pre conversions must be performed.
 class DlMatrixColorFilter final : public DlColorFilter {
  public:
   DlMatrixColorFilter(const float matrix[20]) {
@@ -124,30 +122,18 @@
   DlMatrixColorFilter(const DlMatrixColorFilter* filter)
       : DlMatrixColorFilter(filter->matrix_) {}
 
+  static std::shared_ptr<DlColorFilter> Make(const float matrix[20]);
+
   DlColorFilterType type() const override { return DlColorFilterType::kMatrix; }
   size_t size() const override { return sizeof(*this); }
-  bool modifies_transparent_black() const override {
-    // Look at the matrix to make a faster determination?
-    // Basically, are the translation components all 0?
-    sk_sp<SkColorFilter> sk_filter = skia_object();
-    return sk_filter &&
-           sk_filter->filterColor(SK_ColorTRANSPARENT) != SK_ColorTRANSPARENT;
-  }
 
-  bool can_commute_with_opacity() const override {
-    return matrix_[3] == 0 && matrix_[8] == 0 && matrix_[13] == 0 &&
-           matrix_[15] == 0 && matrix_[16] == 0 && matrix_[17] == 0 &&
-           (matrix_[18] >= 0.0 && matrix_[18] <= 1.0) && matrix_[19] == 0;
-  }
+  bool modifies_transparent_black() const override;
+  bool can_commute_with_opacity() const override;
 
   std::shared_ptr<DlColorFilter> shared() const override {
     return std::make_shared<DlMatrixColorFilter>(this);
   }
 
-  sk_sp<SkColorFilter> skia_object() const override {
-    return SkColorFilters::Matrix(matrix_);
-  }
-
   const DlMatrixColorFilter* asMatrix() const override { return this; }
 
   const float& operator[](int index) const { return matrix_[index]; }
@@ -186,7 +172,6 @@
   bool can_commute_with_opacity() const override { return true; }
 
   std::shared_ptr<DlColorFilter> shared() const override { return instance; }
-  sk_sp<SkColorFilter> skia_object() const override { return sk_filter_; }
 
  protected:
   bool equals_(const DlColorFilter& other) const override {
@@ -195,7 +180,6 @@
   }
 
  private:
-  static const sk_sp<SkColorFilter> sk_filter_;
   friend class DlColorFilter;
 };
 
@@ -219,7 +203,6 @@
   bool can_commute_with_opacity() const override { return true; }
 
   std::shared_ptr<DlColorFilter> shared() const override { return instance; }
-  sk_sp<SkColorFilter> skia_object() const override { return sk_filter_; }
 
  protected:
   bool equals_(const DlColorFilter& other) const override {
@@ -228,7 +211,6 @@
   }
 
  private:
-  static const sk_sp<SkColorFilter> sk_filter_;
   friend class DlColorFilter;
 };
 
diff --git a/display_list/display_list_color_filter_unittests.cc b/display_list/display_list_color_filter_unittests.cc
index 2403a6d..efdd1b5 100644
--- a/display_list/display_list_color_filter_unittests.cc
+++ b/display_list/display_list_color_filter_unittests.cc
@@ -17,18 +17,6 @@
     16, 17, 18, 19, 20,  //
 };
 
-TEST(DisplayListColorFilter, BuilderSetGet) {
-  DlBlendColorFilter filter(DlColor::kRed(), DlBlendMode::kDstATop);
-  DisplayListBuilder builder;
-  ASSERT_EQ(builder.getColorFilter(), nullptr);
-  builder.setColorFilter(&filter);
-  ASSERT_NE(builder.getColorFilter(), nullptr);
-  ASSERT_TRUE(
-      Equals(builder.getColorFilter(), static_cast<DlColorFilter*>(&filter)));
-  builder.setColorFilter(nullptr);
-  ASSERT_EQ(builder.getColorFilter(), nullptr);
-}
-
 TEST(DisplayListColorFilter, BlendConstructor) {
   DlBlendColorFilter filter(DlColor::kRed(), DlBlendMode::kDstATop);
 }
diff --git a/display_list/display_list_color_source.cc b/display_list/display_list_color_source.cc
index 94c9fbb..01da26d 100644
--- a/display_list/display_list_color_source.cc
+++ b/display_list/display_list_color_source.cc
@@ -19,7 +19,7 @@
   ::operator delete(p);
 }
 
-std::shared_ptr<DlColorSource> DlColorSource::MakeLinear(
+std::shared_ptr<DlLinearGradientColorSource> DlColorSource::MakeLinear(
     const SkPoint start_point,
     const SkPoint end_point,
     uint32_t stop_count,
@@ -37,10 +37,10 @@
                 DlLinearGradientColorSource(start_point, end_point, stop_count,
                                             colors, stops, tile_mode, matrix),
             DlGradientDeleter);
-  return std::move(ret);
+  return ret;
 }
 
-std::shared_ptr<DlColorSource> DlColorSource::MakeRadial(
+std::shared_ptr<DlRadialGradientColorSource> DlColorSource::MakeRadial(
     SkPoint center,
     SkScalar radius,
     uint32_t stop_count,
@@ -57,10 +57,10 @@
   ret.reset(new (storage) DlRadialGradientColorSource(
                 center, radius, stop_count, colors, stops, tile_mode, matrix),
             DlGradientDeleter);
-  return std::move(ret);
+  return ret;
 }
 
-std::shared_ptr<DlColorSource> DlColorSource::MakeConical(
+std::shared_ptr<DlConicalGradientColorSource> DlColorSource::MakeConical(
     SkPoint start_center,
     SkScalar start_radius,
     SkPoint end_center,
@@ -80,10 +80,10 @@
                 start_center, start_radius, end_center, end_radius, stop_count,
                 colors, stops, tile_mode, matrix),
             DlGradientDeleter);
-  return std::move(ret);
+  return ret;
 }
 
-std::shared_ptr<DlColorSource> DlColorSource::MakeSweep(
+std::shared_ptr<DlSweepGradientColorSource> DlColorSource::MakeSweep(
     SkPoint center,
     SkScalar start,
     SkScalar end,
@@ -102,7 +102,7 @@
                 DlSweepGradientColorSource(center, start, end, stop_count,
                                            colors, stops, tile_mode, matrix),
             DlGradientDeleter);
-  return std::move(ret);
+  return ret;
 }
 
 std::shared_ptr<DlRuntimeEffectColorSource> DlColorSource::MakeRuntimeEffect(
diff --git a/display_list/display_list_color_source.h b/display_list/display_list_color_source.h
index 7705893..8c93dc8 100644
--- a/display_list/display_list_color_source.h
+++ b/display_list/display_list_color_source.h
@@ -63,10 +63,9 @@
 #endif  // IMPELLER_ENABLE_3D
 };
 
-class DlColorSource
-    : public DlAttribute<DlColorSource, SkShader, DlColorSourceType> {
+class DlColorSource : public DlAttribute<DlColorSource, DlColorSourceType> {
  public:
-  static std::shared_ptr<DlColorSource> MakeLinear(
+  static std::shared_ptr<DlLinearGradientColorSource> MakeLinear(
       const SkPoint start_point,
       const SkPoint end_point,
       uint32_t stop_count,
@@ -75,7 +74,7 @@
       DlTileMode tile_mode,
       const SkMatrix* matrix = nullptr);
 
-  static std::shared_ptr<DlColorSource> MakeRadial(
+  static std::shared_ptr<DlRadialGradientColorSource> MakeRadial(
       SkPoint center,
       SkScalar radius,
       uint32_t stop_count,
@@ -84,7 +83,7 @@
       DlTileMode tile_mode,
       const SkMatrix* matrix = nullptr);
 
-  static std::shared_ptr<DlColorSource> MakeConical(
+  static std::shared_ptr<DlConicalGradientColorSource> MakeConical(
       SkPoint start_center,
       SkScalar start_radius,
       SkPoint end_center,
@@ -95,7 +94,7 @@
       DlTileMode tile_mode,
       const SkMatrix* matrix = nullptr);
 
-  static std::shared_ptr<DlColorSource> MakeSweep(
+  static std::shared_ptr<DlSweepGradientColorSource> MakeSweep(
       SkPoint center,
       SkScalar start,
       SkScalar end,
@@ -112,11 +111,6 @@
 
   virtual bool is_opaque() const = 0;
 
-  virtual std::shared_ptr<DlColorSource> with_sampling(
-      DlImageSampling options) const {
-    return shared();
-  }
-
   // Return a DlColorColorSource pointer to this object iff it is an Color
   // type of ColorSource, otherwise return nullptr.
   virtual const DlColorColorSource* asColor() const { return nullptr; }
@@ -190,10 +184,6 @@
 
   DlColor color() const { return color_; }
 
-  sk_sp<SkShader> skia_object() const override {
-    return SkShaders::Color(color_);
-  }
-
  protected:
   bool equals_(DlColorSource const& other) const override {
     FML_DCHECK(other.type() == DlColorSourceType::kColor);
@@ -242,8 +232,7 @@
     return with_sampling(sampling_);
   }
 
-  std::shared_ptr<DlColorSource> with_sampling(
-      DlImageSampling sampling) const override {
+  std::shared_ptr<DlColorSource> with_sampling(DlImageSampling sampling) const {
     return std::make_shared<DlImageColorSource>(image_, horizontal_tile_mode_,
                                                 vertical_tile_mode_, sampling,
                                                 matrix_ptr());
@@ -263,15 +252,6 @@
   DlTileMode vertical_tile_mode() const { return vertical_tile_mode_; }
   DlImageSampling sampling() const { return sampling_; }
 
-  virtual sk_sp<SkShader> skia_object() const override {
-    if (!image_->skia_image()) {
-      return nullptr;
-    }
-    return image_->skia_image()->makeShader(ToSk(horizontal_tile_mode_),
-                                            ToSk(vertical_tile_mode_),
-                                            ToSk(sampling_), matrix_ptr());
-  }
-
  protected:
   bool equals_(DlColorSource const& other) const override {
     FML_DCHECK(other.type() == DlColorSourceType::kImage);
@@ -386,13 +366,6 @@
   const SkPoint& start_point() const { return start_point_; }
   const SkPoint& end_point() const { return end_point_; }
 
-  sk_sp<SkShader> skia_object() const override {
-    SkPoint pts[] = {start_point_, end_point_};
-    const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors());
-    return SkGradientShader::MakeLinear(pts, sk_colors, stops(), stop_count(),
-                                        ToSk(tile_mode()), 0, matrix_ptr());
-  }
-
  protected:
   virtual const void* pod() const override { return this + 1; }
 
@@ -454,13 +427,6 @@
   SkPoint center() const { return center_; }
   SkScalar radius() const { return radius_; }
 
-  sk_sp<SkShader> skia_object() const override {
-    const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors());
-    return SkGradientShader::MakeRadial(center_, radius_, sk_colors, stops(),
-                                        stop_count(), ToSk(tile_mode()), 0,
-                                        matrix_ptr());
-  }
-
  protected:
   virtual const void* pod() const override { return this + 1; }
 
@@ -525,13 +491,6 @@
   SkPoint end_center() const { return end_center_; }
   SkScalar end_radius() const { return end_radius_; }
 
-  sk_sp<SkShader> skia_object() const override {
-    const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors());
-    return SkGradientShader::MakeTwoPointConical(
-        start_center_, start_radius_, end_center_, end_radius_, sk_colors,
-        stops(), stop_count(), ToSk(tile_mode()), 0, matrix_ptr());
-  }
-
  protected:
   virtual const void* pod() const override { return this + 1; }
 
@@ -604,13 +563,6 @@
   SkScalar start() const { return start_; }
   SkScalar end() const { return end_; }
 
-  sk_sp<SkShader> skia_object() const override {
-    const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors());
-    return SkGradientShader::MakeSweep(center_.x(), center_.y(), sk_colors,
-                                       stops(), stop_count(), ToSk(tile_mode()),
-                                       start_, end_, 0, matrix_ptr());
-  }
-
  protected:
   virtual const void* pod() const override { return this + 1; }
 
@@ -693,35 +645,6 @@
     return uniform_data_;
   }
 
-  sk_sp<SkShader> skia_object() const override {
-    if (!runtime_effect_) {
-      return nullptr;
-    }
-    if (!runtime_effect_->skia_runtime_effect()) {
-      return nullptr;
-    }
-    std::vector<sk_sp<SkShader>> sk_samplers(samplers_.size());
-    for (size_t i = 0; i < samplers_.size(); i++) {
-      auto sampler = samplers_[i];
-      if (sampler == nullptr) {
-        return nullptr;
-      }
-      sk_samplers[i] = sampler->skia_object();
-    }
-
-    auto ref = new std::shared_ptr<std::vector<uint8_t>>(uniform_data_);
-    auto uniform_data = SkData::MakeWithProc(
-        uniform_data_->data(), uniform_data_->size(),
-        [](const void* ptr, void* context) {
-          delete reinterpret_cast<std::shared_ptr<std::vector<uint8_t>>*>(
-              context);
-        },
-        ref);
-
-    return runtime_effect_->skia_runtime_effect()->makeShader(
-        uniform_data, sk_samplers.data(), sk_samplers.size());
-  }
-
  protected:
   bool equals_(DlColorSource const& other) const override {
     FML_DCHECK(other.type() == DlColorSourceType::kRuntimeEffect);
@@ -773,8 +696,6 @@
 
   impeller::Matrix camera_matrix() const { return camera_matrix_; }
 
-  sk_sp<SkShader> skia_object() const override { return nullptr; }
-
  protected:
   bool equals_(DlColorSource const& other) const override {
     FML_DCHECK(other.type() == DlColorSourceType::kScene);
diff --git a/display_list/display_list_color_source_unittests.cc b/display_list/display_list_color_source_unittests.cc
index a4c982c..582d723 100644
--- a/display_list/display_list_color_source_unittests.cc
+++ b/display_list/display_list_color_source_unittests.cc
@@ -86,19 +86,6 @@
     SkPoint::Make(107, 118),
 };
 
-TEST(DisplayListColorSource, BuilderSetGet) {
-  DlImageColorSource source(kTestImage1, DlTileMode::kClamp, DlTileMode::kClamp,
-                            DlImageSampling::kLinear, &kTestMatrix1);
-  DisplayListBuilder builder;
-  ASSERT_EQ(builder.getColorSource(), nullptr);
-  builder.setColorSource(&source);
-  ASSERT_NE(builder.getColorSource(), nullptr);
-  ASSERT_TRUE(
-      Equals(builder.getColorSource(), static_cast<DlColorSource*>(&source)));
-  builder.setColorSource(nullptr);
-  ASSERT_EQ(builder.getColorSource(), nullptr);
-}
-
 TEST(DisplayListColorSource, ColorConstructor) {
   DlColorColorSource source(SK_ColorRED);
 }
@@ -763,23 +750,11 @@
   ASSERT_EQ(source1->asConicalGradient(), nullptr);
   ASSERT_EQ(source1->asSweepGradient(), nullptr);
 
-  ASSERT_NE(source1->skia_object(), nullptr);
-  ASSERT_EQ(source3->skia_object(), nullptr);
-
   TestEquals(source1, source1);
   TestEquals(source3, source3);
   TestNotEquals(source1, source2, "SkRuntimeEffect differs");
   TestNotEquals(source2, source3, "SkRuntimeEffect differs");
 }
 
-TEST(DisplayListColorSource, RuntimeEffectWithNullSampler) {
-  std::shared_ptr<DlRuntimeEffectColorSource> source1 =
-      DlColorSource::MakeRuntimeEffect(
-          kTestRuntimeEffect1, {nullptr},
-          std::make_shared<std::vector<uint8_t>>());
-
-  ASSERT_EQ(source1->skia_object(), nullptr);
-}
-
 }  // namespace testing
 }  // namespace flutter
diff --git a/display_list/display_list_complexity_gl.cc b/display_list/display_list_complexity_gl.cc
index 40c3bde..165744d 100644
--- a/display_list/display_list_complexity_gl.cc
+++ b/display_list/display_list_complexity_gl.cc
@@ -114,7 +114,7 @@
   //
   // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
   // currently use it anywhere in Flutter.
-  if (Style() == SkPaint::Style::kFill_Style) {
+  if (DrawStyle() == DlDrawStyle::kFill) {
     // No real difference for AA with filled styles
     unsigned int area = rect.width() * rect.height();
 
@@ -165,7 +165,7 @@
 
   // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
   // currently use it anywhere in Flutter.
-  if (Style() == SkPaint::Style::kFill_Style) {
+  if (DrawStyle() == DlDrawStyle::kFill) {
     // With filled styles, there is no significant AA penalty.
     // m = 1/6000
     // c = 0
@@ -199,7 +199,7 @@
 
   // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
   // currently use it anywhere in Flutter.
-  if (Style() == SkPaint::Style::kFill_Style) {
+  if (DrawStyle() == DlDrawStyle::kFill) {
     // We can ignore pi here
     unsigned int area = radius * radius;
     // m = 1/525
@@ -245,7 +245,7 @@
   // These values were worked out by creating a straight line graph (y=mx+c)
   // approximately matching the measured data, normalising the data so that
   // 0.0005ms resulted in a score of 100 then simplifying down the formula.
-  if (Style() == SkPaint::Style::kFill_Style ||
+  if (DrawStyle() == DlDrawStyle::kFill ||
       ((rrect.getType() == SkRRect::Type::kSimple_Type) && IsAntiAliased())) {
     unsigned int area = rrect.width() * rrect.height();
     // m = 1/3200
@@ -295,7 +295,7 @@
   //
   // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
   // currently use it anywhere in Flutter.
-  if (Style() == SkPaint::Style::kFill_Style) {
+  if (DrawStyle() == DlDrawStyle::kFill) {
     unsigned int area = outer.width() * outer.height();
     if (outer.getType() == SkRRect::Type::kComplex_Type) {
       // m = 1/500
@@ -382,7 +382,7 @@
   //
   // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
   // currently use it anywhere in Flutter.
-  if (Style() == SkPaint::Style::kStroke_Style) {
+  if (DrawStyle() == DlDrawStyle::kStroke) {
     if (IsAntiAliased()) {
       // m = 1/3800
       // c = 12
@@ -550,7 +550,7 @@
     const SkISize& size,
     bool texture_backed,
     bool render_with_attributes,
-    SkCanvas::SrcRectConstraint constraint) {
+    bool enforce_src_edges) {
   if (IsComplex()) {
     return;
   }
@@ -563,10 +563,8 @@
   // approximately matching the measured data, normalising the data so that
   // 0.0005ms resulted in a score of 100 then simplifying down the formula.
   unsigned int complexity;
-  if (!texture_backed ||
-      (texture_backed && render_with_attributes &&
-       constraint == SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint &&
-       IsAntiAliased())) {
+  if (!texture_backed || (texture_backed && render_with_attributes &&
+                          enforce_src_edges && IsAntiAliased())) {
     unsigned int area = size.width() * size.height();
     // m = 1/4000
     // c = 5
@@ -610,11 +608,15 @@
 }
 
 void DisplayListGLComplexityCalculator::GLHelper::drawDisplayList(
-    const sk_sp<DisplayList> display_list) {
+    const sk_sp<DisplayList> display_list,
+    SkScalar opacity) {
   if (IsComplex()) {
     return;
   }
   GLHelper helper(Ceiling() - CurrentComplexityScore());
+  if (opacity < SK_Scalar1 && !display_list->can_apply_group_opacity()) {
+    helper.saveLayer(nullptr, SaveLayerOptions::kWithAttributes, nullptr);
+  }
   display_list->Dispatch(helper);
   AccumulateComplexity(helper.ComplexityScore());
 }
diff --git a/display_list/display_list_complexity_gl.h b/display_list/display_list_complexity_gl.h
index 9756943..821c696 100644
--- a/display_list/display_list_complexity_gl.h
+++ b/display_list/display_list_complexity_gl.h
@@ -65,7 +65,8 @@
                        const SkRect& dst,
                        DlFilterMode filter,
                        bool render_with_attributes) override;
-    void drawDisplayList(const sk_sp<DisplayList> display_list) override;
+    void drawDisplayList(const sk_sp<DisplayList> display_list,
+                         SkScalar opacity) override;
     void drawTextBlob(const sk_sp<SkTextBlob> blob,
                       SkScalar x,
                       SkScalar y) override;
@@ -79,7 +80,7 @@
     void ImageRect(const SkISize& size,
                    bool texture_backed,
                    bool render_with_attributes,
-                   SkCanvas::SrcRectConstraint constraint) override;
+                   bool enforce_src_edges) override;
 
     unsigned int BatchedComplexity() override;
 
diff --git a/display_list/display_list_complexity_helper.h b/display_list/display_list_complexity_helper.h
index eb738b9..488dc32 100644
--- a/display_list/display_list_complexity_helper.h
+++ b/display_list/display_list_complexity_helper.h
@@ -7,8 +7,8 @@
 
 #include "flutter/display_list/display_list_blend_mode.h"
 #include "flutter/display_list/display_list_complexity.h"
-#include "flutter/display_list/display_list_dispatcher.h"
 #include "flutter/display_list/display_list_utils.h"
+#include "flutter/display_list/dl_op_receiver.h"
 
 namespace flutter {
 
@@ -92,7 +92,7 @@
 //   y = 4x
 
 class ComplexityCalculatorHelper
-    : public virtual Dispatcher,
+    : public virtual DlOpReceiver,
       public virtual IgnoreClipDispatchHelper,
       public virtual IgnoreTransformDispatchHelper {
  public:
@@ -121,7 +121,7 @@
   void setAntiAlias(bool aa) override { current_paint_.setAntiAlias(aa); }
 
   void setStyle(DlDrawStyle style) override {
-    current_paint_.setStyle(ToSk(style));
+    current_paint_.setDrawStyle(style);
   }
 
   void setStrokeWidth(SkScalar width) override {
@@ -145,17 +145,18 @@
     AccumulateComplexity(50);
   }
 
-  void drawImageRect(const sk_sp<DlImage> image,
-                     const SkRect& src,
-                     const SkRect& dst,
-                     DlImageSampling sampling,
-                     bool render_with_attributes,
-                     SkCanvas::SrcRectConstraint constraint) override {
+  void drawImageRect(
+      const sk_sp<DlImage> image,
+      const SkRect& src,
+      const SkRect& dst,
+      DlImageSampling sampling,
+      bool render_with_attributes,
+      SrcRectConstraint constraint = SrcRectConstraint::kFast) override {
     if (IsComplex()) {
       return;
     }
     ImageRect(image->dimensions(), image->isTextureBacked(),
-              render_with_attributes, constraint);
+              render_with_attributes, constraint == SrcRectConstraint::kStrict);
   }
 
   void drawAtlas(const sk_sp<DlImage> atlas,
@@ -174,8 +175,7 @@
     // This is equivalent to calling drawImageRect lots of times
     for (int i = 0; i < count; i++) {
       ImageRect(SkISize::Make(tex[i].width(), tex[i].height()), true,
-                render_with_attributes,
-                SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint);
+                render_with_attributes, true);
     }
   }
 
@@ -211,7 +211,7 @@
 
   inline bool IsAntiAliased() { return current_paint_.isAntiAlias(); }
   inline bool IsHairline() { return current_paint_.getStrokeWidth() == 0.0f; }
-  inline SkPaint::Style Style() { return current_paint_.getStyle(); }
+  inline DlDrawStyle DrawStyle() { return current_paint_.getDrawStyle(); }
   inline bool IsComplex() { return is_complex_; }
   inline unsigned int Ceiling() { return ceiling_; }
   inline unsigned int CurrentComplexityScore() { return complexity_score_; }
@@ -248,7 +248,7 @@
   virtual void ImageRect(const SkISize& size,
                          bool texture_backed,
                          bool render_with_attributes,
-                         SkCanvas::SrcRectConstraint constraint) = 0;
+                         bool enforce_src_edges) = 0;
 
   // This calculates and returns the cost of draw calls which are batched and
   // thus have a time cost proportional to the number of draw calls made, such
@@ -256,7 +256,7 @@
   virtual unsigned int BatchedComplexity() = 0;
 
  private:
-  SkPaint current_paint_;
+  DlPaint current_paint_;
 
   // If we exceed the ceiling (defaults to the largest number representable
   // by unsigned int), then set the is_complex_ bool and we no longer
diff --git a/display_list/display_list_complexity_metal.cc b/display_list/display_list_complexity_metal.cc
index 2cd5c7c..8edb87a 100644
--- a/display_list/display_list_complexity_metal.cc
+++ b/display_list/display_list_complexity_metal.cc
@@ -128,7 +128,7 @@
   //
   // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
   // currently use it anywhere in Flutter.
-  if (Style() == SkPaint::Style::kFill_Style) {
+  if (DrawStyle() == DlDrawStyle::kFill) {
     // No real difference for AA with filled styles.
     unsigned int area = rect.width() * rect.height();
 
@@ -170,7 +170,7 @@
 
   // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
   // currently use it anywhere in Flutter.
-  if (Style() == SkPaint::Style::kFill_Style) {
+  if (DrawStyle() == DlDrawStyle::kFill) {
     // With filled styles, there is no significant AA penalty.
     // m = 1/16000
     // c = 0
@@ -204,7 +204,7 @@
 
   // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
   // currently use it anywhere in Flutter.
-  if (Style() == SkPaint::Style::kFill_Style) {
+  if (DrawStyle() == DlDrawStyle::kFill) {
     // We can ignore pi here.
     unsigned int area = radius * radius;
     // m = 1/1300
@@ -244,7 +244,7 @@
   //
   // Expensive: All filled style, symmetric w/AA.
   bool expensive =
-      (Style() == SkPaint::Style::kFill_Style) ||
+      (DrawStyle() == DlDrawStyle::kFill) ||
       ((rrect.getType() == SkRRect::Type::kSimple_Type) && IsAntiAliased());
 
   unsigned int complexity;
@@ -292,7 +292,7 @@
   //
   // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
   // currently use it anywhere in Flutter.
-  if (Style() == SkPaint::Style::kFill_Style) {
+  if (DrawStyle() == DlDrawStyle::kFill) {
     unsigned int area = outer.width() * outer.height();
     if (outer.getType() == SkRRect::Type::kComplex_Type) {
       // m = 1/1000
@@ -374,7 +374,7 @@
   //
   // There is also a kStrokeAndFill_Style that Skia exposes, but we do not
   // currently use it anywhere in Flutter.
-  if (Style() == SkPaint::Style::kStroke_Style) {
+  if (DrawStyle() == DlDrawStyle::kStroke) {
     if (IsAntiAliased()) {
       // m = 1/8500
       // c = 16
@@ -493,7 +493,7 @@
     const SkISize& size,
     bool texture_backed,
     bool render_with_attributes,
-    SkCanvas::SrcRectConstraint constraint) {
+    bool enforce_src_edges) {
   if (IsComplex()) {
     return;
   }
@@ -512,16 +512,12 @@
     // m = 1/23000
     // c = 2.3
     complexity = (area + 52900) * 2 / 115;
-    if (render_with_attributes &&
-        constraint == SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint &&
-        IsAntiAliased()) {
+    if (render_with_attributes && enforce_src_edges && IsAntiAliased()) {
       // There's about a 30% performance penalty from the baseline.
       complexity *= 1.3f;
     }
   } else {
-    if (render_with_attributes &&
-        constraint == SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint &&
-        IsAntiAliased()) {
+    if (render_with_attributes && enforce_src_edges && IsAntiAliased()) {
       // m = 1/12200
       // c = 2.75
       complexity = (area + 33550) * 2 / 61;
@@ -556,11 +552,15 @@
 }
 
 void DisplayListMetalComplexityCalculator::MetalHelper::drawDisplayList(
-    const sk_sp<DisplayList> display_list) {
+    const sk_sp<DisplayList> display_list,
+    SkScalar opacity) {
   if (IsComplex()) {
     return;
   }
   MetalHelper helper(Ceiling() - CurrentComplexityScore());
+  if (opacity < SK_Scalar1 && !display_list->can_apply_group_opacity()) {
+    helper.saveLayer(nullptr, SaveLayerOptions::kWithAttributes, nullptr);
+  }
   display_list->Dispatch(helper);
   AccumulateComplexity(helper.ComplexityScore());
 }
diff --git a/display_list/display_list_complexity_metal.h b/display_list/display_list_complexity_metal.h
index 530335e..94d3ca5 100644
--- a/display_list/display_list_complexity_metal.h
+++ b/display_list/display_list_complexity_metal.h
@@ -65,7 +65,8 @@
                        const SkRect& dst,
                        DlFilterMode filter,
                        bool render_with_attributes) override;
-    void drawDisplayList(const sk_sp<DisplayList> display_list) override;
+    void drawDisplayList(const sk_sp<DisplayList> display_list,
+                         SkScalar opacity) override;
     void drawTextBlob(const sk_sp<SkTextBlob> blob,
                       SkScalar x,
                       SkScalar y) override;
@@ -79,7 +80,7 @@
     void ImageRect(const SkISize& size,
                    bool texture_backed,
                    bool render_with_attributes,
-                   SkCanvas::SrcRectConstraint constraint) override;
+                   bool enforce_src_edges) override;
 
     unsigned int BatchedComplexity() override;
 
diff --git a/display_list/display_list_complexity_unittests.cc b/display_list/display_list_complexity_unittests.cc
index b459a78..3d4f9b0 100644
--- a/display_list/display_list_complexity_unittests.cc
+++ b/display_list/display_list_complexity_unittests.cc
@@ -76,12 +76,13 @@
 
 TEST(DisplayListComplexity, AntiAliasing) {
   DisplayListBuilder builder_no_aa;
-  builder_no_aa.drawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100));
+  builder_no_aa.DrawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100),
+                         DlPaint());
   auto display_list_no_aa = builder_no_aa.Build();
 
   DisplayListBuilder builder_aa;
-  builder_aa.setAntiAlias(true);
-  builder_aa.drawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100));
+  builder_aa.DrawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100),
+                      DlPaint().setAntiAlias(true));
   auto display_list_aa = builder_aa.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -93,13 +94,13 @@
 
 TEST(DisplayListComplexity, StrokeWidth) {
   DisplayListBuilder builder_stroke_0;
-  builder_stroke_0.setStrokeWidth(0.0f);
-  builder_stroke_0.drawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100));
+  builder_stroke_0.DrawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100),
+                            DlPaint().setStrokeWidth(0.0f));
   auto display_list_stroke_0 = builder_stroke_0.Build();
 
   DisplayListBuilder builder_stroke_1;
-  builder_stroke_1.setStrokeWidth(1.0f);
-  builder_stroke_1.drawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100));
+  builder_stroke_0.DrawLine(SkPoint::Make(0, 0), SkPoint::Make(100, 100),
+                            DlPaint().setStrokeWidth(1.0f));
   auto display_list_stroke_1 = builder_stroke_1.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -111,13 +112,13 @@
 
 TEST(DisplayListComplexity, Style) {
   DisplayListBuilder builder_filled;
-  builder_filled.setStyle(DlDrawStyle::kFill);
-  builder_filled.drawRect(SkRect::MakeXYWH(10, 10, 80, 80));
+  builder_filled.DrawRect(SkRect::MakeXYWH(10, 10, 80, 80),
+                          DlPaint().setDrawStyle(DlDrawStyle::kFill));
   auto display_list_filled = builder_filled.Build();
 
   DisplayListBuilder builder_stroked;
-  builder_stroked.setStyle(DlDrawStyle::kStroke);
-  builder_stroked.drawRect(SkRect::MakeXYWH(10, 10, 80, 80));
+  builder_stroked.DrawRect(SkRect::MakeXYWH(10, 10, 80, 80),
+                           DlPaint().setDrawStyle(DlDrawStyle::kStroke));
   auto display_list_stroked = builder_stroked.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -129,7 +130,7 @@
 
 TEST(DisplayListComplexity, SaveLayers) {
   DisplayListBuilder builder;
-  builder.saveLayer(nullptr, true);
+  builder.SaveLayer(nullptr, nullptr);
   auto display_list = builder.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -144,7 +145,7 @@
   line_path.moveTo(SkPoint::Make(0, 0));
   line_path.lineTo(SkPoint::Make(10, 10));
   line_path.close();
-  builder_line.drawPath(line_path);
+  builder_line.DrawPath(line_path, DlPaint());
   auto display_list_line = builder_line.Build();
 
   DisplayListBuilder builder_quad;
@@ -152,7 +153,7 @@
   quad_path.moveTo(SkPoint::Make(0, 0));
   quad_path.quadTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20));
   quad_path.close();
-  builder_quad.drawPath(quad_path);
+  builder_quad.DrawPath(quad_path, DlPaint());
   auto display_list_quad = builder_quad.Build();
 
   DisplayListBuilder builder_conic;
@@ -160,7 +161,7 @@
   conic_path.moveTo(SkPoint::Make(0, 0));
   conic_path.conicTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20), 1.5f);
   conic_path.close();
-  builder_conic.drawPath(conic_path);
+  builder_conic.DrawPath(conic_path, DlPaint());
   auto display_list_conic = builder_conic.Build();
 
   DisplayListBuilder builder_cubic;
@@ -168,7 +169,7 @@
   cubic_path.moveTo(SkPoint::Make(0, 0));
   cubic_path.cubicTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20),
                      SkPoint::Make(20, 20));
-  builder_cubic.drawPath(cubic_path);
+  builder_cubic.DrawPath(cubic_path, DlPaint());
   auto display_list_cubic = builder_cubic.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -186,7 +187,7 @@
   line_path.moveTo(SkPoint::Make(0, 0));
   line_path.lineTo(SkPoint::Make(10, 10));
   line_path.close();
-  builder_line.drawShadow(line_path, SK_ColorRED, 10.0f, false, 1.0f);
+  builder_line.DrawShadow(line_path, SK_ColorRED, 10.0f, false, 1.0f);
   auto display_list_line = builder_line.Build();
 
   DisplayListBuilder builder_quad;
@@ -194,7 +195,7 @@
   quad_path.moveTo(SkPoint::Make(0, 0));
   quad_path.quadTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20));
   quad_path.close();
-  builder_quad.drawShadow(quad_path, SK_ColorRED, 10.0f, false, 1.0f);
+  builder_quad.DrawShadow(quad_path, SK_ColorRED, 10.0f, false, 1.0f);
   auto display_list_quad = builder_quad.Build();
 
   DisplayListBuilder builder_conic;
@@ -202,7 +203,7 @@
   conic_path.moveTo(SkPoint::Make(0, 0));
   conic_path.conicTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20), 1.5f);
   conic_path.close();
-  builder_conic.drawShadow(conic_path, SK_ColorRED, 10.0f, false, 1.0f);
+  builder_conic.DrawShadow(conic_path, SK_ColorRED, 10.0f, false, 1.0f);
   auto display_list_conic = builder_conic.Build();
 
   DisplayListBuilder builder_cubic;
@@ -210,7 +211,7 @@
   cubic_path.moveTo(SkPoint::Make(0, 0));
   cubic_path.cubicTo(SkPoint::Make(10, 10), SkPoint::Make(10, 20),
                      SkPoint::Make(20, 20));
-  builder_cubic.drawShadow(cubic_path, SK_ColorRED, 10.0f, false, 1.0f);
+  builder_cubic.DrawShadow(cubic_path, SK_ColorRED, 10.0f, false, 1.0f);
   auto display_list_cubic = builder_cubic.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -224,7 +225,7 @@
 
 TEST(DisplayListComplexity, DrawOval) {
   DisplayListBuilder builder;
-  builder.drawOval(SkRect::MakeXYWH(10, 10, 100, 80));
+  builder.DrawOval(SkRect::MakeXYWH(10, 10, 100, 80), DlPaint());
   auto display_list = builder.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -235,7 +236,7 @@
 
 TEST(DisplayListComplexity, DrawCircle) {
   DisplayListBuilder builder;
-  builder.drawCircle(SkPoint::Make(50, 50), 10.0f);
+  builder.DrawCircle(SkPoint::Make(50, 50), 10.0f, DlPaint());
   auto display_list = builder.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -246,8 +247,9 @@
 
 TEST(DisplayListComplexity, DrawRRect) {
   DisplayListBuilder builder;
-  builder.drawRRect(
-      SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 80), 2.0f, 3.0f));
+  builder.DrawRRect(
+      SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 80), 2.0f, 3.0f),
+      DlPaint());
   auto display_list = builder.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -262,7 +264,7 @@
       SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 80), 2.0f, 3.0f);
   SkRRect inner =
       SkRRect::MakeRectXY(SkRect::MakeXYWH(15, 15, 70, 70), 1.5f, 1.5f);
-  builder.drawDRRect(outer, inner);
+  builder.DrawDRRect(outer, inner, DlPaint());
   auto display_list = builder.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -273,7 +275,8 @@
 
 TEST(DisplayListComplexity, DrawArc) {
   DisplayListBuilder builder;
-  builder.drawArc(SkRect::MakeXYWH(10, 10, 100, 80), 0.0f, 10.0f, true);
+  builder.DrawArc(SkRect::MakeXYWH(10, 10, 100, 80), 0.0f, 10.0f, true,
+                  DlPaint());
   auto display_list = builder.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -287,7 +290,7 @@
   auto vertices = DlVertices::Make(DlVertexMode::kTriangles, points.size(),
                                    points.data(), nullptr, nullptr);
   DisplayListBuilder builder;
-  builder.drawVertices(vertices, DlBlendMode::kSrc);
+  builder.DrawVertices(vertices.get(), DlBlendMode::kSrc, DlPaint());
   auto display_list = builder.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -301,12 +304,12 @@
       "The quick brown fox jumps over the lazy dog.", SkFont());
 
   DisplayListBuilder builder;
-  builder.drawTextBlob(text_blob, 0.0f, 0.0f);
+  builder.DrawTextBlob(text_blob, 0.0f, 0.0f, DlPaint());
   auto display_list = builder.Build();
 
   DisplayListBuilder builder_multiple;
-  builder_multiple.drawTextBlob(text_blob, 0.0f, 0.0f);
-  builder_multiple.drawTextBlob(text_blob, 0.0f, 0.0f);
+  builder_multiple.DrawTextBlob(text_blob, 0.0f, 0.0f, DlPaint());
+  builder_multiple.DrawTextBlob(text_blob, 0.0f, 0.0f, DlPaint());
   auto display_list_multiple = builder_multiple.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -320,18 +323,18 @@
 TEST(DisplayListComplexity, DrawPoints) {
   auto points = GetTestPoints();
   DisplayListBuilder builder_lines;
-  builder_lines.drawPoints(DlCanvas::PointMode::kLines, points.size(),
-                           points.data());
+  builder_lines.DrawPoints(DlCanvas::PointMode::kLines, points.size(),
+                           points.data(), DlPaint());
   auto display_list_lines = builder_lines.Build();
 
   DisplayListBuilder builder_points;
-  builder_points.drawPoints(DlCanvas::PointMode::kPoints, points.size(),
-                            points.data());
+  builder_points.DrawPoints(DlCanvas::PointMode::kPoints, points.size(),
+                            points.data(), DlPaint());
   auto display_list_points = builder_points.Build();
 
   DisplayListBuilder builder_polygon;
-  builder_polygon.drawPoints(DlCanvas::PointMode::kPolygon, points.size(),
-                             points.data());
+  builder_polygon.DrawPoints(DlCanvas::PointMode::kPolygon, points.size(),
+                             points.data(), DlPaint());
   auto display_list_polygon = builder_polygon.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -351,8 +354,8 @@
   auto image = SkImage::MakeFromBitmap(bitmap);
 
   DisplayListBuilder builder;
-  builder.drawImage(DlImage::Make(image), SkPoint::Make(0, 0),
-                    DlImageSampling::kNearestNeighbor, false);
+  builder.DrawImage(DlImage::Make(image), SkPoint::Make(0, 0),
+                    DlImageSampling::kNearestNeighbor, nullptr);
   auto display_list = builder.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -373,8 +376,8 @@
   SkRect dest = SkRect::MakeXYWH(0, 0, 50, 50);
 
   DisplayListBuilder builder;
-  builder.drawImageNine(DlImage::Make(image), center, dest,
-                        DlFilterMode::kNearest, true);
+  builder.DrawImageNine(DlImage::Make(image), center, dest,
+                        DlFilterMode::kNearest, nullptr);
   auto display_list = builder.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -395,8 +398,8 @@
   SkRect dest = SkRect::MakeXYWH(0, 0, 50, 50);
 
   DisplayListBuilder builder;
-  builder.drawImageRect(DlImage::Make(image), src, dest,
-                        DlImageSampling::kNearestNeighbor, true);
+  builder.DrawImageRect(DlImage::Make(image), src, dest,
+                        DlImageSampling::kNearestNeighbor, nullptr);
   auto display_list = builder.Build();
 
   auto calculators = AccumulatorCalculators();
@@ -421,9 +424,9 @@
   }
 
   DisplayListBuilder builder;
-  builder.drawAtlas(DlImage::Make(image), xforms.data(), rects.data(), nullptr,
+  builder.DrawAtlas(DlImage::Make(image), xforms.data(), rects.data(), nullptr,
                     10, DlBlendMode::kSrc, DlImageSampling::kNearestNeighbor,
-                    nullptr, true);
+                    nullptr, nullptr);
   auto display_list = builder.Build();
 
   auto calculators = AccumulatorCalculators();
diff --git a/display_list/display_list_enum_unittests.cc b/display_list/display_list_enum_unittests.cc
deleted file mode 100644
index 0da01c0..0000000
--- a/display_list/display_list_enum_unittests.cc
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "flutter/display_list/display_list_blend_mode.h"
-#include "flutter/display_list/display_list_paint.h"
-#include "flutter/display_list/display_list_sampling_options.h"
-#include "flutter/display_list/display_list_tile_mode.h"
-#include "flutter/display_list/display_list_vertices.h"
-#include "flutter/display_list/types.h"
-#include "gtest/gtest.h"
-#include "include/core/SkSamplingOptions.h"
-
-namespace flutter {
-namespace testing {
-
-TEST(DisplayListEnum, ToDlTileMode) {
-  ASSERT_EQ(ToDl(SkTileMode::kClamp), DlTileMode::kClamp);
-  ASSERT_EQ(ToDl(SkTileMode::kRepeat), DlTileMode::kRepeat);
-  ASSERT_EQ(ToDl(SkTileMode::kMirror), DlTileMode::kMirror);
-  ASSERT_EQ(ToDl(SkTileMode::kDecal), DlTileMode::kDecal);
-}
-
-TEST(DisplayListEnum, ToSkTileMode) {
-  ASSERT_EQ(ToSk(DlTileMode::kClamp), SkTileMode::kClamp);
-  ASSERT_EQ(ToSk(DlTileMode::kRepeat), SkTileMode::kRepeat);
-  ASSERT_EQ(ToSk(DlTileMode::kMirror), SkTileMode::kMirror);
-  ASSERT_EQ(ToSk(DlTileMode::kDecal), SkTileMode::kDecal);
-}
-
-TEST(DisplayListEnum, ToDlDrawStyle) {
-  ASSERT_EQ(ToDl(SkPaint::Style::kFill_Style), DlDrawStyle::kFill);
-  ASSERT_EQ(ToDl(SkPaint::Style::kStroke_Style), DlDrawStyle::kStroke);
-  ASSERT_EQ(ToDl(SkPaint::Style::kStrokeAndFill_Style),
-            DlDrawStyle::kStrokeAndFill);
-}
-
-TEST(DisplayListEnum, ToSkDrawStyle) {
-  ASSERT_EQ(ToSk(DlDrawStyle::kFill), SkPaint::Style::kFill_Style);
-  ASSERT_EQ(ToSk(DlDrawStyle::kStroke), SkPaint::Style::kStroke_Style);
-  ASSERT_EQ(ToSk(DlDrawStyle::kStrokeAndFill),
-            SkPaint::Style::kStrokeAndFill_Style);
-}
-
-TEST(DisplayListEnum, ToDlStrokeCap) {
-  ASSERT_EQ(ToDl(SkPaint::Cap::kButt_Cap), DlStrokeCap::kButt);
-  ASSERT_EQ(ToDl(SkPaint::Cap::kRound_Cap), DlStrokeCap::kRound);
-  ASSERT_EQ(ToDl(SkPaint::Cap::kSquare_Cap), DlStrokeCap::kSquare);
-}
-
-TEST(DisplayListEnum, ToSkStrokeCap) {
-  ASSERT_EQ(ToSk(DlStrokeCap::kButt), SkPaint::Cap::kButt_Cap);
-  ASSERT_EQ(ToSk(DlStrokeCap::kRound), SkPaint::Cap::kRound_Cap);
-  ASSERT_EQ(ToSk(DlStrokeCap::kSquare), SkPaint::Cap::kSquare_Cap);
-}
-
-TEST(DisplayListEnum, ToDlStrokeJoin) {
-  ASSERT_EQ(ToDl(SkPaint::Join::kMiter_Join), DlStrokeJoin::kMiter);
-  ASSERT_EQ(ToDl(SkPaint::Join::kRound_Join), DlStrokeJoin::kRound);
-  ASSERT_EQ(ToDl(SkPaint::Join::kBevel_Join), DlStrokeJoin::kBevel);
-}
-
-TEST(DisplayListEnum, ToSkStrokeJoin) {
-  ASSERT_EQ(ToSk(DlStrokeJoin::kMiter), SkPaint::Join::kMiter_Join);
-  ASSERT_EQ(ToSk(DlStrokeJoin::kRound), SkPaint::Join::kRound_Join);
-  ASSERT_EQ(ToSk(DlStrokeJoin::kBevel), SkPaint::Join::kBevel_Join);
-}
-
-TEST(DisplayListEnum, ToDlVertexMode) {
-  ASSERT_EQ(ToDl(SkVertices::VertexMode::kTriangles_VertexMode),
-            DlVertexMode::kTriangles);
-  ASSERT_EQ(ToDl(SkVertices::VertexMode::kTriangleStrip_VertexMode),
-            DlVertexMode::kTriangleStrip);
-  ASSERT_EQ(ToDl(SkVertices::VertexMode::kTriangleFan_VertexMode),
-            DlVertexMode::kTriangleFan);
-}
-
-TEST(DisplayListEnum, ToSkVertexMode) {
-  ASSERT_EQ(ToSk(DlVertexMode::kTriangles),
-            SkVertices::VertexMode::kTriangles_VertexMode);
-  ASSERT_EQ(ToSk(DlVertexMode::kTriangleStrip),
-            SkVertices::VertexMode::kTriangleStrip_VertexMode);
-  ASSERT_EQ(ToSk(DlVertexMode::kTriangleFan),
-            SkVertices::VertexMode::kTriangleFan_VertexMode);
-}
-
-TEST(DisplayListEnum, ToDlFilterMode) {
-  ASSERT_EQ(ToDl(SkFilterMode::kLinear), DlFilterMode::kLinear);
-  ASSERT_EQ(ToDl(SkFilterMode::kNearest), DlFilterMode::kNearest);
-  ASSERT_EQ(ToDl(SkFilterMode::kLast), DlFilterMode::kLast);
-}
-
-TEST(DisplayListEnum, ToSkFilterMode) {
-  ASSERT_EQ(ToSk(DlFilterMode::kLinear), SkFilterMode::kLinear);
-  ASSERT_EQ(ToSk(DlFilterMode::kNearest), SkFilterMode::kNearest);
-  ASSERT_EQ(ToSk(DlFilterMode::kLast), SkFilterMode::kLast);
-}
-
-TEST(DisplayListEnum, ToDlImageSampling) {
-  ASSERT_EQ(ToDl(SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone)),
-            DlImageSampling::kLinear);
-  ASSERT_EQ(
-      ToDl(SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear)),
-      DlImageSampling::kMipmapLinear);
-  ASSERT_EQ(
-      ToDl(SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone)),
-      DlImageSampling::kNearestNeighbor);
-  ASSERT_EQ(ToDl(SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f})),
-            DlImageSampling::kCubic);
-}
-
-TEST(DisplayListEnum, ToSkSamplingOptions) {
-  ASSERT_EQ(ToSk(DlImageSampling::kLinear),
-            SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone));
-  ASSERT_EQ(ToSk(DlImageSampling::kMipmapLinear),
-            SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear));
-  ASSERT_EQ(ToSk(DlImageSampling::kNearestNeighbor),
-            SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone));
-  ASSERT_EQ(ToSk(DlImageSampling::kCubic),
-            SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f}));
-}
-
-#define CHECK_TO_DLENUM(V) ASSERT_EQ(ToDl(SkBlendMode::V), DlBlendMode::V);
-#define CHECK_TO_SKENUM(V) ASSERT_EQ(ToSk(DlBlendMode::V), SkBlendMode::V);
-
-#define FOR_EACH_ENUM(FUNC) \
-  FUNC(kSrc)                \
-  FUNC(kClear)              \
-  FUNC(kSrc)                \
-  FUNC(kDst)                \
-  FUNC(kSrcOver)            \
-  FUNC(kDstOver)            \
-  FUNC(kSrcIn)              \
-  FUNC(kDstIn)              \
-  FUNC(kSrcOut)             \
-  FUNC(kDstOut)             \
-  FUNC(kSrcATop)            \
-  FUNC(kDstATop)            \
-  FUNC(kXor)                \
-  FUNC(kPlus)               \
-  FUNC(kModulate)           \
-  FUNC(kScreen)             \
-  FUNC(kOverlay)            \
-  FUNC(kDarken)             \
-  FUNC(kLighten)            \
-  FUNC(kColorDodge)         \
-  FUNC(kColorBurn)          \
-  FUNC(kHardLight)          \
-  FUNC(kSoftLight)          \
-  FUNC(kDifference)         \
-  FUNC(kExclusion)          \
-  FUNC(kMultiply)           \
-  FUNC(kHue)                \
-  FUNC(kSaturation)         \
-  FUNC(kColor)              \
-  FUNC(kLuminosity)         \
-  FUNC(kLastCoeffMode)      \
-  FUNC(kLastSeparableMode)  \
-  FUNC(kLastMode)
-
-TEST(DisplayListEnum, ToDlBlendMode){FOR_EACH_ENUM(CHECK_TO_DLENUM)}
-
-TEST(DisplayListEnum, ToSkBlendMode) {
-  FOR_EACH_ENUM(CHECK_TO_SKENUM)
-}
-
-#undef CHECK_TO_DLENUM
-#undef CHECK_TO_SKENUM
-#undef FOR_EACH_ENUM
-
-}  // namespace testing
-}  // namespace flutter
diff --git a/display_list/display_list_image_filter.h b/display_list/display_list_image_filter.h
index 5e29bdb..c497d4f 100644
--- a/display_list/display_list_image_filter.h
+++ b/display_list/display_list_image_filter.h
@@ -29,9 +29,9 @@
   kDilate,
   kErode,
   kMatrix,
-  kComposeFilter,
+  kCompose,
   kColorFilter,
-  kLocalMatrixFilter,
+  kLocalMatrix,
 };
 
 class DlBlurImageFilter;
@@ -42,8 +42,7 @@
 class DlComposeImageFilter;
 class DlColorFilterImageFilter;
 
-class DlImageFilter
-    : public DlAttribute<DlImageFilter, SkImageFilter, DlImageFilterType> {
+class DlImageFilter : public DlAttribute<DlImageFilter, DlImageFilterType> {
  public:
   enum class MatrixCapability {
     kTranslate,
@@ -227,6 +226,16 @@
   explicit DlBlurImageFilter(const DlBlurImageFilter& filter)
       : DlBlurImageFilter(&filter) {}
 
+  static std::shared_ptr<DlImageFilter> Make(SkScalar sigma_x,
+                                             SkScalar sigma_y,
+                                             DlTileMode tile_mode) {
+    if (SkScalarIsFinite(sigma_x) && sigma_x > SK_ScalarNearlyZero &&
+        SkScalarIsFinite(sigma_y) && sigma_y > SK_ScalarNearlyZero) {
+      return std::make_shared<DlBlurImageFilter>(sigma_x, sigma_y, tile_mode);
+    }
+    return nullptr;
+  }
+
   std::shared_ptr<DlImageFilter> shared() const override {
     return std::make_shared<DlBlurImageFilter>(this);
   }
@@ -262,10 +271,6 @@
   SkScalar sigma_y() const { return sigma_y_; }
   DlTileMode tile_mode() const { return tile_mode_; }
 
-  sk_sp<SkImageFilter> skia_object() const override {
-    return SkImageFilters::Blur(sigma_x_, sigma_y_, ToSk(tile_mode_), nullptr);
-  }
-
  protected:
   bool equals_(const DlImageFilter& other) const override {
     FML_DCHECK(other.type() == DlImageFilterType::kBlur);
@@ -289,6 +294,15 @@
   explicit DlDilateImageFilter(const DlDilateImageFilter& filter)
       : DlDilateImageFilter(&filter) {}
 
+  static std::shared_ptr<DlImageFilter> Make(SkScalar radius_x,
+                                             SkScalar radius_y) {
+    if (SkScalarIsFinite(radius_x) && radius_x > SK_ScalarNearlyZero &&
+        SkScalarIsFinite(radius_y) && radius_y > SK_ScalarNearlyZero) {
+      return std::make_shared<DlDilateImageFilter>(radius_x, radius_y);
+    }
+    return nullptr;
+  }
+
   std::shared_ptr<DlImageFilter> shared() const override {
     return std::make_shared<DlDilateImageFilter>(this);
   }
@@ -323,10 +337,6 @@
   SkScalar radius_x() const { return radius_x_; }
   SkScalar radius_y() const { return radius_y_; }
 
-  sk_sp<SkImageFilter> skia_object() const override {
-    return SkImageFilters::Dilate(radius_x_, radius_y_, nullptr);
-  }
-
  protected:
   bool equals_(const DlImageFilter& other) const override {
     FML_DCHECK(other.type() == DlImageFilterType::kDilate);
@@ -348,6 +358,15 @@
   explicit DlErodeImageFilter(const DlErodeImageFilter& filter)
       : DlErodeImageFilter(&filter) {}
 
+  static std::shared_ptr<DlImageFilter> Make(SkScalar radius_x,
+                                             SkScalar radius_y) {
+    if (SkScalarIsFinite(radius_x) && radius_x > SK_ScalarNearlyZero &&
+        SkScalarIsFinite(radius_y) && radius_y > SK_ScalarNearlyZero) {
+      return std::make_shared<DlErodeImageFilter>(radius_x, radius_y);
+    }
+    return nullptr;
+  }
+
   std::shared_ptr<DlImageFilter> shared() const override {
     return std::make_shared<DlErodeImageFilter>(this);
   }
@@ -382,10 +401,6 @@
   SkScalar radius_x() const { return radius_x_; }
   SkScalar radius_y() const { return radius_y_; }
 
-  sk_sp<SkImageFilter> skia_object() const override {
-    return SkImageFilters::Erode(radius_x_, radius_y_, nullptr);
-  }
-
  protected:
   bool equals_(const DlImageFilter& other) const override {
     FML_DCHECK(other.type() == DlImageFilterType::kErode);
@@ -407,6 +422,14 @@
   explicit DlMatrixImageFilter(const DlMatrixImageFilter& filter)
       : DlMatrixImageFilter(&filter) {}
 
+  static std::shared_ptr<DlImageFilter> Make(const SkMatrix& matrix,
+                                             DlImageSampling sampling) {
+    if (matrix.isFinite() && !matrix.isIdentity()) {
+      return std::make_shared<DlMatrixImageFilter>(matrix, sampling);
+    }
+    return nullptr;
+  }
+
   std::shared_ptr<DlImageFilter> shared() const override {
     return std::make_shared<DlMatrixImageFilter>(this);
   }
@@ -460,10 +483,6 @@
     return &input_bounds;
   }
 
-  sk_sp<SkImageFilter> skia_object() const override {
-    return SkImageFilters::MatrixTransform(matrix_, ToSk(sampling_), nullptr);
-  }
-
  protected:
   bool equals_(const DlImageFilter& other) const override {
     FML_DCHECK(other.type() == DlImageFilterType::kMatrix);
@@ -478,8 +497,8 @@
 
 class DlComposeImageFilter final : public DlImageFilter {
  public:
-  DlComposeImageFilter(std::shared_ptr<DlImageFilter> outer,
-                       std::shared_ptr<DlImageFilter> inner)
+  DlComposeImageFilter(std::shared_ptr<const DlImageFilter> outer,
+                       std::shared_ptr<const DlImageFilter> inner)
       : outer_(std::move(outer)), inner_(std::move(inner)) {}
   DlComposeImageFilter(const DlImageFilter* outer, const DlImageFilter* inner)
       : outer_(outer->shared()), inner_(inner->shared()) {}
@@ -490,17 +509,29 @@
   explicit DlComposeImageFilter(const DlComposeImageFilter& filter)
       : DlComposeImageFilter(&filter) {}
 
+  static std::shared_ptr<const DlImageFilter> Make(
+      std::shared_ptr<const DlImageFilter> outer,
+      std::shared_ptr<const DlImageFilter> inner) {
+    if (!outer) {
+      return inner;
+    }
+    if (!inner) {
+      return outer;
+    }
+    return std::make_shared<DlComposeImageFilter>(outer, inner);
+  }
+
   std::shared_ptr<DlImageFilter> shared() const override {
     return std::make_shared<DlComposeImageFilter>(this);
   }
 
   DlImageFilterType type() const override {
-    return DlImageFilterType::kComposeFilter;
+    return DlImageFilterType::kCompose;
   }
   size_t size() const override { return sizeof(*this); }
 
-  std::shared_ptr<DlImageFilter> outer() const { return outer_; }
-  std::shared_ptr<DlImageFilter> inner() const { return inner_; }
+  std::shared_ptr<const DlImageFilter> outer() const { return outer_; }
+  std::shared_ptr<const DlImageFilter> inner() const { return inner_; }
 
   const DlComposeImageFilter* asCompose() const override { return this; }
 
@@ -525,30 +556,25 @@
                                    const SkMatrix& ctm,
                                    SkIRect& input_bounds) const override;
 
-  sk_sp<SkImageFilter> skia_object() const override {
-    return SkImageFilters::Compose(outer_->skia_object(),
-                                   inner_->skia_object());
-  }
-
   MatrixCapability matrix_capability() const override {
     return std::min(outer_->matrix_capability(), inner_->matrix_capability());
   }
 
  protected:
   bool equals_(const DlImageFilter& other) const override {
-    FML_DCHECK(other.type() == DlImageFilterType::kComposeFilter);
+    FML_DCHECK(other.type() == DlImageFilterType::kCompose);
     auto that = static_cast<const DlComposeImageFilter*>(&other);
     return (Equals(outer_, that->outer_) && Equals(inner_, that->inner_));
   }
 
  private:
-  std::shared_ptr<DlImageFilter> outer_;
-  std::shared_ptr<DlImageFilter> inner_;
+  std::shared_ptr<const DlImageFilter> outer_;
+  std::shared_ptr<const DlImageFilter> inner_;
 };
 
 class DlColorFilterImageFilter final : public DlImageFilter {
  public:
-  explicit DlColorFilterImageFilter(std::shared_ptr<DlColorFilter> filter)
+  explicit DlColorFilterImageFilter(std::shared_ptr<const DlColorFilter> filter)
       : color_filter_(std::move(filter)) {}
   explicit DlColorFilterImageFilter(const DlColorFilter* filter)
       : color_filter_(filter->shared()) {}
@@ -559,6 +585,14 @@
   explicit DlColorFilterImageFilter(const DlColorFilterImageFilter& filter)
       : DlColorFilterImageFilter(&filter) {}
 
+  static std::shared_ptr<DlImageFilter> Make(
+      std::shared_ptr<const DlColorFilter> filter) {
+    if (filter) {
+      return std::make_shared<DlColorFilterImageFilter>(filter);
+    }
+    return nullptr;
+  }
+
   std::shared_ptr<DlImageFilter> shared() const override {
     return std::make_shared<DlColorFilterImageFilter>(color_filter_);
   }
@@ -568,7 +602,7 @@
   }
   size_t size() const override { return sizeof(*this); }
 
-  const std::shared_ptr<DlColorFilter> color_filter() const {
+  const std::shared_ptr<const DlColorFilter> color_filter() const {
     return color_filter_;
   }
 
@@ -602,10 +636,6 @@
     return map_device_bounds(output_bounds, ctm, input_bounds);
   }
 
-  sk_sp<SkImageFilter> skia_object() const override {
-    return SkImageFilters::ColorFilter(color_filter_->skia_object(), nullptr);
-  }
-
   MatrixCapability matrix_capability() const override {
     return MatrixCapability::kComplex;
   }
@@ -623,7 +653,7 @@
   }
 
  private:
-  std::shared_ptr<DlColorFilter> color_filter_;
+  std::shared_ptr<const DlColorFilter> color_filter_;
 };
 
 class DlLocalMatrixImageFilter final : public DlImageFilter {
@@ -640,7 +670,7 @@
   }
 
   DlImageFilterType type() const override {
-    return DlImageFilterType::kLocalMatrixFilter;
+    return DlImageFilterType::kLocalMatrix;
   }
   size_t size() const override { return sizeof(*this); }
 
@@ -689,20 +719,9 @@
         output_bounds, SkMatrix::Concat(ctm, matrix_), input_bounds);
   }
 
-  sk_sp<SkImageFilter> skia_object() const override {
-    if (!image_filter_) {
-      return nullptr;
-    }
-    sk_sp<SkImageFilter> skia_object = image_filter_->skia_object();
-    if (!skia_object) {
-      return nullptr;
-    }
-    return skia_object->makeWithLocalMatrix(matrix_);
-  }
-
  protected:
   bool equals_(const DlImageFilter& other) const override {
-    FML_DCHECK(other.type() == DlImageFilterType::kLocalMatrixFilter);
+    FML_DCHECK(other.type() == DlImageFilterType::kLocalMatrix);
     auto that = static_cast<const DlLocalMatrixImageFilter*>(&other);
     return (matrix_ == that->matrix_ &&
             Equals(image_filter_, that->image_filter_));
diff --git a/display_list/display_list_image_filter_unittests.cc b/display_list/display_list_image_filter_unittests.cc
index 7e125db..1cc0231 100644
--- a/display_list/display_list_image_filter_unittests.cc
+++ b/display_list/display_list_image_filter_unittests.cc
@@ -17,21 +17,6 @@
 namespace flutter {
 namespace testing {
 
-TEST(DisplayListImageFilter, BuilderSetGet) {
-  DlBlurImageFilter filter(5.0, 5.0, DlTileMode::kDecal);
-  DisplayListBuilder builder;
-
-  ASSERT_EQ(builder.getImageFilter(), nullptr);
-
-  builder.setImageFilter(&filter);
-  ASSERT_NE(builder.getImageFilter(), nullptr);
-  ASSERT_TRUE(
-      Equals(builder.getImageFilter(), static_cast<DlImageFilter*>(&filter)));
-
-  builder.setImageFilter(nullptr);
-  ASSERT_EQ(builder.getImageFilter(), nullptr);
-}
-
 // SkRect::contains treats the rect as a half-open interval which is
 // appropriate for so many operations. Unfortunately, we are using
 // it here to test containment of the corners of a transformed quad
@@ -708,8 +693,8 @@
       SkImageFilters::ColorFilter(
           SkColorFilters::Blend(SK_ColorRED, SkBlendMode::kSrcOver), nullptr),
       SkImageFilters::Dilate(5.0, 10.0, nullptr),
-      SkImageFilters::MatrixTransform(filter_matrix,
-                                      ToSk(DlImageSampling::kLinear), nullptr),
+      SkImageFilters::MatrixTransform(
+          filter_matrix, SkSamplingOptions(SkFilterMode::kLinear), nullptr),
       SkImageFilters::Compose(
           SkImageFilters::Blur(5.0, 6.0, SkTileMode::kRepeat, nullptr),
           SkImageFilters::ColorFilter(
@@ -785,15 +770,5 @@
   }
 }
 
-TEST(DisplayListImageFilter, LocalImageSkiaNull) {
-  auto blur_filter =
-      std::make_shared<DlBlurImageFilter>(0, 0, DlTileMode::kClamp);
-  DlLocalMatrixImageFilter dl_local_matrix_filter(SkMatrix::RotateDeg(45),
-                                                  blur_filter);
-  // With sigmas set to zero on the blur filter, Skia will return a null filter.
-  // The local matrix filter should return nullptr instead of crashing.
-  ASSERT_EQ(dl_local_matrix_filter.skia_object(), nullptr);
-}
-
 }  // namespace testing
 }  // namespace flutter
diff --git a/display_list/display_list_mask_filter.h b/display_list/display_list_mask_filter.h
index 7e8c678..c48dae5 100644
--- a/display_list/display_list_mask_filter.h
+++ b/display_list/display_list_mask_filter.h
@@ -20,8 +20,7 @@
 // An enumerated type for the supported MaskFilter operations.
 enum class DlMaskFilterType { kBlur };
 
-class DlMaskFilter
-    : public DlAttribute<DlMaskFilter, SkMaskFilter, DlMaskFilterType> {
+class DlMaskFilter : public DlAttribute<DlMaskFilter, DlMaskFilterType> {
  public:
   // Return a DlBlurMaskFilter pointer to this object iff it is a Blur
   // type of MaskFilter, otherwise return nullptr.
@@ -43,6 +42,15 @@
       : DlBlurMaskFilter(filter->style_, filter->sigma_, filter->respect_ctm_) {
   }
 
+  static std::shared_ptr<DlMaskFilter> Make(SkBlurStyle style,
+                                            SkScalar sigma,
+                                            bool respect_ctm = true) {
+    if (SkScalarIsFinite(sigma) && sigma > 0) {
+      return std::make_shared<DlBlurMaskFilter>(style, sigma, respect_ctm);
+    }
+    return nullptr;
+  }
+
   DlMaskFilterType type() const override { return DlMaskFilterType::kBlur; }
   size_t size() const override { return sizeof(*this); }
 
@@ -50,10 +58,6 @@
     return std::make_shared<DlBlurMaskFilter>(this);
   }
 
-  sk_sp<SkMaskFilter> skia_object() const override {
-    return SkMaskFilter::MakeBlur(style_, sigma_, respect_ctm_);
-  }
-
   const DlBlurMaskFilter* asBlur() const override { return this; }
 
   SkBlurStyle style() const { return style_; }
diff --git a/display_list/display_list_mask_filter_unittests.cc b/display_list/display_list_mask_filter_unittests.cc
index 5ae2261..b794360 100644
--- a/display_list/display_list_mask_filter_unittests.cc
+++ b/display_list/display_list_mask_filter_unittests.cc
@@ -12,18 +12,6 @@
 namespace flutter {
 namespace testing {
 
-TEST(DisplayListMaskFilter, BuilderSetGet) {
-  DlBlurMaskFilter filter(SkBlurStyle::kNormal_SkBlurStyle, 5.0);
-  DisplayListBuilder builder;
-  ASSERT_EQ(builder.getMaskFilter(), nullptr);
-  builder.setMaskFilter(&filter);
-  ASSERT_NE(builder.getMaskFilter(), nullptr);
-  ASSERT_TRUE(
-      Equals(builder.getMaskFilter(), static_cast<DlMaskFilter*>(&filter)));
-  builder.setMaskFilter(nullptr);
-  ASSERT_EQ(builder.getMaskFilter(), nullptr);
-}
-
 TEST(DisplayListMaskFilter, BlurConstructor) {
   DlBlurMaskFilter filter(SkBlurStyle::kNormal_SkBlurStyle, 5.0);
 }
diff --git a/display_list/display_list_ops.h b/display_list/display_list_ops.h
index fb949b5..e9c6642 100644
--- a/display_list/display_list_ops.h
+++ b/display_list/display_list_ops.h
@@ -8,8 +8,8 @@
 #include "display_list_color_source.h"
 #include "flutter/display_list/display_list.h"
 #include "flutter/display_list/display_list_blend_mode.h"
-#include "flutter/display_list/display_list_dispatcher.h"
 #include "flutter/display_list/display_list_sampling_options.h"
+#include "flutter/display_list/dl_op_receiver.h"
 #include "flutter/display_list/types.h"
 #include "flutter/fml/macros.h"
 
@@ -37,7 +37,7 @@
 // cheap to deal with a complicated "lifetime" tracking to
 // determine if they will be used.
 struct DispatchContext {
-  Dispatcher& dispatcher;
+  DlOpReceiver& receiver;
 
   int cur_index;
   int next_render_index;
@@ -111,7 +111,7 @@
     const bool value;                                        \
                                                              \
     void dispatch(DispatchContext& ctx) const {              \
-      ctx.dispatcher.set##name(value);                       \
+      ctx.receiver.set##name(value);                         \
     }                                                        \
   };
 DEFINE_SET_BOOL_OP(AntiAlias)
@@ -129,7 +129,7 @@
     const DlStroke##name value;                                          \
                                                                          \
     void dispatch(DispatchContext& ctx) const {                          \
-      ctx.dispatcher.setStroke##name(value);                             \
+      ctx.receiver.setStroke##name(value);                               \
     }                                                                    \
   };
 DEFINE_SET_ENUM_OP(Cap)
@@ -144,7 +144,7 @@
 
   const DlDrawStyle style;
 
-  void dispatch(DispatchContext& ctx) const { ctx.dispatcher.setStyle(style); }
+  void dispatch(DispatchContext& ctx) const { ctx.receiver.setStyle(style); }
 };
 // 4 byte header + 4 byte payload packs into minimum 8 bytes
 struct SetStrokeWidthOp final : DLOp {
@@ -155,7 +155,7 @@
   const float width;
 
   void dispatch(DispatchContext& ctx) const {
-    ctx.dispatcher.setStrokeWidth(width);
+    ctx.receiver.setStrokeWidth(width);
   }
 };
 // 4 byte header + 4 byte payload packs into minimum 8 bytes
@@ -167,7 +167,7 @@
   const float limit;
 
   void dispatch(DispatchContext& ctx) const {
-    ctx.dispatcher.setStrokeMiter(limit);
+    ctx.receiver.setStrokeMiter(limit);
   }
 };
 
@@ -179,7 +179,7 @@
 
   const DlColor color;
 
-  void dispatch(DispatchContext& ctx) const { ctx.dispatcher.setColor(color); }
+  void dispatch(DispatchContext& ctx) const { ctx.receiver.setColor(color); }
 };
 // 4 byte header + 4 byte payload packs into minimum 8 bytes
 struct SetBlendModeOp final : DLOp {
@@ -189,8 +189,8 @@
 
   const DlBlendMode mode;
 
-  void dispatch(DispatchContext& ctx) const {
-    ctx.dispatcher.setBlendMode(mode);
+  void dispatch(DispatchContext& ctx) const {  //
+    ctx.receiver.setBlendMode(mode);
   }
 };
 
@@ -207,7 +207,7 @@
     Clear##name##Op() {}                                                    \
                                                                             \
     void dispatch(DispatchContext& ctx) const {                             \
-      ctx.dispatcher.set##name(nullptr);                                    \
+      ctx.receiver.set##name(nullptr);                                      \
     }                                                                       \
   };                                                                        \
   struct SetPod##name##Op final : DLOp {                                    \
@@ -217,7 +217,7 @@
                                                                             \
     void dispatch(DispatchContext& ctx) const {                             \
       const Dl##name* filter = reinterpret_cast<const Dl##name*>(this + 1); \
-      ctx.dispatcher.set##name(filter);                                     \
+      ctx.receiver.set##name(filter);                                       \
     }                                                                       \
   };
 DEFINE_SET_CLEAR_DLATTR_OP(ColorFilter, ColorFilter, filter)
@@ -242,7 +242,7 @@
   const DlImageColorSource source;
 
   void dispatch(DispatchContext& ctx) const {
-    ctx.dispatcher.setColorSource(&source);
+    ctx.receiver.setColorSource(&source);
   }
 };
 
@@ -259,7 +259,7 @@
   const DlRuntimeEffectColorSource source;
 
   void dispatch(DispatchContext& ctx) const {
-    ctx.dispatcher.setColorSource(&source);
+    ctx.receiver.setColorSource(&source);
   }
 
   DisplayListCompare equals(const SetRuntimeEffectColorSourceOp* other) const {
@@ -278,7 +278,7 @@
   const DlSceneColorSource source;
 
   void dispatch(DispatchContext& ctx) const {
-    ctx.dispatcher.setColorSource(&source);
+    ctx.receiver.setColorSource(&source);
   }
 
   DisplayListCompare equals(const SetSceneColorSourceOp* other) const {
@@ -298,7 +298,7 @@
   const std::shared_ptr<DlImageFilter> filter;
 
   void dispatch(DispatchContext& ctx) const {
-    ctx.dispatcher.setImageFilter(filter.get());
+    ctx.receiver.setImageFilter(filter.get());
   }
 
   DisplayListCompare equals(const SetSharedImageFilterOp* other) const {
@@ -336,7 +336,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (save_needed(ctx)) {
-      ctx.dispatcher.save();
+      ctx.receiver.save();
     }
   }
 };
@@ -348,7 +348,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (save_needed(ctx)) {
-      ctx.dispatcher.saveLayer(nullptr, options);
+      ctx.receiver.saveLayer(nullptr, options);
     }
   }
 };
@@ -363,7 +363,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (save_needed(ctx)) {
-      ctx.dispatcher.saveLayer(&rect, options);
+      ctx.receiver.saveLayer(&rect, options);
     }
   }
 };
@@ -379,7 +379,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (save_needed(ctx)) {
-      ctx.dispatcher.saveLayer(nullptr, options, backdrop.get());
+      ctx.receiver.saveLayer(nullptr, options, backdrop.get());
     }
   }
 
@@ -403,7 +403,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (save_needed(ctx)) {
-      ctx.dispatcher.saveLayer(&rect, options, backdrop.get());
+      ctx.receiver.saveLayer(&rect, options, backdrop.get());
     }
   }
 
@@ -423,7 +423,7 @@
   void dispatch(DispatchContext& ctx) const {
     DispatchContext::SaveInfo& info = ctx.save_infos.back();
     if (info.save_was_needed) {
-      ctx.dispatcher.restore();
+      ctx.receiver.restore();
     }
     ctx.next_restore_index = info.previous_restore_index;
     ctx.save_infos.pop_back();
@@ -447,7 +447,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.translate(tx, ty);
+      ctx.receiver.translate(tx, ty);
     }
   }
 };
@@ -463,7 +463,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.scale(sx, sy);
+      ctx.receiver.scale(sx, sy);
     }
   }
 };
@@ -477,7 +477,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.rotate(degrees);
+      ctx.receiver.rotate(degrees);
     }
   }
 };
@@ -493,7 +493,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.skew(sx, sy);
+      ctx.receiver.skew(sx, sy);
     }
   }
 };
@@ -513,8 +513,8 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.transform2DAffine(mxx, mxy, mxt,  //
-                                       myx, myy, myt);
+      ctx.receiver.transform2DAffine(mxx, mxy, mxt,  //
+                                     myx, myy, myt);
     }
   }
 };
@@ -542,10 +542,10 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.transformFullPerspective(mxx, mxy, mxz, mxt,  //
-                                              myx, myy, myz, myt,  //
-                                              mzx, mzy, mzz, mzt,  //
-                                              mwx, mwy, mwz, mwt);
+      ctx.receiver.transformFullPerspective(mxx, mxy, mxz, mxt,  //
+                                            myx, myy, myz, myt,  //
+                                            mzx, mzy, mzz, mzt,  //
+                                            mwx, mwy, mwz, mwt);
     }
   }
 };
@@ -558,7 +558,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.transformReset();
+      ctx.receiver.transformReset();
     }
   }
 };
@@ -585,8 +585,8 @@
                                                                            \
     void dispatch(DispatchContext& ctx) const {                            \
       if (op_needed(ctx)) {                                                \
-        ctx.dispatcher.clip##shapetype(shape, DlCanvas::ClipOp::k##clipop, \
-                                       is_aa);                             \
+        ctx.receiver.clip##shapetype(shape, DlCanvas::ClipOp::k##clipop,   \
+                                     is_aa);                               \
       }                                                                    \
     }                                                                      \
   };
@@ -596,27 +596,27 @@
 DEFINE_CLIP_SHAPE_OP(RRect, Difference)
 #undef DEFINE_CLIP_SHAPE_OP
 
-#define DEFINE_CLIP_PATH_OP(clipop)                                        \
-  struct Clip##clipop##PathOp final : TransformClipOpBase {                \
-    static const auto kType = DisplayListOpType::kClip##clipop##Path;      \
-                                                                           \
-    Clip##clipop##PathOp(SkPath path, bool is_aa)                          \
-        : is_aa(is_aa), path(path) {}                                      \
-                                                                           \
-    const bool is_aa;                                                      \
-    const SkPath path;                                                     \
-                                                                           \
-    void dispatch(DispatchContext& ctx) const {                            \
-      if (op_needed(ctx)) {                                                \
-        ctx.dispatcher.clipPath(path, DlCanvas::ClipOp::k##clipop, is_aa); \
-      }                                                                    \
-    }                                                                      \
-                                                                           \
-    DisplayListCompare equals(const Clip##clipop##PathOp* other) const {   \
-      return is_aa == other->is_aa && path == other->path                  \
-                 ? DisplayListCompare::kEqual                              \
-                 : DisplayListCompare::kNotEqual;                          \
-    }                                                                      \
+#define DEFINE_CLIP_PATH_OP(clipop)                                      \
+  struct Clip##clipop##PathOp final : TransformClipOpBase {              \
+    static const auto kType = DisplayListOpType::kClip##clipop##Path;    \
+                                                                         \
+    Clip##clipop##PathOp(SkPath path, bool is_aa)                        \
+        : is_aa(is_aa), path(path) {}                                    \
+                                                                         \
+    const bool is_aa;                                                    \
+    const SkPath path;                                                   \
+                                                                         \
+    void dispatch(DispatchContext& ctx) const {                          \
+      if (op_needed(ctx)) {                                              \
+        ctx.receiver.clipPath(path, DlCanvas::ClipOp::k##clipop, is_aa); \
+      }                                                                  \
+    }                                                                    \
+                                                                         \
+    DisplayListCompare equals(const Clip##clipop##PathOp* other) const { \
+      return is_aa == other->is_aa && path == other->path                \
+                 ? DisplayListCompare::kEqual                            \
+                 : DisplayListCompare::kNotEqual;                        \
+    }                                                                    \
   };
 DEFINE_CLIP_PATH_OP(Intersect)
 DEFINE_CLIP_PATH_OP(Difference)
@@ -636,7 +636,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.drawPaint();
+      ctx.receiver.drawPaint();
     }
   }
 };
@@ -652,7 +652,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.drawColor(color, mode);
+      ctx.receiver.drawColor(color, mode);
     }
   }
 };
@@ -672,7 +672,7 @@
                                                                           \
     void dispatch(DispatchContext& ctx) const {                           \
       if (op_needed(ctx)) {                                               \
-        ctx.dispatcher.draw##op_name(arg_name);                           \
+        ctx.receiver.draw##op_name(arg_name);                             \
       }                                                                   \
     }                                                                     \
   };
@@ -692,7 +692,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.drawPath(path);
+      ctx.receiver.drawPath(path);
     }
   }
 
@@ -720,7 +720,7 @@
                                                                  \
     void dispatch(DispatchContext& ctx) const {                  \
       if (op_needed(ctx)) {                                      \
-        ctx.dispatcher.draw##op_name(name1, name2);              \
+        ctx.receiver.draw##op_name(name1, name2);                \
       }                                                          \
     }                                                            \
   };
@@ -743,7 +743,7 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.drawArc(bounds, start, sweep, center);
+      ctx.receiver.drawArc(bounds, start, sweep, center);
     }
   }
 };
@@ -754,20 +754,20 @@
 // so this op will always pack efficiently
 // The point type is packed into 3 different OpTypes to avoid expanding
 // the fixed payload beyond the 8 bytes
-#define DEFINE_DRAW_POINTS_OP(name, mode)                                 \
-  struct Draw##name##Op final : DrawOpBase {                              \
-    static const auto kType = DisplayListOpType::kDraw##name;             \
-                                                                          \
-    explicit Draw##name##Op(uint32_t count) : count(count) {}             \
-                                                                          \
-    const uint32_t count;                                                 \
-                                                                          \
-    void dispatch(DispatchContext& ctx) const {                           \
-      if (op_needed(ctx)) {                                               \
-        const SkPoint* pts = reinterpret_cast<const SkPoint*>(this + 1);  \
-        ctx.dispatcher.drawPoints(DlCanvas::PointMode::mode, count, pts); \
-      }                                                                   \
-    }                                                                     \
+#define DEFINE_DRAW_POINTS_OP(name, mode)                                \
+  struct Draw##name##Op final : DrawOpBase {                             \
+    static const auto kType = DisplayListOpType::kDraw##name;            \
+                                                                         \
+    explicit Draw##name##Op(uint32_t count) : count(count) {}            \
+                                                                         \
+    const uint32_t count;                                                \
+                                                                         \
+    void dispatch(DispatchContext& ctx) const {                          \
+      if (op_needed(ctx)) {                                              \
+        const SkPoint* pts = reinterpret_cast<const SkPoint*>(this + 1); \
+        ctx.receiver.drawPoints(DlCanvas::PointMode::mode, count, pts);  \
+      }                                                                  \
+    }                                                                    \
   };
 DEFINE_DRAW_POINTS_OP(Points, kPoints);
 DEFINE_DRAW_POINTS_OP(Lines, kLines);
@@ -792,38 +792,38 @@
     if (op_needed(ctx)) {
       const DlVertices* vertices =
           reinterpret_cast<const DlVertices*>(this + 1);
-      ctx.dispatcher.drawVertices(vertices, mode);
+      ctx.receiver.drawVertices(vertices, mode);
     }
   }
 };
 
 // 4 byte header + 40 byte payload uses 44 bytes but is rounded up to 48 bytes
 // (4 bytes unused)
-#define DEFINE_DRAW_IMAGE_OP(name, with_attributes)                        \
-  struct name##Op final : DrawOpBase {                                     \
-    static const auto kType = DisplayListOpType::k##name;                  \
-                                                                           \
-    name##Op(const sk_sp<DlImage> image,                                   \
-             const SkPoint& point,                                         \
-             DlImageSampling sampling)                                     \
-        : point(point), sampling(sampling), image(std::move(image)) {}     \
-                                                                           \
-    const SkPoint point;                                                   \
-    const DlImageSampling sampling;                                        \
-    const sk_sp<DlImage> image;                                            \
-                                                                           \
-    void dispatch(DispatchContext& ctx) const {                            \
-      if (op_needed(ctx)) {                                                \
-        ctx.dispatcher.drawImage(image, point, sampling, with_attributes); \
-      }                                                                    \
-    }                                                                      \
-                                                                           \
-    DisplayListCompare equals(const name##Op* other) const {               \
-      return (point == other->point && sampling == other->sampling &&      \
-              image->Equals(other->image))                                 \
-                 ? DisplayListCompare::kEqual                              \
-                 : DisplayListCompare::kNotEqual;                          \
-    }                                                                      \
+#define DEFINE_DRAW_IMAGE_OP(name, with_attributes)                      \
+  struct name##Op final : DrawOpBase {                                   \
+    static const auto kType = DisplayListOpType::k##name;                \
+                                                                         \
+    name##Op(const sk_sp<DlImage> image,                                 \
+             const SkPoint& point,                                       \
+             DlImageSampling sampling)                                   \
+        : point(point), sampling(sampling), image(std::move(image)) {}   \
+                                                                         \
+    const SkPoint point;                                                 \
+    const DlImageSampling sampling;                                      \
+    const sk_sp<DlImage> image;                                          \
+                                                                         \
+    void dispatch(DispatchContext& ctx) const {                          \
+      if (op_needed(ctx)) {                                              \
+        ctx.receiver.drawImage(image, point, sampling, with_attributes); \
+      }                                                                  \
+    }                                                                    \
+                                                                         \
+    DisplayListCompare equals(const name##Op* other) const {             \
+      return (point == other->point && sampling == other->sampling &&    \
+              image->Equals(other->image))                               \
+                 ? DisplayListCompare::kEqual                            \
+                 : DisplayListCompare::kNotEqual;                        \
+    }                                                                    \
   };
 DEFINE_DRAW_IMAGE_OP(DrawImage, false)
 DEFINE_DRAW_IMAGE_OP(DrawImageWithAttr, true)
@@ -839,7 +839,7 @@
                   const SkRect& dst,
                   DlImageSampling sampling,
                   bool render_with_attributes,
-                  SkCanvas::SrcRectConstraint constraint)
+                  DlCanvas::SrcRectConstraint constraint)
       : src(src),
         dst(dst),
         sampling(sampling),
@@ -851,13 +851,13 @@
   const SkRect dst;
   const DlImageSampling sampling;
   const bool render_with_attributes;
-  const SkCanvas::SrcRectConstraint constraint;
+  const DlCanvas::SrcRectConstraint constraint;
   const sk_sp<DlImage> image;
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.drawImageRect(image, src, dst, sampling,
-                                   render_with_attributes, constraint);
+      ctx.receiver.drawImageRect(image, src, dst, sampling,
+                                 render_with_attributes, constraint);
     }
   }
 
@@ -889,8 +889,8 @@
                                                                            \
     void dispatch(DispatchContext& ctx) const {                            \
       if (op_needed(ctx)) {                                                \
-        ctx.dispatcher.drawImageNine(image, center, dst, mode,             \
-                                     render_with_attributes);              \
+        ctx.receiver.drawImageNine(image, center, dst, mode,               \
+                                   render_with_attributes);                \
       }                                                                    \
     }                                                                      \
                                                                            \
@@ -976,8 +976,8 @@
       const DlColor* colors =
           has_colors ? reinterpret_cast<const DlColor*>(tex + count) : nullptr;
       const DlBlendMode mode = static_cast<DlBlendMode>(mode_index);
-      ctx.dispatcher.drawAtlas(atlas, xform, tex, colors, count, mode, sampling,
-                               nullptr, render_with_attributes);
+      ctx.receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling,
+                             nullptr, render_with_attributes);
     }
   }
 
@@ -1021,8 +1021,8 @@
       const DlColor* colors =
           has_colors ? reinterpret_cast<const DlColor*>(tex + count) : nullptr;
       const DlBlendMode mode = static_cast<DlBlendMode>(mode_index);
-      ctx.dispatcher.drawAtlas(atlas, xform, tex, colors, count, mode, sampling,
-                               &cull_rect, render_with_attributes);
+      ctx.receiver.drawAtlas(atlas, xform, tex, colors, count, mode, sampling,
+                             &cull_rect, render_with_attributes);
     }
   }
 
@@ -1041,19 +1041,22 @@
 struct DrawDisplayListOp final : DrawOpBase {
   static const auto kType = DisplayListOpType::kDrawDisplayList;
 
-  explicit DrawDisplayListOp(const sk_sp<DisplayList> display_list)
-      : display_list(std::move(display_list)) {}
+  explicit DrawDisplayListOp(const sk_sp<DisplayList> display_list,
+                             SkScalar opacity)
+      : opacity(opacity), display_list(std::move(display_list)) {}
 
+  SkScalar opacity;
   const sk_sp<DisplayList> display_list;
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.drawDisplayList(display_list);
+      ctx.receiver.drawDisplayList(display_list, opacity);
     }
   }
 
   DisplayListCompare equals(const DrawDisplayListOp* other) const {
-    return display_list->Equals(other->display_list)
+    return (opacity == other->opacity &&
+            display_list->Equals(other->display_list))
                ? DisplayListCompare::kEqual
                : DisplayListCompare::kNotEqual;
   }
@@ -1073,33 +1076,33 @@
 
   void dispatch(DispatchContext& ctx) const {
     if (op_needed(ctx)) {
-      ctx.dispatcher.drawTextBlob(blob, x, y);
+      ctx.receiver.drawTextBlob(blob, x, y);
     }
   }
 };
 
 // 4 byte header + 28 byte payload packs evenly into 32 bytes
-#define DEFINE_DRAW_SHADOW_OP(name, transparent_occluder)             \
-  struct Draw##name##Op final : DrawOpBase {                          \
-    static const auto kType = DisplayListOpType::kDraw##name;         \
-                                                                      \
-    Draw##name##Op(const SkPath& path,                                \
-                   DlColor color,                                     \
-                   SkScalar elevation,                                \
-                   SkScalar dpr)                                      \
-        : color(color), elevation(elevation), dpr(dpr), path(path) {} \
-                                                                      \
-    const DlColor color;                                              \
-    const SkScalar elevation;                                         \
-    const SkScalar dpr;                                               \
-    const SkPath path;                                                \
-                                                                      \
-    void dispatch(DispatchContext& ctx) const {                       \
-      if (op_needed(ctx)) {                                           \
-        ctx.dispatcher.drawShadow(path, color, elevation,             \
-                                  transparent_occluder, dpr);         \
-      }                                                               \
-    }                                                                 \
+#define DEFINE_DRAW_SHADOW_OP(name, transparent_occluder)                     \
+  struct Draw##name##Op final : DrawOpBase {                                  \
+    static const auto kType = DisplayListOpType::kDraw##name;                 \
+                                                                              \
+    Draw##name##Op(const SkPath& path,                                        \
+                   DlColor color,                                             \
+                   SkScalar elevation,                                        \
+                   SkScalar dpr)                                              \
+        : color(color), elevation(elevation), dpr(dpr), path(path) {}         \
+                                                                              \
+    const DlColor color;                                                      \
+    const SkScalar elevation;                                                 \
+    const SkScalar dpr;                                                       \
+    const SkPath path;                                                        \
+                                                                              \
+    void dispatch(DispatchContext& ctx) const {                               \
+      if (op_needed(ctx)) {                                                   \
+        ctx.receiver.drawShadow(path, color, elevation, transparent_occluder, \
+                                dpr);                                         \
+      }                                                                       \
+    }                                                                         \
   };
 DEFINE_DRAW_SHADOW_OP(Shadow, false)
 DEFINE_DRAW_SHADOW_OP(ShadowTransparentOccluder, true)
diff --git a/display_list/display_list_paint.h b/display_list/display_list_paint.h
index 871be22..7bfdbfa 100644
--- a/display_list/display_list_paint.h
+++ b/display_list/display_list_paint.h
@@ -25,14 +25,6 @@
   kDefaultStyle = kFill,
 };
 
-inline DlDrawStyle ToDl(SkPaint::Style style) {
-  return static_cast<DlDrawStyle>(style);
-}
-
-inline SkPaint::Style ToSk(DlDrawStyle style) {
-  return static_cast<SkPaint::Style>(style);
-}
-
 enum class DlStrokeCap {
   kButt,    //!< no stroke extension
   kRound,   //!< adds circle
@@ -42,14 +34,6 @@
   kDefaultCap = kButt,
 };
 
-inline DlStrokeCap ToDl(SkPaint::Cap cap) {
-  return static_cast<DlStrokeCap>(cap);
-}
-
-inline SkPaint::Cap ToSk(DlStrokeCap cap) {
-  return static_cast<SkPaint::Cap>(cap);
-}
-
 enum class DlStrokeJoin {
   kMiter,  //!< extends to miter limit
   kRound,  //!< adds circle
@@ -59,14 +43,6 @@
   kDefaultJoin = kMiter,
 };
 
-inline DlStrokeJoin ToDl(SkPaint::Join join) {
-  return static_cast<DlStrokeJoin>(join);
-}
-
-inline SkPaint::Join ToSk(DlStrokeJoin join) {
-  return static_cast<SkPaint::Join>(join);
-}
-
 class DlPaint {
  public:
   static constexpr DlColor kDefaultColor = DlColor::kBlack();
@@ -174,7 +150,7 @@
   }
   const DlColorFilter* getColorFilterPtr() const { return colorFilter_.get(); }
   DlPaint& setColorFilter(const std::shared_ptr<const DlColorFilter> filter) {
-    colorFilter_ = filter ? filter->shared() : nullptr;
+    colorFilter_ = filter;
     return *this;
   }
   DlPaint& setColorFilter(const DlColorFilter* filter) {
@@ -216,6 +192,10 @@
     pathEffect_ = pathEffect;
     return *this;
   }
+  DlPaint& setPathEffect(const DlPathEffect* effect) {
+    pathEffect_ = effect ? effect->shared() : nullptr;
+    return *this;
+  }
 
   bool isDefault() const { return *this == kDefault; }
 
diff --git a/display_list/display_list_path_effect.h b/display_list/display_list_path_effect.h
index d03f194..f8bd030 100644
--- a/display_list/display_list_path_effect.h
+++ b/display_list/display_list_path_effect.h
@@ -25,8 +25,7 @@
   kDash,
 };
 
-class DlPathEffect
-    : public DlAttribute<DlPathEffect, SkPathEffect, DlPathEffectType> {
+class DlPathEffect : public DlAttribute<DlPathEffect, DlPathEffectType> {
  public:
   virtual const DlDashPathEffect* asDash() const { return nullptr; }
 
@@ -77,13 +76,11 @@
 
   const DlDashPathEffect* asDash() const override { return this; }
 
-  sk_sp<SkPathEffect> skia_object() const override {
-    return SkDashPathEffect::Make(intervals(), count_, phase_);
-  }
-
   const SkScalar* intervals() const {
     return reinterpret_cast<const SkScalar*>(this + 1);
   }
+  int count() const { return count_; }
+  SkScalar phase() const { return phase_; }
 
   std::optional<SkRect> effect_bounds(SkRect& rect) const override;
 
diff --git a/display_list/display_list_path_effect_unittests.cc b/display_list/display_list_path_effect_unittests.cc
index 0067c37..e111108 100644
--- a/display_list/display_list_path_effect_unittests.cc
+++ b/display_list/display_list_path_effect_unittests.cc
@@ -13,19 +13,6 @@
 namespace flutter {
 namespace testing {
 
-TEST(DisplayListPathEffect, BuilderSetGet) {
-  const SkScalar test_dashes[] = {4.0, 2.0};
-  auto dash_path_effect = DlDashPathEffect::Make(test_dashes, 2, 0.0);
-  DisplayListBuilder builder;
-  ASSERT_EQ(builder.getPathEffect(), nullptr);
-  builder.setPathEffect(dash_path_effect.get());
-  ASSERT_NE(builder.getPathEffect(), nullptr);
-  ASSERT_TRUE(Equals(builder.getPathEffect(),
-                     static_cast<DlPathEffect*>(dash_path_effect.get())));
-  builder.setPathEffect(nullptr);
-  ASSERT_EQ(builder.getPathEffect(), nullptr);
-}
-
 TEST(DisplayListPathEffect, EffectShared) {
   const SkScalar TestDashes2[] = {1.0, 1.5};
   auto effect = DlDashPathEffect::Make(TestDashes2, 2, 0.0);
diff --git a/display_list/display_list_sampling_options.h b/display_list/display_list_sampling_options.h
index b3f1afa..c3896bf 100644
--- a/display_list/display_list_sampling_options.h
+++ b/display_list/display_list_sampling_options.h
@@ -16,14 +16,6 @@
   kLast = kLinear,
 };
 
-inline DlFilterMode ToDl(const SkFilterMode filter_mode) {
-  return static_cast<DlFilterMode>(filter_mode);
-}
-
-inline SkFilterMode ToSk(const DlFilterMode filter_mode) {
-  return static_cast<SkFilterMode>(filter_mode);
-}
-
 enum class DlImageSampling {
   kNearestNeighbor,
   kLinear,
@@ -31,34 +23,6 @@
   kCubic,
 };
 
-inline DlImageSampling ToDl(const SkSamplingOptions& so) {
-  if (so.useCubic) {
-    return DlImageSampling::kCubic;
-  }
-  if (so.filter == SkFilterMode::kLinear) {
-    if (so.mipmap == SkMipmapMode::kNone) {
-      return DlImageSampling::kLinear;
-    }
-    if (so.mipmap == SkMipmapMode::kLinear) {
-      return DlImageSampling::kMipmapLinear;
-    }
-  }
-  return DlImageSampling::kNearestNeighbor;
-}
-
-inline SkSamplingOptions ToSk(DlImageSampling sampling) {
-  switch (sampling) {
-    case DlImageSampling::kCubic:
-      return SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f});
-    case DlImageSampling::kLinear:
-      return SkSamplingOptions(SkFilterMode::kLinear);
-    case DlImageSampling::kMipmapLinear:
-      return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
-    case DlImageSampling::kNearestNeighbor:
-      return SkSamplingOptions(SkFilterMode::kNearest);
-  }
-}
-
 }  // namespace flutter
 
 #endif  // FLUTTER_DISPLAY_LIST_DISPLAY_LIST_SAMPLING_OPTIONS_H_
diff --git a/display_list/display_list_tile_mode.h b/display_list/display_list_tile_mode.h
index 0be9b54..15c04cf 100644
--- a/display_list/display_list_tile_mode.h
+++ b/display_list/display_list_tile_mode.h
@@ -30,14 +30,6 @@
   kDecal,
 };
 
-inline DlTileMode ToDl(SkTileMode sk_mode) {
-  return static_cast<DlTileMode>(sk_mode);
-}
-
-inline SkTileMode ToSk(DlTileMode dl_mode) {
-  return static_cast<SkTileMode>(dl_mode);
-}
-
 }  // namespace flutter
 
 #endif  // FLUTTER_DISPLAY_LIST_DISPLAY_LIST_TILE_MODE_H_
diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc
index b2ac33e..03c05c0 100644
--- a/display_list/display_list_unittests.cc
+++ b/display_list/display_list_unittests.cc
@@ -14,6 +14,7 @@
 #include "flutter/display_list/display_list_paint.h"
 #include "flutter/display_list/display_list_rtree.h"
 #include "flutter/display_list/display_list_utils.h"
+#include "flutter/display_list/skia/dl_sk_dispatcher.h"
 #include "flutter/display_list/testing/dl_test_snippets.h"
 #include "flutter/fml/logging.h"
 #include "flutter/fml/math.h"
@@ -24,6 +25,11 @@
 #include "third_party/skia/include/core/SkSurface.h"
 
 namespace flutter {
+
+DlOpReceiver& DisplayListBuilderTestingAccessor(DisplayListBuilder& builder) {
+  return builder.asReceiver();
+}
+
 namespace testing {
 
 static std::vector<testing::DisplayListInvocationGroup> allGroups =
@@ -32,7 +38,61 @@
 using ClipOp = DlCanvas::ClipOp;
 using PointMode = DlCanvas::PointMode;
 
-TEST(DisplayList, BuilderCanBeReused) {
+template <typename BaseT>
+class DisplayListTestBase : public BaseT {
+ public:
+  DisplayListTestBase() = default;
+
+  static DlOpReceiver& ToReceiver(DisplayListBuilder& builder) {
+    return DisplayListBuilderTestingAccessor(builder);
+  }
+
+  static sk_sp<DisplayList> Build(DisplayListInvocation& invocation) {
+    DisplayListBuilder builder;
+    invocation.Invoke(ToReceiver(builder));
+    return builder.Build();
+  }
+
+  static sk_sp<DisplayList> Build(size_t g_index, size_t v_index) {
+    DisplayListBuilder builder;
+    DlOpReceiver& receiver =
+        DisplayListTestBase<::testing::Test>::ToReceiver(builder);
+    unsigned int op_count = 0;
+    size_t byte_count = 0;
+    for (size_t i = 0; i < allGroups.size(); i++) {
+      DisplayListInvocationGroup& group = allGroups[i];
+      size_t j = (i == g_index ? v_index : 0);
+      if (j >= group.variants.size()) {
+        continue;
+      }
+      DisplayListInvocation& invocation = group.variants[j];
+      op_count += invocation.op_count();
+      byte_count += invocation.raw_byte_count();
+      invocation.invoker(receiver);
+    }
+    sk_sp<DisplayList> dl = builder.Build();
+    std::string name;
+    if (g_index >= allGroups.size()) {
+      name = "Default";
+    } else {
+      name = allGroups[g_index].op_name;
+      if (v_index >= allGroups[g_index].variants.size()) {
+        name += " skipped";
+      } else {
+        name += " variant " + std::to_string(v_index + 1);
+      }
+    }
+    EXPECT_EQ(dl->op_count(false), op_count) << name;
+    EXPECT_EQ(dl->bytes(false), byte_count + sizeof(DisplayList)) << name;
+    return dl;
+  }
+
+ private:
+  FML_DISALLOW_COPY_AND_ASSIGN(DisplayListTestBase);
+};
+using DisplayListTest = DisplayListTestBase<::testing::Test>;
+
+TEST_F(DisplayListTest, BuilderCanBeReused) {
   DisplayListBuilder builder(kTestBounds);
   builder.DrawRect(kTestBounds, DlPaint());
   auto dl = builder.Build();
@@ -41,7 +101,7 @@
   ASSERT_TRUE(dl->Equals(dl2));
 }
 
-TEST(DisplayList, BuilderBoundsTransformComparedToSkia) {
+TEST_F(DisplayListTest, BuilderBoundsTransformComparedToSkia) {
   const SkRect frame_rect = SkRect::MakeLTRB(10, 10, 100, 100);
   DisplayListBuilder builder(frame_rect);
   SkPictureRecorder recorder;
@@ -53,60 +113,60 @@
   ASSERT_EQ(builder.GetTransform(), canvas->getTotalMatrix());
 }
 
-TEST(DisplayList, BuilderInitialClipBounds) {
+TEST_F(DisplayListTest, BuilderInitialClipBounds) {
   SkRect cull_rect = SkRect::MakeWH(100, 100);
   SkRect clip_bounds = SkRect::MakeWH(100, 100);
   DisplayListBuilder builder(cull_rect);
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds);
 }
 
-TEST(DisplayList, BuilderInitialClipBoundsNaN) {
+TEST_F(DisplayListTest, BuilderInitialClipBoundsNaN) {
   SkRect cull_rect = SkRect::MakeWH(SK_ScalarNaN, SK_ScalarNaN);
   SkRect clip_bounds = SkRect::MakeEmpty();
   DisplayListBuilder builder(cull_rect);
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds);
 }
 
-TEST(DisplayList, BuilderClipBoundsAfterClipRect) {
+TEST_F(DisplayListTest, BuilderClipBoundsAfterClipRect) {
   SkRect cull_rect = SkRect::MakeWH(100, 100);
   SkRect clip_rect = SkRect::MakeLTRB(10, 10, 20, 20);
   SkRect clip_bounds = SkRect::MakeLTRB(10, 10, 20, 20);
   DisplayListBuilder builder(cull_rect);
-  builder.clipRect(clip_rect, ClipOp::kIntersect, false);
+  builder.ClipRect(clip_rect, ClipOp::kIntersect, false);
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds);
 }
 
-TEST(DisplayList, BuilderClipBoundsAfterClipRRect) {
+TEST_F(DisplayListTest, BuilderClipBoundsAfterClipRRect) {
   SkRect cull_rect = SkRect::MakeWH(100, 100);
   SkRect clip_rect = SkRect::MakeLTRB(10, 10, 20, 20);
   SkRRect clip_rrect = SkRRect::MakeRectXY(clip_rect, 2, 2);
   SkRect clip_bounds = SkRect::MakeLTRB(10, 10, 20, 20);
   DisplayListBuilder builder(cull_rect);
-  builder.clipRRect(clip_rrect, ClipOp::kIntersect, false);
+  builder.ClipRRect(clip_rrect, ClipOp::kIntersect, false);
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds);
 }
 
-TEST(DisplayList, BuilderClipBoundsAfterClipPath) {
+TEST_F(DisplayListTest, BuilderClipBoundsAfterClipPath) {
   SkRect cull_rect = SkRect::MakeWH(100, 100);
   SkPath clip_path = SkPath().addRect(10, 10, 15, 15).addRect(15, 15, 20, 20);
   SkRect clip_bounds = SkRect::MakeLTRB(10, 10, 20, 20);
   DisplayListBuilder builder(cull_rect);
-  builder.clipPath(clip_path, ClipOp::kIntersect, false);
+  builder.ClipPath(clip_path, ClipOp::kIntersect, false);
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds);
 }
 
-TEST(DisplayList, BuilderInitialClipBoundsNonZero) {
+TEST_F(DisplayListTest, BuilderInitialClipBoundsNonZero) {
   SkRect cull_rect = SkRect::MakeLTRB(10, 10, 100, 100);
   SkRect clip_bounds = SkRect::MakeLTRB(10, 10, 100, 100);
   DisplayListBuilder builder(cull_rect);
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds);
 }
 
-TEST(DisplayList, SingleOpSizes) {
+TEST_F(DisplayListTest, SingleOpSizes) {
   for (auto& group : allGroups) {
     for (size_t i = 0; i < group.variants.size(); i++) {
       auto& invocation = group.variants[i];
-      sk_sp<DisplayList> dl = invocation.Build();
+      sk_sp<DisplayList> dl = Build(invocation);
       auto desc = group.op_name + "(variant " + std::to_string(i + 1) + ")";
       ASSERT_EQ(dl->op_count(false), invocation.op_count()) << desc;
       ASSERT_EQ(dl->bytes(false), invocation.byte_count()) << desc;
@@ -114,11 +174,11 @@
   }
 }
 
-TEST(DisplayList, SingleOpDisplayListsNotEqualEmpty) {
+TEST_F(DisplayListTest, SingleOpDisplayListsNotEqualEmpty) {
   sk_sp<DisplayList> empty = DisplayListBuilder().Build();
   for (auto& group : allGroups) {
     for (size_t i = 0; i < group.variants.size(); i++) {
-      sk_sp<DisplayList> dl = group.variants[i].Build();
+      sk_sp<DisplayList> dl = Build(group.variants[i]);
       auto desc =
           group.op_name + "(variant " + std::to_string(i + 1) + " != empty)";
       if (group.variants[i].is_empty()) {
@@ -132,15 +192,16 @@
   }
 }
 
-TEST(DisplayList, SingleOpDisplayListsRecapturedAreEqual) {
+TEST_F(DisplayListTest, SingleOpDisplayListsRecapturedAreEqual) {
   for (auto& group : allGroups) {
     for (size_t i = 0; i < group.variants.size(); i++) {
-      sk_sp<DisplayList> dl = group.variants[i].Build();
+      sk_sp<DisplayList> dl = Build(group.variants[i]);
       // Verify recapturing the replay of the display list is Equals()
       // when dispatching directly from the DL to another builder
-      DisplayListBuilder builder;
-      dl->Dispatch(builder.asDispatcher());
-      sk_sp<DisplayList> copy = builder.Build();
+      DisplayListBuilder copy_builder;
+      DlOpReceiver& r = ToReceiver(copy_builder);
+      dl->Dispatch(r);
+      sk_sp<DisplayList> copy = copy_builder.Build();
       auto desc =
           group.op_name + "(variant " + std::to_string(i + 1) + " == copy)";
       ASSERT_EQ(copy->op_count(false), dl->op_count(false)) << desc;
@@ -154,13 +215,13 @@
   }
 }
 
-TEST(DisplayList, SingleOpDisplayListsCompareToEachOther) {
+TEST_F(DisplayListTest, SingleOpDisplayListsCompareToEachOther) {
   for (auto& group : allGroups) {
     std::vector<sk_sp<DisplayList>> lists_a;
     std::vector<sk_sp<DisplayList>> lists_b;
     for (size_t i = 0; i < group.variants.size(); i++) {
-      lists_a.push_back(group.variants[i].Build());
-      lists_b.push_back(group.variants[i].Build());
+      lists_a.push_back(Build(group.variants[i]));
+      lists_b.push_back(Build(group.variants[i]));
     }
 
     for (size_t i = 0; i < lists_a.size(); i++) {
@@ -190,13 +251,13 @@
   }
 }
 
-TEST(DisplayList, SingleOpDisplayListsAreEqualWhetherOrNotToPrepareRtree) {
+TEST_F(DisplayListTest, SingleOpDisplayListsAreEqualWithOrWithoutRtree) {
   for (auto& group : allGroups) {
     for (size_t i = 0; i < group.variants.size(); i++) {
       DisplayListBuilder builder1(/*prepare_rtree=*/false);
       DisplayListBuilder builder2(/*prepare_rtree=*/true);
-      group.variants[i].invoker(builder1);
-      group.variants[i].invoker(builder2);
+      group.variants[i].invoker(ToReceiver(builder1));
+      group.variants[i].invoker(ToReceiver(builder2));
       sk_sp<DisplayList> dl1 = builder1.Build();
       sk_sp<DisplayList> dl2 = builder2.Build();
 
@@ -214,13 +275,14 @@
   }
 }
 
-TEST(DisplayList, FullRotationsAreNop) {
+TEST_F(DisplayListTest, FullRotationsAreNop) {
   DisplayListBuilder builder;
-  builder.rotate(0);
-  builder.rotate(360);
-  builder.rotate(720);
-  builder.rotate(1080);
-  builder.rotate(1440);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.rotate(0);
+  receiver.rotate(360);
+  receiver.rotate(720);
+  receiver.rotate(1080);
+  receiver.rotate(1440);
   sk_sp<DisplayList> dl = builder.Build();
   ASSERT_EQ(dl->bytes(false), sizeof(DisplayList));
   ASSERT_EQ(dl->bytes(true), sizeof(DisplayList));
@@ -228,9 +290,10 @@
   ASSERT_EQ(dl->op_count(true), 0u);
 }
 
-TEST(DisplayList, AllBlendModeNops) {
+TEST_F(DisplayListTest, AllBlendModeNops) {
   DisplayListBuilder builder;
-  builder.setBlendMode(DlBlendMode::kSrcOver);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setBlendMode(DlBlendMode::kSrcOver);
   sk_sp<DisplayList> dl = builder.Build();
   ASSERT_EQ(dl->bytes(false), sizeof(DisplayList));
   ASSERT_EQ(dl->bytes(true), sizeof(DisplayList));
@@ -238,39 +301,7 @@
   ASSERT_EQ(dl->op_count(true), 0u);
 }
 
-static sk_sp<DisplayList> Build(size_t g_index, size_t v_index) {
-  DisplayListBuilder builder;
-  unsigned int op_count = 0;
-  size_t byte_count = 0;
-  for (size_t i = 0; i < allGroups.size(); i++) {
-    DisplayListInvocationGroup& group = allGroups[i];
-    size_t j = (i == g_index ? v_index : 0);
-    if (j >= group.variants.size()) {
-      continue;
-    }
-    DisplayListInvocation& invocation = group.variants[j];
-    op_count += invocation.op_count();
-    byte_count += invocation.raw_byte_count();
-    invocation.invoker(builder);
-  }
-  sk_sp<DisplayList> dl = builder.Build();
-  std::string name;
-  if (g_index >= allGroups.size()) {
-    name = "Default";
-  } else {
-    name = allGroups[g_index].op_name;
-    if (v_index >= allGroups[g_index].variants.size()) {
-      name += " skipped";
-    } else {
-      name += " variant " + std::to_string(v_index + 1);
-    }
-  }
-  EXPECT_EQ(dl->op_count(false), op_count) << name;
-  EXPECT_EQ(dl->bytes(false), byte_count + sizeof(DisplayList)) << name;
-  return dl;
-}
-
-TEST(DisplayList, DisplayListsWithVaryingOpComparisons) {
+TEST_F(DisplayListTest, DisplayListsWithVaryingOpComparisons) {
   sk_sp<DisplayList> default_dl = Build(allGroups.size(), 0);
   ASSERT_TRUE(default_dl->Equals(*default_dl)) << "Default == itself";
   for (size_t gi = 0; gi < allGroups.size(); gi++) {
@@ -303,7 +334,7 @@
   }
 }
 
-TEST(DisplayList, DisplayListSaveLayerBoundsWithAlphaFilter) {
+TEST_F(DisplayListTest, DisplayListSaveLayerBoundsWithAlphaFilter) {
   SkRect build_bounds = SkRect::MakeLTRB(-100, -100, 200, 200);
   SkRect save_bounds = SkRect::MakeWH(100, 100);
   SkRect rect = SkRect::MakeLTRB(30, 30, 70, 70);
@@ -325,13 +356,16 @@
   };
   // clang-format on
   DlMatrixColorFilter alpha_color_filter(alpha_matrix);
+  sk_sp<SkColorFilter> sk_alpha_color_filter =
+      SkColorFilters::Matrix(alpha_matrix);
 
   {
     // No tricky stuff, just verifying drawing a rect produces rect bounds
     DisplayListBuilder builder(build_bounds);
-    builder.saveLayer(&save_bounds, true);
-    builder.drawRect(rect);
-    builder.restore();
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.saveLayer(&save_bounds, SaveLayerOptions::kWithAttributes);
+    receiver.drawRect(rect);
+    receiver.restore();
     sk_sp<DisplayList> display_list = builder.Build();
     ASSERT_EQ(display_list->bounds(), rect);
   }
@@ -339,11 +373,12 @@
   {
     // Now checking that a normal color filter still produces rect bounds
     DisplayListBuilder builder(build_bounds);
-    builder.setColorFilter(&base_color_filter);
-    builder.saveLayer(&save_bounds, true);
-    builder.setColorFilter(nullptr);
-    builder.drawRect(rect);
-    builder.restore();
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.setColorFilter(&base_color_filter);
+    receiver.saveLayer(&save_bounds, SaveLayerOptions::kWithAttributes);
+    receiver.setColorFilter(nullptr);
+    receiver.drawRect(rect);
+    receiver.restore();
     sk_sp<DisplayList> display_list = builder.Build();
     ASSERT_EQ(display_list->bounds(), rect);
   }
@@ -356,7 +391,7 @@
     SkRTreeFactory rtree_factory;
     SkCanvas* canvas = recorder.beginRecording(build_bounds, &rtree_factory);
     SkPaint p1;
-    p1.setColorFilter(alpha_color_filter.skia_object());
+    p1.setColorFilter(sk_alpha_color_filter);
     canvas->saveLayer(save_bounds, &p1);
     SkPaint p2;
     canvas->drawRect(rect, p2);
@@ -371,11 +406,12 @@
     // cull rect of the DisplayListBuilder when it encounters a
     // save layer that modifies an unbounded region
     DisplayListBuilder builder(build_bounds);
-    builder.setColorFilter(&alpha_color_filter);
-    builder.saveLayer(&save_bounds, true);
-    builder.setColorFilter(nullptr);
-    builder.drawRect(rect);
-    builder.restore();
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.setColorFilter(&alpha_color_filter);
+    receiver.saveLayer(&save_bounds, SaveLayerOptions::kWithAttributes);
+    receiver.setColorFilter(nullptr);
+    receiver.drawRect(rect);
+    receiver.restore();
     sk_sp<DisplayList> display_list = builder.Build();
     ASSERT_EQ(display_list->bounds(), build_bounds);
   }
@@ -384,11 +420,12 @@
     // Verifying that the save layer bounds are not relevant
     // to the behavior in the previous example
     DisplayListBuilder builder(build_bounds);
-    builder.setColorFilter(&alpha_color_filter);
-    builder.saveLayer(nullptr, true);
-    builder.setColorFilter(nullptr);
-    builder.drawRect(rect);
-    builder.restore();
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.setColorFilter(&alpha_color_filter);
+    receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+    receiver.setColorFilter(nullptr);
+    receiver.drawRect(rect);
+    receiver.restore();
     sk_sp<DisplayList> display_list = builder.Build();
     ASSERT_EQ(display_list->bounds(), build_bounds);
   }
@@ -397,12 +434,13 @@
     // Making sure hiding a ColorFilter as an ImageFilter will
     // generate the same behavior as setting it as a ColorFilter
     DisplayListBuilder builder(build_bounds);
+    DlOpReceiver& receiver = ToReceiver(builder);
     DlColorFilterImageFilter color_filter_image_filter(base_color_filter);
-    builder.setImageFilter(&color_filter_image_filter);
-    builder.saveLayer(&save_bounds, true);
-    builder.setImageFilter(nullptr);
-    builder.drawRect(rect);
-    builder.restore();
+    receiver.setImageFilter(&color_filter_image_filter);
+    receiver.saveLayer(&save_bounds, SaveLayerOptions::kWithAttributes);
+    receiver.setImageFilter(nullptr);
+    receiver.drawRect(rect);
+    receiver.restore();
     sk_sp<DisplayList> display_list = builder.Build();
     ASSERT_EQ(display_list->bounds(), rect);
   }
@@ -411,12 +449,13 @@
     // Making sure hiding a problematic ColorFilter as an ImageFilter
     // will generate the same behavior as setting it as a ColorFilter
     DisplayListBuilder builder(build_bounds);
+    DlOpReceiver& receiver = ToReceiver(builder);
     DlColorFilterImageFilter color_filter_image_filter(alpha_color_filter);
-    builder.setImageFilter(&color_filter_image_filter);
-    builder.saveLayer(&save_bounds, true);
-    builder.setImageFilter(nullptr);
-    builder.drawRect(rect);
-    builder.restore();
+    receiver.setImageFilter(&color_filter_image_filter);
+    receiver.saveLayer(&save_bounds, SaveLayerOptions::kWithAttributes);
+    receiver.setImageFilter(nullptr);
+    receiver.drawRect(rect);
+    receiver.restore();
     sk_sp<DisplayList> display_list = builder.Build();
     ASSERT_EQ(display_list->bounds(), build_bounds);
   }
@@ -424,12 +463,13 @@
   {
     // Same as above (ImageFilter hiding ColorFilter) with no save bounds
     DisplayListBuilder builder(build_bounds);
+    DlOpReceiver& receiver = ToReceiver(builder);
     DlColorFilterImageFilter color_filter_image_filter(alpha_color_filter);
-    builder.setImageFilter(&color_filter_image_filter);
-    builder.saveLayer(nullptr, true);
-    builder.setImageFilter(nullptr);
-    builder.drawRect(rect);
-    builder.restore();
+    receiver.setImageFilter(&color_filter_image_filter);
+    receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+    receiver.setImageFilter(nullptr);
+    receiver.drawRect(rect);
+    receiver.restore();
     sk_sp<DisplayList> display_list = builder.Build();
     ASSERT_EQ(display_list->bounds(), build_bounds);
   }
@@ -437,11 +477,12 @@
   {
     // Testing behavior with an unboundable blend mode
     DisplayListBuilder builder(build_bounds);
-    builder.setBlendMode(DlBlendMode::kClear);
-    builder.saveLayer(&save_bounds, true);
-    builder.setBlendMode(DlBlendMode::kSrcOver);
-    builder.drawRect(rect);
-    builder.restore();
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.setBlendMode(DlBlendMode::kClear);
+    receiver.saveLayer(&save_bounds, SaveLayerOptions::kWithAttributes);
+    receiver.setBlendMode(DlBlendMode::kSrcOver);
+    receiver.drawRect(rect);
+    receiver.restore();
     sk_sp<DisplayList> display_list = builder.Build();
     ASSERT_EQ(display_list->bounds(), build_bounds);
   }
@@ -449,17 +490,18 @@
   {
     // Same as previous with no save bounds
     DisplayListBuilder builder(build_bounds);
-    builder.setBlendMode(DlBlendMode::kClear);
-    builder.saveLayer(nullptr, true);
-    builder.setBlendMode(DlBlendMode::kSrcOver);
-    builder.drawRect(rect);
-    builder.restore();
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.setBlendMode(DlBlendMode::kClear);
+    receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+    receiver.setBlendMode(DlBlendMode::kSrcOver);
+    receiver.drawRect(rect);
+    receiver.restore();
     sk_sp<DisplayList> display_list = builder.Build();
     ASSERT_EQ(display_list->bounds(), build_bounds);
   }
 }
 
-TEST(DisplayList, NestedOpCountMetricsSameAsSkPicture) {
+TEST_F(DisplayListTest, NestedOpCountMetricsSameAsSkPicture) {
   SkPictureRecorder recorder;
   recorder.beginRecording(SkRect::MakeWH(150, 100));
   SkCanvas* canvas = recorder.getRecordingCanvas();
@@ -480,14 +522,16 @@
   ASSERT_EQ(picture->approximateOpCount(true), 36);
 
   DisplayListBuilder builder(SkRect::MakeWH(150, 100));
+  DlOpReceiver& receiver = ToReceiver(builder);
   for (int y = 10; y <= 60; y += 10) {
     for (int x = 10; x <= 60; x += 10) {
-      builder.setColor(((x + y) % 20) == 10 ? SK_ColorRED : SK_ColorBLUE);
-      builder.drawRect(SkRect::MakeXYWH(x, y, 80, 80));
+      receiver.setColor(((x + y) % 20) == 10 ? SK_ColorRED : SK_ColorBLUE);
+      receiver.drawRect(SkRect::MakeXYWH(x, y, 80, 80));
     }
   }
   DisplayListBuilder outer_builder(SkRect::MakeWH(150, 100));
-  outer_builder.drawDisplayList(builder.Build());
+  DlOpReceiver& outer_receiver = ToReceiver(outer_builder);
+  outer_receiver.drawDisplayList(builder.Build());
 
   auto display_list = outer_builder.Build();
   ASSERT_EQ(display_list->op_count(), 1u);
@@ -499,7 +543,7 @@
             static_cast<int>(display_list->op_count(true)));
 }
 
-TEST(DisplayList, DisplayListFullPerspectiveTransformHandling) {
+TEST_F(DisplayListTest, DisplayListFullPerspectiveTransformHandling) {
   // SkM44 constructor takes row-major order
   SkM44 sk_matrix = SkM44(
       // clang-format off
@@ -512,8 +556,9 @@
 
   {  // First test ==
     DisplayListBuilder builder;
-    // builder.transformFullPerspective takes row-major order
-    builder.transformFullPerspective(
+    DlOpReceiver& receiver = ToReceiver(builder);
+    // receiver.transformFullPerspective takes row-major order
+    receiver.transformFullPerspective(
         // clang-format off
          1,  2,  3,  4,
          5,  6,  7,  8,
@@ -524,14 +569,18 @@
     sk_sp<DisplayList> display_list = builder.Build();
     sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(10, 10);
     SkCanvas* canvas = surface->getCanvas();
-    display_list->RenderTo(canvas);
+    // We can't use DlSkCanvas.DrawDisplayList as that method protects
+    // the canvas against mutations from the display list being drawn.
+    auto dispatcher = DlSkCanvasDispatcher(surface->getCanvas());
+    display_list->Dispatch(dispatcher);
     SkM44 dl_matrix = canvas->getLocalToDevice();
     ASSERT_EQ(sk_matrix, dl_matrix);
   }
   {  // Next test !=
     DisplayListBuilder builder;
-    // builder.transformFullPerspective takes row-major order
-    builder.transformFullPerspective(
+    DlOpReceiver& receiver = ToReceiver(builder);
+    // receiver.transformFullPerspective takes row-major order
+    receiver.transformFullPerspective(
         // clang-format off
          1,  5,  9, 13,
          2,  6,  7, 11,
@@ -542,33 +591,41 @@
     sk_sp<DisplayList> display_list = builder.Build();
     sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(10, 10);
     SkCanvas* canvas = surface->getCanvas();
-    display_list->RenderTo(canvas);
+    // We can't use DlSkCanvas.DrawDisplayList as that method protects
+    // the canvas against mutations from the display list being drawn.
+    auto dispatcher = DlSkCanvasDispatcher(surface->getCanvas());
+    display_list->Dispatch(dispatcher);
     SkM44 dl_matrix = canvas->getLocalToDevice();
     ASSERT_NE(sk_matrix, dl_matrix);
   }
 }
 
-TEST(DisplayList, DisplayListTransformResetHandling) {
+TEST_F(DisplayListTest, DisplayListTransformResetHandling) {
   DisplayListBuilder builder;
-  builder.scale(20.0, 20.0);
-  builder.transformReset();
-  auto list = builder.Build();
-  ASSERT_NE(list, nullptr);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.scale(20.0, 20.0);
+  receiver.transformReset();
+  auto display_list = builder.Build();
+  ASSERT_NE(display_list, nullptr);
   sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(10, 10);
   SkCanvas* canvas = surface->getCanvas();
-  list->RenderTo(canvas);
+  // We can't use DlSkCanvas.DrawDisplayList as that method protects
+  // the canvas against mutations from the display list being drawn.
+  auto dispatcher = DlSkCanvasDispatcher(surface->getCanvas());
+  display_list->Dispatch(dispatcher);
   ASSERT_TRUE(canvas->getTotalMatrix().isIdentity());
 }
 
-TEST(DisplayList, SingleOpsMightSupportGroupOpacityWithOrWithoutBlendMode) {
+TEST_F(DisplayListTest, SingleOpsMightSupportGroupOpacityBlendMode) {
   auto run_tests = [](const std::string& name,
-                      void build(DisplayListBuilder & builder),
-                      bool expect_for_op, bool expect_with_kSrc) {
+                      void build(DlOpReceiver & receiver), bool expect_for_op,
+                      bool expect_with_kSrc) {
     {
       // First test is the draw op, by itself
       // (usually supports group opacity)
       DisplayListBuilder builder;
-      build(builder);
+      DlOpReceiver& receiver = ToReceiver(builder);
+      build(receiver);
       auto display_list = builder.Build();
       EXPECT_EQ(display_list->can_apply_group_opacity(), expect_for_op)
           << "{" << std::endl
@@ -579,12 +636,13 @@
       // Second test i the draw op with kSrc,
       // (usually fails group opacity)
       DisplayListBuilder builder;
-      builder.setBlendMode(DlBlendMode::kSrc);
-      build(builder);
+      DlOpReceiver& receiver = ToReceiver(builder);
+      receiver.setBlendMode(DlBlendMode::kSrc);
+      build(receiver);
       auto display_list = builder.Build();
       EXPECT_EQ(display_list->can_apply_group_opacity(), expect_with_kSrc)
           << "{" << std::endl
-          << "  builder.setBlendMode(kSrc);" << std::endl
+          << "  receiver.setBlendMode(kSrc);" << std::endl
           << "  " << name << std::endl
           << "}";
     }
@@ -592,150 +650,163 @@
 
 #define RUN_TESTS(body) \
   run_tests(            \
-      #body, [](DisplayListBuilder& builder) { body }, true, false)
+      #body, [](DlOpReceiver& receiver) { body }, true, false)
 #define RUN_TESTS2(body, expect) \
   run_tests(                     \
-      #body, [](DisplayListBuilder& builder) { body }, expect, expect)
+      #body, [](DlOpReceiver& receiver) { body }, expect, expect)
 
-  RUN_TESTS(builder.drawPaint(););
-  RUN_TESTS2(builder.drawColor(SK_ColorRED, DlBlendMode::kSrcOver);, true);
-  RUN_TESTS2(builder.drawColor(SK_ColorRED, DlBlendMode::kSrc);, false);
-  RUN_TESTS(builder.drawLine({0, 0}, {10, 10}););
-  RUN_TESTS(builder.drawRect({0, 0, 10, 10}););
-  RUN_TESTS(builder.drawOval({0, 0, 10, 10}););
-  RUN_TESTS(builder.drawCircle({10, 10}, 5););
-  RUN_TESTS(builder.drawRRect(SkRRect::MakeRectXY({0, 0, 10, 10}, 2, 2)););
-  RUN_TESTS(builder.drawDRRect(SkRRect::MakeRectXY({0, 0, 10, 10}, 2, 2),
-                               SkRRect::MakeRectXY({2, 2, 8, 8}, 2, 2)););
-  RUN_TESTS(builder.drawPath(
+  RUN_TESTS(receiver.drawPaint(););
+  RUN_TESTS2(receiver.drawColor(SK_ColorRED, DlBlendMode::kSrcOver);, true);
+  RUN_TESTS2(receiver.drawColor(SK_ColorRED, DlBlendMode::kSrc);, false);
+  RUN_TESTS(receiver.drawLine({0, 0}, {10, 10}););
+  RUN_TESTS(receiver.drawRect({0, 0, 10, 10}););
+  RUN_TESTS(receiver.drawOval({0, 0, 10, 10}););
+  RUN_TESTS(receiver.drawCircle({10, 10}, 5););
+  RUN_TESTS(receiver.drawRRect(SkRRect::MakeRectXY({0, 0, 10, 10}, 2, 2)););
+  RUN_TESTS(receiver.drawDRRect(SkRRect::MakeRectXY({0, 0, 10, 10}, 2, 2),
+                                SkRRect::MakeRectXY({2, 2, 8, 8}, 2, 2)););
+  RUN_TESTS(receiver.drawPath(
       SkPath().addOval({0, 0, 10, 10}).addOval({5, 5, 15, 15})););
-  RUN_TESTS(builder.drawArc({0, 0, 10, 10}, 0, math::kPi, true););
-  RUN_TESTS2(builder.drawPoints(PointMode::kPoints, TestPointCount, TestPoints);
+  RUN_TESTS(receiver.drawArc({0, 0, 10, 10}, 0, math::kPi, true););
+  RUN_TESTS2(
+      receiver.drawPoints(PointMode::kPoints, TestPointCount, TestPoints);
+      , false);
+  RUN_TESTS2(receiver.drawVertices(TestVertices1.get(), DlBlendMode::kSrc);
              , false);
-  RUN_TESTS2(builder.drawVertices(TestVertices1, DlBlendMode::kSrc);, false);
-  RUN_TESTS(builder.drawImage(TestImage1, {0, 0}, kLinearSampling, true););
-  RUN_TESTS2(builder.drawImage(TestImage1, {0, 0}, kLinearSampling, false);
+  RUN_TESTS(receiver.drawImage(TestImage1, {0, 0}, kLinearSampling, true););
+  RUN_TESTS2(receiver.drawImage(TestImage1, {0, 0}, kLinearSampling, false);
              , true);
-  RUN_TESTS(builder.drawImageRect(TestImage1, {10, 10, 20, 20}, {0, 0, 10, 10},
-                                  kNearestSampling, true););
-  RUN_TESTS2(builder.drawImageRect(TestImage1, {10, 10, 20, 20}, {0, 0, 10, 10},
-                                   kNearestSampling, false);
+  RUN_TESTS(receiver.drawImageRect(TestImage1, {10, 10, 20, 20}, {0, 0, 10, 10},
+                                   kNearestSampling, true,
+                                   DlCanvas::SrcRectConstraint::kFast););
+  RUN_TESTS2(receiver.drawImageRect(TestImage1, {10, 10, 20, 20},
+                                    {0, 0, 10, 10}, kNearestSampling, false,
+                                    DlCanvas::SrcRectConstraint::kFast);
              , true);
-  RUN_TESTS(builder.drawImageNine(TestImage2, {20, 20, 30, 30}, {0, 0, 20, 20},
-                                  DlFilterMode::kLinear, true););
-  RUN_TESTS2(builder.drawImageNine(TestImage2, {20, 20, 30, 30}, {0, 0, 20, 20},
-                                   DlFilterMode::kLinear, false);
-             , true);
+  RUN_TESTS(receiver.drawImageNine(TestImage2, {20, 20, 30, 30}, {0, 0, 20, 20},
+                                   DlFilterMode::kLinear, true););
+  RUN_TESTS2(
+      receiver.drawImageNine(TestImage2, {20, 20, 30, 30}, {0, 0, 20, 20},
+                             DlFilterMode::kLinear, false);
+      , true);
   static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
   static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
   RUN_TESTS2(
-      builder.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
-                        DlBlendMode::kSrcIn, kNearestSampling, nullptr, true);
+      receiver.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
+                         DlBlendMode::kSrcIn, kNearestSampling, nullptr, true);
       , false);
   RUN_TESTS2(
-      builder.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
-                        DlBlendMode::kSrcIn, kNearestSampling, nullptr, false);
+      receiver.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
+                         DlBlendMode::kSrcIn, kNearestSampling, nullptr, false);
       , false);
   EXPECT_TRUE(TestDisplayList1->can_apply_group_opacity());
-  RUN_TESTS2(builder.drawDisplayList(TestDisplayList1);, true);
+  RUN_TESTS2(receiver.drawDisplayList(TestDisplayList1);, true);
   {
     static DisplayListBuilder builder;
-    builder.drawRect({0, 0, 10, 10});
-    builder.drawRect({5, 5, 15, 15});
+    builder.DrawRect({0, 0, 10, 10}, DlPaint());
+    builder.DrawRect({5, 5, 15, 15}, DlPaint());
     static auto display_list = builder.Build();
-    RUN_TESTS2(builder.drawDisplayList(display_list);, false);
+    RUN_TESTS2(receiver.drawDisplayList(display_list);, false);
   }
-  RUN_TESTS2(builder.drawTextBlob(TestBlob1, 0, 0);, false);
-  RUN_TESTS2(builder.drawShadow(kTestPath1, SK_ColorBLACK, 1.0, false, 1.0);
+  RUN_TESTS2(receiver.drawTextBlob(TestBlob1, 0, 0);, false);
+  RUN_TESTS2(receiver.drawShadow(kTestPath1, SK_ColorBLACK, 1.0, false, 1.0);
              , false);
 
 #undef RUN_TESTS2
 #undef RUN_TESTS
 }
 
-TEST(DisplayList, OverlappingOpsDoNotSupportGroupOpacity) {
+TEST_F(DisplayListTest, OverlappingOpsDoNotSupportGroupOpacity) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   for (int i = 0; i < 10; i++) {
-    builder.drawRect(SkRect::MakeXYWH(i * 10, 0, 30, 30));
+    receiver.drawRect(SkRect::MakeXYWH(i * 10, 0, 30, 30));
   }
   auto display_list = builder.Build();
   EXPECT_FALSE(display_list->can_apply_group_opacity());
 }
 
-TEST(DisplayList, SaveLayerFalseSupportsGroupOpacityWithOverlappingChidren) {
+TEST_F(DisplayListTest, SaveLayerFalseSupportsGroupOpacityOverlappingChidren) {
   DisplayListBuilder builder;
-  builder.saveLayer(nullptr, false);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kNoAttributes);
   for (int i = 0; i < 10; i++) {
-    builder.drawRect(SkRect::MakeXYWH(i * 10, 0, 30, 30));
+    receiver.drawRect(SkRect::MakeXYWH(i * 10, 0, 30, 30));
   }
-  builder.restore();
+  receiver.restore();
   auto display_list = builder.Build();
   EXPECT_TRUE(display_list->can_apply_group_opacity());
 }
 
-TEST(DisplayList, SaveLayerTrueSupportsGroupOpacityWithOverlappingChidren) {
+TEST_F(DisplayListTest, SaveLayerTrueSupportsGroupOpacityOverlappingChidren) {
   DisplayListBuilder builder;
-  builder.saveLayer(nullptr, true);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
   for (int i = 0; i < 10; i++) {
-    builder.drawRect(SkRect::MakeXYWH(i * 10, 0, 30, 30));
+    receiver.drawRect(SkRect::MakeXYWH(i * 10, 0, 30, 30));
   }
-  builder.restore();
+  receiver.restore();
   auto display_list = builder.Build();
   EXPECT_TRUE(display_list->can_apply_group_opacity());
 }
 
-TEST(DisplayList, SaveLayerFalseWithSrcBlendSupportsGroupOpacity) {
+TEST_F(DisplayListTest, SaveLayerFalseWithSrcBlendSupportsGroupOpacity) {
   DisplayListBuilder builder;
-  builder.setBlendMode(DlBlendMode::kSrc);
-  builder.saveLayer(nullptr, false);
-  builder.drawRect({0, 0, 10, 10});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setBlendMode(DlBlendMode::kSrc);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kNoAttributes);
+  receiver.drawRect({0, 0, 10, 10});
+  receiver.restore();
   auto display_list = builder.Build();
   EXPECT_TRUE(display_list->can_apply_group_opacity());
 }
 
-TEST(DisplayList, SaveLayerTrueWithSrcBlendDoesNotSupportGroupOpacity) {
+TEST_F(DisplayListTest, SaveLayerTrueWithSrcBlendDoesNotSupportGroupOpacity) {
   DisplayListBuilder builder;
-  builder.setBlendMode(DlBlendMode::kSrc);
-  builder.saveLayer(nullptr, true);
-  builder.drawRect({0, 0, 10, 10});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setBlendMode(DlBlendMode::kSrc);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.drawRect({0, 0, 10, 10});
+  receiver.restore();
   auto display_list = builder.Build();
   EXPECT_FALSE(display_list->can_apply_group_opacity());
 }
 
-TEST(DisplayList, SaveLayerFalseSupportsGroupOpacityWithChildSrcBlend) {
+TEST_F(DisplayListTest, SaveLayerFalseSupportsGroupOpacityWithChildSrcBlend) {
   DisplayListBuilder builder;
-  builder.saveLayer(nullptr, false);
-  builder.setBlendMode(DlBlendMode::kSrc);
-  builder.drawRect({0, 0, 10, 10});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kNoAttributes);
+  receiver.setBlendMode(DlBlendMode::kSrc);
+  receiver.drawRect({0, 0, 10, 10});
+  receiver.restore();
   auto display_list = builder.Build();
   EXPECT_TRUE(display_list->can_apply_group_opacity());
 }
 
-TEST(DisplayList, SaveLayerTrueSupportsGroupOpacityWithChildSrcBlend) {
+TEST_F(DisplayListTest, SaveLayerTrueSupportsGroupOpacityWithChildSrcBlend) {
   DisplayListBuilder builder;
-  builder.saveLayer(nullptr, true);
-  builder.setBlendMode(DlBlendMode::kSrc);
-  builder.drawRect({0, 0, 10, 10});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.setBlendMode(DlBlendMode::kSrc);
+  receiver.drawRect({0, 0, 10, 10});
+  receiver.restore();
   auto display_list = builder.Build();
   EXPECT_TRUE(display_list->can_apply_group_opacity());
 }
 
-TEST(DisplayList, SaveLayerBoundsSnapshotsImageFilter) {
+TEST_F(DisplayListTest, SaveLayerBoundsSnapshotsImageFilter) {
   DisplayListBuilder builder;
-  builder.saveLayer(nullptr, true);
-  builder.drawRect({50, 50, 100, 100});
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.drawRect({50, 50, 100, 100});
   // This image filter should be ignored since it was not set before saveLayer
-  builder.setImageFilter(&kTestBlurImageFilter1);
-  builder.restore();
+  receiver.setImageFilter(&kTestBlurImageFilter1);
+  receiver.restore();
   SkRect bounds = builder.Build()->bounds();
   EXPECT_EQ(bounds, SkRect::MakeLTRB(50, 50, 100, 100));
 }
 
-class SaveLayerOptionsExpector : public virtual Dispatcher,
+class SaveLayerOptionsExpector : public virtual DlOpReceiver,
                                  public IgnoreAttributeDispatchHelper,
                                  public IgnoreClipDispatchHelper,
                                  public IgnoreTransformDispatchHelper,
@@ -762,51 +833,54 @@
   int save_layer_count_ = 0;
 };
 
-TEST(DisplayList, SaveLayerOneSimpleOpSupportsOpacityOptimization) {
+TEST_F(DisplayListTest, SaveLayerOneSimpleOpInheritsOpacity) {
   SaveLayerOptions expected =
       SaveLayerOptions::kWithAttributes.with_can_distribute_opacity();
   SaveLayerOptionsExpector expector(expected);
 
   DisplayListBuilder builder;
-  builder.setColor(SkColorSetARGB(127, 255, 255, 255));
-  builder.saveLayer(nullptr, true);
-  builder.drawRect({10, 10, 20, 20});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setColor(SkColorSetARGB(127, 255, 255, 255));
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.restore();
 
   builder.Build()->Dispatch(expector);
   EXPECT_EQ(expector.save_layer_count(), 1);
 }
 
-TEST(DisplayList, SaveLayerNoAttributesSupportsOpacityOptimization) {
+TEST_F(DisplayListTest, SaveLayerNoAttributesInheritsOpacity) {
   SaveLayerOptions expected =
       SaveLayerOptions::kNoAttributes.with_can_distribute_opacity();
   SaveLayerOptionsExpector expector(expected);
 
   DisplayListBuilder builder;
-  builder.saveLayer(nullptr, false);
-  builder.drawRect({10, 10, 20, 20});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kNoAttributes);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.restore();
 
   builder.Build()->Dispatch(expector);
   EXPECT_EQ(expector.save_layer_count(), 1);
 }
 
-TEST(DisplayList, SaveLayerTwoOverlappingOpsPreventsOpacityOptimization) {
+TEST_F(DisplayListTest, SaveLayerTwoOverlappingOpsDoesNotInheritOpacity) {
   SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
   SaveLayerOptionsExpector expector(expected);
 
   DisplayListBuilder builder;
-  builder.setColor(SkColorSetARGB(127, 255, 255, 255));
-  builder.saveLayer(nullptr, true);
-  builder.drawRect({10, 10, 20, 20});
-  builder.drawRect({15, 15, 25, 25});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setColor(SkColorSetARGB(127, 255, 255, 255));
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.drawRect({15, 15, 25, 25});
+  receiver.restore();
 
   builder.Build()->Dispatch(expector);
   EXPECT_EQ(expector.save_layer_count(), 1);
 }
 
-TEST(DisplayList, NestedSaveLayersMightSupportOpacityOptimization) {
+TEST_F(DisplayListTest, NestedSaveLayersMightInheritOpacity) {
   SaveLayerOptions expected1 =
       SaveLayerOptions::kWithAttributes.with_can_distribute_opacity();
   SaveLayerOptions expected2 = SaveLayerOptions::kWithAttributes;
@@ -815,21 +889,22 @@
   SaveLayerOptionsExpector expector({expected1, expected2, expected3});
 
   DisplayListBuilder builder;
-  builder.setColor(SkColorSetARGB(127, 255, 255, 255));
-  builder.saveLayer(nullptr, true);
-  builder.saveLayer(nullptr, true);
-  builder.drawRect({10, 10, 20, 20});
-  builder.saveLayer(nullptr, true);
-  builder.drawRect({15, 15, 25, 25});
-  builder.restore();
-  builder.restore();
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setColor(SkColorSetARGB(127, 255, 255, 255));
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.drawRect({15, 15, 25, 25});
+  receiver.restore();
+  receiver.restore();
+  receiver.restore();
 
   builder.Build()->Dispatch(expector);
   EXPECT_EQ(expector.save_layer_count(), 3);
 }
 
-TEST(DisplayList, NestedSaveLayersCanBothSupportOpacityOptimization) {
+TEST_F(DisplayListTest, NestedSaveLayersCanBothSupportOpacityOptimization) {
   SaveLayerOptions expected1 =
       SaveLayerOptions::kWithAttributes.with_can_distribute_opacity();
   SaveLayerOptions expected2 =
@@ -837,112 +912,119 @@
   SaveLayerOptionsExpector expector({expected1, expected2});
 
   DisplayListBuilder builder;
-  builder.setColor(SkColorSetARGB(127, 255, 255, 255));
-  builder.saveLayer(nullptr, true);
-  builder.saveLayer(nullptr, false);
-  builder.drawRect({10, 10, 20, 20});
-  builder.restore();
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setColor(SkColorSetARGB(127, 255, 255, 255));
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kNoAttributes);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.restore();
+  receiver.restore();
 
   builder.Build()->Dispatch(expector);
   EXPECT_EQ(expector.save_layer_count(), 2);
 }
 
-TEST(DisplayList, SaveLayerImageFilterPreventsOpacityOptimization) {
+TEST_F(DisplayListTest, SaveLayerImageFilterDoesNotInheritOpacity) {
   SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
   SaveLayerOptionsExpector expector(expected);
 
   DisplayListBuilder builder;
-  builder.setColor(SkColorSetARGB(127, 255, 255, 255));
-  builder.setImageFilter(&kTestBlurImageFilter1);
-  builder.saveLayer(nullptr, true);
-  builder.setImageFilter(nullptr);
-  builder.drawRect({10, 10, 20, 20});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setColor(SkColorSetARGB(127, 255, 255, 255));
+  receiver.setImageFilter(&kTestBlurImageFilter1);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.setImageFilter(nullptr);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.restore();
 
   builder.Build()->Dispatch(expector);
   EXPECT_EQ(expector.save_layer_count(), 1);
 }
 
-TEST(DisplayList, SaveLayerColorFilterPreventsOpacityOptimization) {
+TEST_F(DisplayListTest, SaveLayerColorFilterDoesNotInheritOpacity) {
   SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
   SaveLayerOptionsExpector expector(expected);
 
   DisplayListBuilder builder;
-  builder.setColor(SkColorSetARGB(127, 255, 255, 255));
-  builder.setColorFilter(&kTestMatrixColorFilter1);
-  builder.saveLayer(nullptr, true);
-  builder.setColorFilter(nullptr);
-  builder.drawRect({10, 10, 20, 20});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setColor(SkColorSetARGB(127, 255, 255, 255));
+  receiver.setColorFilter(&kTestMatrixColorFilter1);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.setColorFilter(nullptr);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.restore();
 
   builder.Build()->Dispatch(expector);
   EXPECT_EQ(expector.save_layer_count(), 1);
 }
 
-TEST(DisplayList, SaveLayerSrcBlendPreventsOpacityOptimization) {
+TEST_F(DisplayListTest, SaveLayerSrcBlendDoesNotInheritOpacity) {
   SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
   SaveLayerOptionsExpector expector(expected);
 
   DisplayListBuilder builder;
-  builder.setColor(SkColorSetARGB(127, 255, 255, 255));
-  builder.setBlendMode(DlBlendMode::kSrc);
-  builder.saveLayer(nullptr, true);
-  builder.setBlendMode(DlBlendMode::kSrcOver);
-  builder.drawRect({10, 10, 20, 20});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setColor(SkColorSetARGB(127, 255, 255, 255));
+  receiver.setBlendMode(DlBlendMode::kSrc);
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.setBlendMode(DlBlendMode::kSrcOver);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.restore();
 
   builder.Build()->Dispatch(expector);
   EXPECT_EQ(expector.save_layer_count(), 1);
 }
 
-TEST(DisplayList, SaveLayerImageFilterOnChildSupportsOpacityOptimization) {
+TEST_F(DisplayListTest, SaveLayerImageFilterOnChildInheritsOpacity) {
   SaveLayerOptions expected =
       SaveLayerOptions::kWithAttributes.with_can_distribute_opacity();
   SaveLayerOptionsExpector expector(expected);
 
   DisplayListBuilder builder;
-  builder.setColor(SkColorSetARGB(127, 255, 255, 255));
-  builder.saveLayer(nullptr, true);
-  builder.setImageFilter(&kTestBlurImageFilter1);
-  builder.drawRect({10, 10, 20, 20});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setColor(SkColorSetARGB(127, 255, 255, 255));
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.setImageFilter(&kTestBlurImageFilter1);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.restore();
 
   builder.Build()->Dispatch(expector);
   EXPECT_EQ(expector.save_layer_count(), 1);
 }
 
-TEST(DisplayList, SaveLayerColorFilterOnChildPreventsOpacityOptimization) {
+TEST_F(DisplayListTest, SaveLayerColorFilterOnChildDoesNotInheritOpacity) {
   SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
   SaveLayerOptionsExpector expector(expected);
 
   DisplayListBuilder builder;
-  builder.setColor(SkColorSetARGB(127, 255, 255, 255));
-  builder.saveLayer(nullptr, true);
-  builder.setColorFilter(&kTestMatrixColorFilter1);
-  builder.drawRect({10, 10, 20, 20});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setColor(SkColorSetARGB(127, 255, 255, 255));
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.setColorFilter(&kTestMatrixColorFilter1);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.restore();
 
   builder.Build()->Dispatch(expector);
   EXPECT_EQ(expector.save_layer_count(), 1);
 }
 
-TEST(DisplayList, SaveLayerSrcBlendOnChildPreventsOpacityOptimization) {
+TEST_F(DisplayListTest, SaveLayerSrcBlendOnChildDoesNotInheritOpacity) {
   SaveLayerOptions expected = SaveLayerOptions::kWithAttributes;
   SaveLayerOptionsExpector expector(expected);
 
   DisplayListBuilder builder;
-  builder.setColor(SkColorSetARGB(127, 255, 255, 255));
-  builder.saveLayer(nullptr, true);
-  builder.setBlendMode(DlBlendMode::kSrc);
-  builder.drawRect({10, 10, 20, 20});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.setColor(SkColorSetARGB(127, 255, 255, 255));
+  receiver.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+  receiver.setBlendMode(DlBlendMode::kSrc);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.restore();
 
   builder.Build()->Dispatch(expector);
   EXPECT_EQ(expector.save_layer_count(), 1);
 }
 
-TEST(DisplayList, FlutterSvgIssue661BoundsWereEmpty) {
+TEST_F(DisplayListTest, FlutterSvgIssue661BoundsWereEmpty) {
   // See https://github.com/dnfield/flutter_svg/issues/661
 
   SkPath path1;
@@ -1013,42 +1095,40 @@
   path2.close();
 
   DisplayListBuilder builder;
+  DlPaint paint = DlPaint(DlColor::kWhite()).setAntiAlias(true);
   {
-    builder.save();
-    builder.clipRect({0, 0, 100, 100}, ClipOp::kIntersect, true);
+    builder.Save();
+    builder.ClipRect({0, 0, 100, 100}, ClipOp::kIntersect, true);
     {
-      builder.save();
-      builder.transform2DAffine(2.17391, 0, -2547.83,  //
+      builder.Save();
+      builder.Transform2DAffine(2.17391, 0, -2547.83,  //
                                 0, 2.04082, -500);
       {
-        builder.save();
-        builder.clipRect({1172, 245, 1218, 294}, ClipOp::kIntersect, true);
+        builder.Save();
+        builder.ClipRect({1172, 245, 1218, 294}, ClipOp::kIntersect, true);
         {
-          builder.saveLayer(nullptr, SaveLayerOptions::kWithAttributes,
-                            nullptr);
+          builder.SaveLayer(nullptr, nullptr, nullptr);
           {
-            builder.save();
-            builder.transform2DAffine(1.4375, 0, 1164.09,  //
+            builder.Save();
+            builder.Transform2DAffine(1.4375, 0, 1164.09,  //
                                       0, 1.53125, 236.548);
-            builder.setAntiAlias(1);
-            builder.setColor(0xffffffff);
-            builder.drawPath(path1);
-            builder.restore();
+            builder.DrawPath(path1, paint);
+            builder.Restore();
           }
           {
-            builder.save();
-            builder.transform2DAffine(1.4375, 0, 1164.09,  //
+            builder.Save();
+            builder.Transform2DAffine(1.4375, 0, 1164.09,  //
                                       0, 1.53125, 236.548);
-            builder.drawPath(path2);
-            builder.restore();
+            builder.DrawPath(path2, paint);
+            builder.Restore();
           }
-          builder.restore();
+          builder.Restore();
         }
-        builder.restore();
+        builder.Restore();
       }
-      builder.restore();
+      builder.Restore();
     }
-    builder.restore();
+    builder.Restore();
   }
   sk_sp<DisplayList> display_list = builder.Build();
   // Prior to the fix, the bounds were empty.
@@ -1070,16 +1150,17 @@
   EXPECT_EQ(display_list->bytes(), sizeof(DisplayList) + 352u);
 }
 
-TEST(DisplayList, TranslateAffectsCurrentTransform) {
+TEST_F(DisplayListTest, TranslateAffectsCurrentTransform) {
   DisplayListBuilder builder;
-  builder.translate(12.3, 14.5);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.translate(12.3, 14.5);
   SkMatrix matrix = SkMatrix::Translate(12.3, 14.5);
   SkM44 m44 = SkM44(matrix);
   SkM44 cur_m44 = builder.GetTransformFullPerspective();
   SkMatrix cur_matrix = builder.GetTransform();
   ASSERT_EQ(cur_m44, m44);
   ASSERT_EQ(cur_matrix, matrix);
-  builder.translate(10, 10);
+  receiver.translate(10, 10);
   // CurrentTransform has changed
   ASSERT_NE(builder.GetTransformFullPerspective(), m44);
   ASSERT_NE(builder.GetTransform(), cur_matrix);
@@ -1088,16 +1169,17 @@
   ASSERT_EQ(cur_matrix, matrix);
 }
 
-TEST(DisplayList, ScaleAffectsCurrentTransform) {
+TEST_F(DisplayListTest, ScaleAffectsCurrentTransform) {
   DisplayListBuilder builder;
-  builder.scale(12.3, 14.5);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.scale(12.3, 14.5);
   SkMatrix matrix = SkMatrix::Scale(12.3, 14.5);
   SkM44 m44 = SkM44(matrix);
   SkM44 cur_m44 = builder.GetTransformFullPerspective();
   SkMatrix cur_matrix = builder.GetTransform();
   ASSERT_EQ(cur_m44, m44);
   ASSERT_EQ(cur_matrix, matrix);
-  builder.translate(10, 10);
+  receiver.translate(10, 10);
   // CurrentTransform has changed
   ASSERT_NE(builder.GetTransformFullPerspective(), m44);
   ASSERT_NE(builder.GetTransform(), cur_matrix);
@@ -1106,16 +1188,17 @@
   ASSERT_EQ(cur_matrix, matrix);
 }
 
-TEST(DisplayList, RotateAffectsCurrentTransform) {
+TEST_F(DisplayListTest, RotateAffectsCurrentTransform) {
   DisplayListBuilder builder;
-  builder.rotate(12.3);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.rotate(12.3);
   SkMatrix matrix = SkMatrix::RotateDeg(12.3);
   SkM44 m44 = SkM44(matrix);
   SkM44 cur_m44 = builder.GetTransformFullPerspective();
   SkMatrix cur_matrix = builder.GetTransform();
   ASSERT_EQ(cur_m44, m44);
   ASSERT_EQ(cur_matrix, matrix);
-  builder.translate(10, 10);
+  receiver.translate(10, 10);
   // CurrentTransform has changed
   ASSERT_NE(builder.GetTransformFullPerspective(), m44);
   ASSERT_NE(builder.GetTransform(), cur_matrix);
@@ -1124,16 +1207,17 @@
   ASSERT_EQ(cur_matrix, matrix);
 }
 
-TEST(DisplayList, SkewAffectsCurrentTransform) {
+TEST_F(DisplayListTest, SkewAffectsCurrentTransform) {
   DisplayListBuilder builder;
-  builder.skew(12.3, 14.5);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.skew(12.3, 14.5);
   SkMatrix matrix = SkMatrix::Skew(12.3, 14.5);
   SkM44 m44 = SkM44(matrix);
   SkM44 cur_m44 = builder.GetTransformFullPerspective();
   SkMatrix cur_matrix = builder.GetTransform();
   ASSERT_EQ(cur_m44, m44);
   ASSERT_EQ(cur_matrix, matrix);
-  builder.translate(10, 10);
+  receiver.translate(10, 10);
   // CurrentTransform has changed
   ASSERT_NE(builder.GetTransformFullPerspective(), m44);
   ASSERT_NE(builder.GetTransform(), cur_matrix);
@@ -1142,10 +1226,11 @@
   ASSERT_EQ(cur_matrix, matrix);
 }
 
-TEST(DisplayList, TransformAffectsCurrentTransform) {
+TEST_F(DisplayListTest, TransformAffectsCurrentTransform) {
   DisplayListBuilder builder;
-  builder.transform2DAffine(3, 0, 12.3,  //
-                            1, 5, 14.5);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.transform2DAffine(3, 0, 12.3,  //
+                             1, 5, 14.5);
   SkMatrix matrix = SkMatrix::MakeAll(3, 0, 12.3,  //
                                       1, 5, 14.5,  //
                                       0, 0, 1);
@@ -1154,7 +1239,7 @@
   SkMatrix cur_matrix = builder.GetTransform();
   ASSERT_EQ(cur_m44, m44);
   ASSERT_EQ(cur_matrix, matrix);
-  builder.translate(10, 10);
+  receiver.translate(10, 10);
   // CurrentTransform has changed
   ASSERT_NE(builder.GetTransformFullPerspective(), m44);
   ASSERT_NE(builder.GetTransform(), cur_matrix);
@@ -1163,12 +1248,13 @@
   ASSERT_EQ(cur_matrix, matrix);
 }
 
-TEST(DisplayList, FullTransformAffectsCurrentTransform) {
+TEST_F(DisplayListTest, FullTransformAffectsCurrentTransform) {
   DisplayListBuilder builder;
-  builder.transformFullPerspective(3, 0, 4, 12.3,  //
-                                   1, 5, 3, 14.5,  //
-                                   0, 0, 7, 16.2,  //
-                                   0, 0, 0, 1);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.transformFullPerspective(3, 0, 4, 12.3,  //
+                                    1, 5, 3, 14.5,  //
+                                    0, 0, 7, 16.2,  //
+                                    0, 0, 0, 1);
   SkMatrix matrix = SkMatrix::MakeAll(3, 0, 12.3,  //
                                       1, 5, 14.5,  //
                                       0, 0, 1);
@@ -1180,7 +1266,7 @@
   SkMatrix cur_matrix = builder.GetTransform();
   ASSERT_EQ(cur_m44, m44);
   ASSERT_EQ(cur_matrix, matrix);
-  builder.translate(10, 10);
+  receiver.translate(10, 10);
   // CurrentTransform has changed
   ASSERT_NE(builder.GetTransformFullPerspective(), m44);
   ASSERT_NE(builder.GetTransform(), cur_matrix);
@@ -1189,10 +1275,11 @@
   ASSERT_EQ(cur_matrix, matrix);
 }
 
-TEST(DisplayList, ClipRectAffectsClipBounds) {
+TEST_F(DisplayListTest, ClipRectAffectsClipBounds) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
-  builder.clipRect(clip_bounds, ClipOp::kIntersect, false);
+  receiver.clipRect(clip_bounds, ClipOp::kIntersect, false);
 
   // Save initial return values for testing restored values
   SkRect initial_local_bounds = builder.GetLocalClipBounds();
@@ -1200,38 +1287,39 @@
   ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
-  builder.save();
-  builder.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, false);
+  receiver.save();
+  receiver.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, false);
   // Both clip bounds have changed
   ASSERT_NE(builder.GetLocalClipBounds(), clip_bounds);
   ASSERT_NE(builder.GetDestinationClipBounds(), clip_bounds);
   // Previous return values have not changed
   ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 
-  builder.save();
-  builder.scale(2, 2);
+  receiver.save();
+  receiver.scale(2, 2);
   SkRect scaled_clip_bounds = SkRect::MakeLTRB(5.1, 5.65, 10.2, 12.85);
   ASSERT_EQ(builder.GetLocalClipBounds(), scaled_clip_bounds);
   // Destination bounds are unaffected by transform
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 }
 
-TEST(DisplayList, ClipRectDoAAAffectsClipBounds) {
+TEST_F(DisplayListTest, ClipRectDoAAAffectsClipBounds) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
   SkRect clip_expanded_bounds = SkRect::MakeLTRB(10, 11, 21, 26);
-  builder.clipRect(clip_bounds, ClipOp::kIntersect, true);
+  receiver.clipRect(clip_bounds, ClipOp::kIntersect, true);
 
   // Save initial return values for testing restored values
   SkRect initial_local_bounds = builder.GetLocalClipBounds();
@@ -1239,57 +1327,59 @@
   ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
 
-  builder.save();
-  builder.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, true);
+  receiver.save();
+  receiver.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, true);
   // Both clip bounds have changed
   ASSERT_NE(builder.GetLocalClipBounds(), clip_expanded_bounds);
   ASSERT_NE(builder.GetDestinationClipBounds(), clip_expanded_bounds);
   // Previous return values have not changed
   ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 
-  builder.save();
-  builder.scale(2, 2);
+  receiver.save();
+  receiver.scale(2, 2);
   SkRect scaled_expanded_bounds = SkRect::MakeLTRB(5, 5.5, 10.5, 13);
   ASSERT_EQ(builder.GetLocalClipBounds(), scaled_expanded_bounds);
   // Destination bounds are unaffected by transform
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_expanded_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 }
 
-TEST(DisplayList, ClipRectAffectsClipBoundsWithMatrix) {
+TEST_F(DisplayListTest, ClipRectAffectsClipBoundsWithMatrix) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkRect clip_bounds_1 = SkRect::MakeLTRB(0, 0, 10, 10);
   SkRect clip_bounds_2 = SkRect::MakeLTRB(10, 10, 20, 20);
-  builder.save();
-  builder.clipRect(clip_bounds_1, ClipOp::kIntersect, false);
-  builder.translate(10, 0);
-  builder.clipRect(clip_bounds_1, ClipOp::kIntersect, false);
+  receiver.save();
+  receiver.clipRect(clip_bounds_1, ClipOp::kIntersect, false);
+  receiver.translate(10, 0);
+  receiver.clipRect(clip_bounds_1, ClipOp::kIntersect, false);
   ASSERT_TRUE(builder.GetDestinationClipBounds().isEmpty());
-  builder.restore();
+  receiver.restore();
 
-  builder.save();
-  builder.clipRect(clip_bounds_1, ClipOp::kIntersect, false);
-  builder.translate(-10, -10);
-  builder.clipRect(clip_bounds_2, ClipOp::kIntersect, false);
+  receiver.save();
+  receiver.clipRect(clip_bounds_1, ClipOp::kIntersect, false);
+  receiver.translate(-10, -10);
+  receiver.clipRect(clip_bounds_2, ClipOp::kIntersect, false);
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds_1);
-  builder.restore();
+  receiver.restore();
 }
 
-TEST(DisplayList, ClipRRectAffectsClipBounds) {
+TEST_F(DisplayListTest, ClipRRectAffectsClipBounds) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
   SkRRect clip = SkRRect::MakeRectXY(clip_bounds, 3, 2);
-  builder.clipRRect(clip, ClipOp::kIntersect, false);
+  receiver.clipRRect(clip, ClipOp::kIntersect, false);
 
   // Save initial return values for testing restored values
   SkRect initial_local_bounds = builder.GetLocalClipBounds();
@@ -1297,39 +1387,40 @@
   ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
-  builder.save();
-  builder.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, false);
+  receiver.save();
+  receiver.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, false);
   // Both clip bounds have changed
   ASSERT_NE(builder.GetLocalClipBounds(), clip_bounds);
   ASSERT_NE(builder.GetDestinationClipBounds(), clip_bounds);
   // Previous return values have not changed
   ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 
-  builder.save();
-  builder.scale(2, 2);
+  receiver.save();
+  receiver.scale(2, 2);
   SkRect scaled_clip_bounds = SkRect::MakeLTRB(5.1, 5.65, 10.2, 12.85);
   ASSERT_EQ(builder.GetLocalClipBounds(), scaled_clip_bounds);
   // Destination bounds are unaffected by transform
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 }
 
-TEST(DisplayList, ClipRRectDoAAAffectsClipBounds) {
+TEST_F(DisplayListTest, ClipRRectDoAAAffectsClipBounds) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
   SkRect clip_expanded_bounds = SkRect::MakeLTRB(10, 11, 21, 26);
   SkRRect clip = SkRRect::MakeRectXY(clip_bounds, 3, 2);
-  builder.clipRRect(clip, ClipOp::kIntersect, true);
+  receiver.clipRRect(clip, ClipOp::kIntersect, true);
 
   // Save initial return values for testing restored values
   SkRect initial_local_bounds = builder.GetLocalClipBounds();
@@ -1337,60 +1428,62 @@
   ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
 
-  builder.save();
-  builder.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, true);
+  receiver.save();
+  receiver.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, true);
   // Both clip bounds have changed
   ASSERT_NE(builder.GetLocalClipBounds(), clip_expanded_bounds);
   ASSERT_NE(builder.GetDestinationClipBounds(), clip_expanded_bounds);
   // Previous return values have not changed
   ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 
-  builder.save();
-  builder.scale(2, 2);
+  receiver.save();
+  receiver.scale(2, 2);
   SkRect scaled_expanded_bounds = SkRect::MakeLTRB(5, 5.5, 10.5, 13);
   ASSERT_EQ(builder.GetLocalClipBounds(), scaled_expanded_bounds);
   // Destination bounds are unaffected by transform
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_expanded_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 }
 
-TEST(DisplayList, ClipRRectAffectsClipBoundsWithMatrix) {
+TEST_F(DisplayListTest, ClipRRectAffectsClipBoundsWithMatrix) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkRect clip_bounds_1 = SkRect::MakeLTRB(0, 0, 10, 10);
   SkRect clip_bounds_2 = SkRect::MakeLTRB(10, 10, 20, 20);
   SkRRect clip1 = SkRRect::MakeRectXY(clip_bounds_1, 3, 2);
   SkRRect clip2 = SkRRect::MakeRectXY(clip_bounds_2, 3, 2);
 
-  builder.save();
-  builder.clipRRect(clip1, ClipOp::kIntersect, false);
-  builder.translate(10, 0);
-  builder.clipRRect(clip1, ClipOp::kIntersect, false);
+  receiver.save();
+  receiver.clipRRect(clip1, ClipOp::kIntersect, false);
+  receiver.translate(10, 0);
+  receiver.clipRRect(clip1, ClipOp::kIntersect, false);
   ASSERT_TRUE(builder.GetDestinationClipBounds().isEmpty());
-  builder.restore();
+  receiver.restore();
 
-  builder.save();
-  builder.clipRRect(clip1, ClipOp::kIntersect, false);
-  builder.translate(-10, -10);
-  builder.clipRRect(clip2, ClipOp::kIntersect, false);
+  receiver.save();
+  receiver.clipRRect(clip1, ClipOp::kIntersect, false);
+  receiver.translate(-10, -10);
+  receiver.clipRRect(clip2, ClipOp::kIntersect, false);
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds_1);
-  builder.restore();
+  receiver.restore();
 }
 
-TEST(DisplayList, ClipPathAffectsClipBounds) {
+TEST_F(DisplayListTest, ClipPathAffectsClipBounds) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);
   SkRect clip_bounds = SkRect::MakeLTRB(8.2, 9.3, 22.4, 27.7);
-  builder.clipPath(clip, ClipOp::kIntersect, false);
+  receiver.clipPath(clip, ClipOp::kIntersect, false);
 
   // Save initial return values for testing restored values
   SkRect initial_local_bounds = builder.GetLocalClipBounds();
@@ -1398,38 +1491,39 @@
   ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
-  builder.save();
-  builder.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, false);
+  receiver.save();
+  receiver.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, false);
   // Both clip bounds have changed
   ASSERT_NE(builder.GetLocalClipBounds(), clip_bounds);
   ASSERT_NE(builder.GetDestinationClipBounds(), clip_bounds);
   // Previous return values have not changed
   ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 
-  builder.save();
-  builder.scale(2, 2);
+  receiver.save();
+  receiver.scale(2, 2);
   SkRect scaled_clip_bounds = SkRect::MakeLTRB(4.1, 4.65, 11.2, 13.85);
   ASSERT_EQ(builder.GetLocalClipBounds(), scaled_clip_bounds);
   // Destination bounds are unaffected by transform
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 }
 
-TEST(DisplayList, ClipPathDoAAAffectsClipBounds) {
+TEST_F(DisplayListTest, ClipPathDoAAAffectsClipBounds) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);
   SkRect clip_expanded_bounds = SkRect::MakeLTRB(8, 9, 23, 28);
-  builder.clipPath(clip, ClipOp::kIntersect, true);
+  receiver.clipPath(clip, ClipOp::kIntersect, true);
 
   // Save initial return values for testing restored values
   SkRect initial_local_bounds = builder.GetLocalClipBounds();
@@ -1437,59 +1531,61 @@
   ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
 
-  builder.save();
-  builder.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, true);
+  receiver.save();
+  receiver.clipRect({0, 0, 15, 15}, ClipOp::kIntersect, true);
   // Both clip bounds have changed
   ASSERT_NE(builder.GetLocalClipBounds(), clip_expanded_bounds);
   ASSERT_NE(builder.GetDestinationClipBounds(), clip_expanded_bounds);
   // Previous return values have not changed
   ASSERT_EQ(initial_local_bounds, clip_expanded_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_expanded_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 
-  builder.save();
-  builder.scale(2, 2);
+  receiver.save();
+  receiver.scale(2, 2);
   SkRect scaled_expanded_bounds = SkRect::MakeLTRB(4, 4.5, 11.5, 14);
   ASSERT_EQ(builder.GetLocalClipBounds(), scaled_expanded_bounds);
   // Destination bounds are unaffected by transform
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_expanded_bounds);
-  builder.restore();
+  receiver.restore();
 
   // save/restore returned the values to their original values
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 }
 
-TEST(DisplayList, ClipPathAffectsClipBoundsWithMatrix) {
+TEST_F(DisplayListTest, ClipPathAffectsClipBoundsWithMatrix) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkRect clip_bounds = SkRect::MakeLTRB(0, 0, 10, 10);
   SkPath clip1 = SkPath().addCircle(2.5, 2.5, 2.5).addCircle(7.5, 7.5, 2.5);
   SkPath clip2 = SkPath().addCircle(12.5, 12.5, 2.5).addCircle(17.5, 17.5, 2.5);
 
-  builder.save();
-  builder.clipPath(clip1, ClipOp::kIntersect, false);
-  builder.translate(10, 0);
-  builder.clipPath(clip1, ClipOp::kIntersect, false);
+  receiver.save();
+  receiver.clipPath(clip1, ClipOp::kIntersect, false);
+  receiver.translate(10, 0);
+  receiver.clipPath(clip1, ClipOp::kIntersect, false);
   ASSERT_TRUE(builder.GetDestinationClipBounds().isEmpty());
-  builder.restore();
+  receiver.restore();
 
-  builder.save();
-  builder.clipPath(clip1, ClipOp::kIntersect, false);
-  builder.translate(-10, -10);
-  builder.clipPath(clip2, ClipOp::kIntersect, false);
+  receiver.save();
+  receiver.clipPath(clip1, ClipOp::kIntersect, false);
+  receiver.translate(-10, -10);
+  receiver.clipPath(clip2, ClipOp::kIntersect, false);
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds);
-  builder.restore();
+  receiver.restore();
 }
 
-TEST(DisplayList, DiffClipRectDoesNotAffectClipBounds) {
+TEST_F(DisplayListTest, DiffClipRectDoesNotAffectClipBounds) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkRect diff_clip = SkRect::MakeLTRB(0, 0, 15, 15);
   SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
-  builder.clipRect(clip_bounds, ClipOp::kIntersect, false);
+  receiver.clipRect(clip_bounds, ClipOp::kIntersect, false);
 
   // Save initial return values for testing after kDifference clip
   SkRect initial_local_bounds = builder.GetLocalClipBounds();
@@ -1497,17 +1593,18 @@
   ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
-  builder.clipRect(diff_clip, ClipOp::kDifference, false);
+  receiver.clipRect(diff_clip, ClipOp::kDifference, false);
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 }
 
-TEST(DisplayList, DiffClipRRectDoesNotAffectClipBounds) {
+TEST_F(DisplayListTest, DiffClipRRectDoesNotAffectClipBounds) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkRRect diff_clip = SkRRect::MakeRectXY({0, 0, 15, 15}, 1, 1);
   SkRect clip_bounds = SkRect::MakeLTRB(10.2, 11.3, 20.4, 25.7);
   SkRRect clip = SkRRect::MakeRectXY({10.2, 11.3, 20.4, 25.7}, 3, 2);
-  builder.clipRRect(clip, ClipOp::kIntersect, false);
+  receiver.clipRRect(clip, ClipOp::kIntersect, false);
 
   // Save initial return values for testing after kDifference clip
   SkRect initial_local_bounds = builder.GetLocalClipBounds();
@@ -1515,17 +1612,18 @@
   ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
-  builder.clipRRect(diff_clip, ClipOp::kDifference, false);
+  receiver.clipRRect(diff_clip, ClipOp::kDifference, false);
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 }
 
-TEST(DisplayList, DiffClipPathDoesNotAffectClipBounds) {
+TEST_F(DisplayListTest, DiffClipPathDoesNotAffectClipBounds) {
   DisplayListBuilder builder;
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkPath diff_clip = SkPath().addRect({0, 0, 15, 15});
   SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);
   SkRect clip_bounds = SkRect::MakeLTRB(8.2, 9.3, 22.4, 27.7);
-  builder.clipPath(clip, ClipOp::kIntersect, false);
+  receiver.clipPath(clip, ClipOp::kIntersect, false);
 
   // Save initial return values for testing after kDifference clip
   SkRect initial_local_bounds = builder.GetLocalClipBounds();
@@ -1533,40 +1631,43 @@
   ASSERT_EQ(initial_local_bounds, clip_bounds);
   ASSERT_EQ(initial_destination_bounds, clip_bounds);
 
-  builder.clipPath(diff_clip, ClipOp::kDifference, false);
+  receiver.clipPath(diff_clip, ClipOp::kDifference, false);
   ASSERT_EQ(builder.GetLocalClipBounds(), initial_local_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), initial_destination_bounds);
 }
 
-TEST(DisplayList, ClipPathWithInvertFillTypeDoesNotAffectClipBounds) {
+TEST_F(DisplayListTest, ClipPathWithInvertFillTypeDoesNotAffectClipBounds) {
   SkRect cull_rect = SkRect::MakeLTRB(0, 0, 100.0, 100.0);
   DisplayListBuilder builder(cull_rect);
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);
   clip.setFillType(SkPathFillType::kInverseWinding);
-  builder.clipPath(clip, ClipOp::kIntersect, false);
+  receiver.clipPath(clip, ClipOp::kIntersect, false);
 
   ASSERT_EQ(builder.GetLocalClipBounds(), cull_rect);
   ASSERT_EQ(builder.GetDestinationClipBounds(), cull_rect);
 }
 
-TEST(DisplayList, DiffClipPathWithInvertFillTypeAffectsClipBounds) {
+TEST_F(DisplayListTest, DiffClipPathWithInvertFillTypeAffectsClipBounds) {
   SkRect cull_rect = SkRect::MakeLTRB(0, 0, 100.0, 100.0);
   DisplayListBuilder builder(cull_rect);
+  DlOpReceiver& receiver = ToReceiver(builder);
   SkPath clip = SkPath().addCircle(10.2, 11.3, 2).addCircle(20.4, 25.7, 2);
   clip.setFillType(SkPathFillType::kInverseWinding);
   SkRect clip_bounds = SkRect::MakeLTRB(8.2, 9.3, 22.4, 27.7);
-  builder.clipPath(clip, ClipOp::kDifference, false);
+  receiver.clipPath(clip, ClipOp::kDifference, false);
 
   ASSERT_EQ(builder.GetLocalClipBounds(), clip_bounds);
   ASSERT_EQ(builder.GetDestinationClipBounds(), clip_bounds);
 }
 
-TEST(DisplayList, FlatDrawPointsProducesBounds) {
+TEST_F(DisplayListTest, FlatDrawPointsProducesBounds) {
   SkPoint horizontal_points[2] = {{10, 10}, {20, 10}};
   SkPoint vertical_points[2] = {{10, 10}, {10, 20}};
   {
     DisplayListBuilder builder;
-    builder.drawPoints(PointMode::kPolygon, 2, horizontal_points);
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.drawPoints(PointMode::kPolygon, 2, horizontal_points);
     SkRect bounds = builder.Build()->bounds();
     EXPECT_TRUE(bounds.contains(10, 10));
     EXPECT_TRUE(bounds.contains(20, 10));
@@ -1574,7 +1675,8 @@
   }
   {
     DisplayListBuilder builder;
-    builder.drawPoints(PointMode::kPolygon, 2, vertical_points);
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.drawPoints(PointMode::kPolygon, 2, vertical_points);
     SkRect bounds = builder.Build()->bounds();
     EXPECT_TRUE(bounds.contains(10, 10));
     EXPECT_TRUE(bounds.contains(10, 20));
@@ -1582,14 +1684,16 @@
   }
   {
     DisplayListBuilder builder;
-    builder.drawPoints(PointMode::kPoints, 1, horizontal_points);
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.drawPoints(PointMode::kPoints, 1, horizontal_points);
     SkRect bounds = builder.Build()->bounds();
     EXPECT_TRUE(bounds.contains(10, 10));
   }
   {
     DisplayListBuilder builder;
-    builder.setStrokeWidth(2);
-    builder.drawPoints(PointMode::kPolygon, 2, horizontal_points);
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.setStrokeWidth(2);
+    receiver.drawPoints(PointMode::kPolygon, 2, horizontal_points);
     SkRect bounds = builder.Build()->bounds();
     EXPECT_TRUE(bounds.contains(10, 10));
     EXPECT_TRUE(bounds.contains(20, 10));
@@ -1597,8 +1701,9 @@
   }
   {
     DisplayListBuilder builder;
-    builder.setStrokeWidth(2);
-    builder.drawPoints(PointMode::kPolygon, 2, vertical_points);
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.setStrokeWidth(2);
+    receiver.drawPoints(PointMode::kPolygon, 2, vertical_points);
     SkRect bounds = builder.Build()->bounds();
     EXPECT_TRUE(bounds.contains(10, 10));
     EXPECT_TRUE(bounds.contains(10, 20));
@@ -1606,8 +1711,9 @@
   }
   {
     DisplayListBuilder builder;
-    builder.setStrokeWidth(2);
-    builder.drawPoints(PointMode::kPoints, 1, horizontal_points);
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.setStrokeWidth(2);
+    receiver.drawPoints(PointMode::kPoints, 1, horizontal_points);
     SkRect bounds = builder.Build()->bounds();
     EXPECT_TRUE(bounds.contains(10, 10));
     EXPECT_EQ(bounds, SkRect::MakeLTRB(9, 9, 11, 11));
@@ -1631,10 +1737,11 @@
   }
 }
 
-TEST(DisplayList, RTreeOfSimpleScene) {
+TEST_F(DisplayListTest, RTreeOfSimpleScene) {
   DisplayListBuilder builder(/*prepare_rtree=*/true);
-  builder.drawRect({10, 10, 20, 20});
-  builder.drawRect({50, 50, 60, 60});
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.drawRect({50, 50, 60, 60});
   auto display_list = builder.Build();
   auto rtree = display_list->rtree();
   std::vector<SkRect> rects = {
@@ -1658,12 +1765,13 @@
   test_rtree(rtree, {19, 19, 51, 51}, rects, {0, 1});
 }
 
-TEST(DisplayList, RTreeOfSaveRestoreScene) {
+TEST_F(DisplayListTest, RTreeOfSaveRestoreScene) {
   DisplayListBuilder builder(/*prepare_rtree=*/true);
-  builder.drawRect({10, 10, 20, 20});
-  builder.save();
-  builder.drawRect({50, 50, 60, 60});
-  builder.restore();
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.drawRect({10, 10, 20, 20});
+  receiver.save();
+  receiver.drawRect({50, 50, 60, 60});
+  receiver.restore();
   auto display_list = builder.Build();
   auto rtree = display_list->rtree();
   std::vector<SkRect> rects = {
@@ -1687,7 +1795,7 @@
   test_rtree(rtree, {19, 19, 51, 51}, rects, {0, 1});
 }
 
-TEST(DisplayList, RTreeOfSaveLayerFilterScene) {
+TEST_F(DisplayListTest, RTreeOfSaveLayerFilterScene) {
   DisplayListBuilder builder(/*prepare_rtree=*/true);
   // blur filter with sigma=1 expands by 3 on all sides
   auto filter = DlBlurImageFilter(1.0, 1.0, DlTileMode::kClamp);
@@ -1722,14 +1830,16 @@
   test_rtree(rtree, {19, 19, 51, 51}, rects, {0, 1});
 }
 
-TEST(DisplayList, NestedDisplayListRTreesAreSparse) {
+TEST_F(DisplayListTest, NestedDisplayListRTreesAreSparse) {
   DisplayListBuilder nested_dl_builder(/**prepare_rtree=*/true);
-  nested_dl_builder.drawRect({10, 10, 20, 20});
-  nested_dl_builder.drawRect({50, 50, 60, 60});
+  DlOpReceiver& nested_dl_receiver = ToReceiver(nested_dl_builder);
+  nested_dl_receiver.drawRect({10, 10, 20, 20});
+  nested_dl_receiver.drawRect({50, 50, 60, 60});
   auto nested_display_list = nested_dl_builder.Build();
 
   DisplayListBuilder builder(/**prepare_rtree=*/true);
-  builder.drawDisplayList(nested_display_list);
+  DlOpReceiver& receiver = ToReceiver(builder);
+  receiver.drawDisplayList(nested_display_list);
   auto display_list = builder.Build();
 
   auto rtree = display_list->rtree();
@@ -1742,98 +1852,106 @@
   test_rtree(rtree, {19, 19, 51, 51}, rects, {0, 1});
 }
 
-TEST(DisplayList, RemoveUnnecessarySaveRestorePairs) {
+TEST_F(DisplayListTest, RemoveUnnecessarySaveRestorePairs) {
   {
     DisplayListBuilder builder;
-    builder.drawRect({10, 10, 20, 20});
-    builder.save();  // This save op is unnecessary
-    builder.drawRect({50, 50, 60, 60});
-    builder.restore();
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.drawRect({10, 10, 20, 20});
+    receiver.save();  // This save op is unnecessary
+    receiver.drawRect({50, 50, 60, 60});
+    receiver.restore();
 
     DisplayListBuilder builder2;
-    builder2.drawRect({10, 10, 20, 20});
-    builder2.drawRect({50, 50, 60, 60});
+    DlOpReceiver& receiver2 = ToReceiver(builder2);
+    receiver2.drawRect({10, 10, 20, 20});
+    receiver2.drawRect({50, 50, 60, 60});
     ASSERT_TRUE(DisplayListsEQ_Verbose(builder.Build(), builder2.Build()));
   }
 
   {
     DisplayListBuilder builder;
-    builder.drawRect({10, 10, 20, 20});
-    builder.save();
-    builder.translate(1.0, 1.0);
+    DlOpReceiver& receiver = ToReceiver(builder);
+    receiver.drawRect({10, 10, 20, 20});
+    receiver.save();
+    receiver.translate(1.0, 1.0);
     {
-      builder.save();  // unnecessary
-      builder.drawRect({50, 50, 60, 60});
-      builder.restore();
+      receiver.save();  // unnecessary
+      receiver.drawRect({50, 50, 60, 60});
+      receiver.restore();
     }
 
-    builder.restore();
+    receiver.restore();
 
     DisplayListBuilder builder2;
-    builder2.drawRect({10, 10, 20, 20});
-    builder2.save();
-    builder2.translate(1.0, 1.0);
-    { builder2.drawRect({50, 50, 60, 60}); }
-    builder2.restore();
+    DlOpReceiver& receiver2 = ToReceiver(builder2);
+    receiver2.drawRect({10, 10, 20, 20});
+    receiver2.save();
+    receiver2.translate(1.0, 1.0);
+    { receiver2.drawRect({50, 50, 60, 60}); }
+    receiver2.restore();
     ASSERT_TRUE(DisplayListsEQ_Verbose(builder.Build(), builder2.Build()));
   }
 }
 
-TEST(DisplayList, CollapseMultipleNestedSaveRestore) {
+TEST_F(DisplayListTest, CollapseMultipleNestedSaveRestore) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.save();
-  builder1.translate(10, 10);
-  builder1.scale(2, 2);
-  builder1.clipRect({10, 10, 20, 20}, ClipOp::kIntersect, false);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.restore();
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.save();
+  receiver1.translate(10, 10);
+  receiver1.scale(2, 2);
+  receiver1.clipRect({10, 10, 20, 20}, ClipOp::kIntersect, false);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.restore();
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.save();
-  builder2.translate(10, 10);
-  builder2.scale(2, 2);
-  builder2.clipRect({10, 10, 20, 20}, ClipOp::kIntersect, false);
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.save();
+  receiver2.translate(10, 10);
+  receiver2.scale(2, 2);
+  receiver2.clipRect({10, 10, 20, 20}, ClipOp::kIntersect, false);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, CollapseNestedSaveAndSaveLayerRestore) {
+TEST_F(DisplayListTest, CollapseNestedSaveAndSaveLayerRestore) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.saveLayer(nullptr, false);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.scale(2, 2);
-  builder1.restore();
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.saveLayer(nullptr, SaveLayerOptions::kNoAttributes);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.scale(2, 2);
+  receiver1.restore();
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.saveLayer(nullptr, false);
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.scale(2, 2);
-  builder2.restore();
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.saveLayer(nullptr, SaveLayerOptions::kNoAttributes);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.scale(2, 2);
+  receiver2.restore();
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, RemoveUnnecessarySaveRestorePairsInSetPaint) {
+TEST_F(DisplayListTest, RemoveUnnecessarySaveRestorePairsInSetPaint) {
   SkRect build_bounds = SkRect::MakeLTRB(-100, -100, 200, 200);
   SkRect rect = SkRect::MakeLTRB(30, 30, 70, 70);
   // clang-format off
   const float alpha_matrix[] = {
-    0, 0, 0, 0, 0,
-    0, 1, 0, 0, 0,
-    0, 0, 1, 0, 0,
-    0, 0, 0, 0, 1,
+      0, 0, 0, 0, 0,
+      0, 1, 0, 0, 0,
+      0, 0, 1, 0, 0,
+      0, 0, 0, 0, 1,
   };
   // clang-format on
   DlMatrixColorFilter alpha_color_filter(alpha_matrix);
@@ -1880,359 +1998,437 @@
   }
 }
 
-TEST(DisplayList, TransformTriggersDeferredSave) {
+TEST_F(DisplayListTest, TransformTriggersDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.transform(SkM44::Translate(10, 100));
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.transform(SkM44::Translate(10, 100));
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.transformFullPerspective(1, 0, 0, 10,   //
+                                     0, 1, 0, 100,  //
+                                     0, 0, 1, 0,    //
+                                     0, 0, 0, 1);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.transformFullPerspective(1, 0, 0, 10,   //
+                                     0, 1, 0, 100,  //
+                                     0, 0, 1, 0,    //
+                                     0, 0, 0, 1);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.save();
-  builder2.transform(SkM44::Translate(10, 100));
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
-  builder2.save();
-  builder2.transform(SkM44::Translate(10, 100));
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.save();
+  receiver2.transformFullPerspective(1, 0, 0, 10,   //
+                                     0, 1, 0, 100,  //
+                                     0, 0, 1, 0,    //
+                                     0, 0, 0, 1);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
+  receiver2.save();
+  receiver2.transformFullPerspective(1, 0, 0, 10,   //
+                                     0, 1, 0, 100,  //
+                                     0, 0, 1, 0,    //
+                                     0, 0, 0, 1);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, Transform2DTriggersDeferredSave) {
+TEST_F(DisplayListTest, Transform2DTriggersDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.transform2DAffine(0, 1, 12, 1, 0, 33);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.transform2DAffine(0, 1, 12, 1, 0, 33);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.save();
-  builder2.transform2DAffine(0, 1, 12, 1, 0, 33);
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.save();
+  receiver2.transform2DAffine(0, 1, 12, 1, 0, 33);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, TransformPerspectiveTriggersDeferredSave) {
+TEST_F(DisplayListTest, TransformPerspectiveTriggersDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.transformFullPerspective(0, 1, 0, 12, 1, 0, 0, 33, 3, 2, 5, 29, 0, 0,
-                                    0, 12);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.transformFullPerspective(0, 1, 0, 12,  //
+                                     1, 0, 0, 33,  //
+                                     3, 2, 5, 29,  //
+                                     0, 0, 0, 12);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.save();
-  builder2.transformFullPerspective(0, 1, 0, 12, 1, 0, 0, 33, 3, 2, 5, 29, 0, 0,
-                                    0, 12);
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.save();
+  receiver2.transformFullPerspective(0, 1, 0, 12,  //
+                                     1, 0, 0, 33,  //
+                                     3, 2, 5, 29,  //
+                                     0, 0, 0, 12);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, ResetTransformTriggersDeferredSave) {
+TEST_F(DisplayListTest, ResetTransformTriggersDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.transformReset();
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.transformReset();
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.save();
-  builder2.transformReset();
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.save();
+  receiver2.transformReset();
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, SkewTriggersDeferredSave) {
+TEST_F(DisplayListTest, SkewTriggersDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.skew(10, 10);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.skew(10, 10);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.save();
-  builder2.skew(10, 10);
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.save();
+  receiver2.skew(10, 10);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, TranslateTriggersDeferredSave) {
+TEST_F(DisplayListTest, TranslateTriggersDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.translate(10, 10);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.translate(10, 10);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.save();
-  builder2.translate(10, 10);
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.save();
+  receiver2.translate(10, 10);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, ScaleTriggersDeferredSave) {
+TEST_F(DisplayListTest, ScaleTriggersDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.scale(0.5, 0.5);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.scale(0.5, 0.5);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.save();
-  builder2.scale(0.5, 0.5);
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.save();
+  receiver2.scale(0.5, 0.5);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, ClipRectTriggersDeferredSave) {
+TEST_F(DisplayListTest, ClipRectTriggersDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.clipRect(SkRect::MakeLTRB(0, 0, 100, 100), ClipOp::kIntersect, true);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.transform(SkM44());
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.clipRect(SkRect::MakeLTRB(0, 0, 100, 100), ClipOp::kIntersect,
+                     true);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.transformFullPerspective(1, 0, 0, 0,  //
+                                     0, 1, 0, 0,  //
+                                     0, 0, 1, 0,  //
+                                     0, 0, 0, 1);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.save();
-  builder2.clipRect(SkRect::MakeLTRB(0, 0, 100, 100), ClipOp::kIntersect, true);
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
-  builder2.transform(SkM44());
-  builder2.drawRect({0, 0, 100, 100});
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.save();
+  receiver2.clipRect(SkRect::MakeLTRB(0, 0, 100, 100), ClipOp::kIntersect,
+                     true);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
+  receiver2.transformFullPerspective(1, 0, 0, 0,  //
+                                     0, 1, 0, 0,  //
+                                     0, 0, 1, 0,  //
+                                     0, 0, 0, 1);
+  receiver2.drawRect({0, 0, 100, 100});
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, ClipRRectTriggersDeferredSave) {
+TEST_F(DisplayListTest, ClipRRectTriggersDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.clipRRect(kTestRRect, ClipOp::kIntersect, true);
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.clipRRect(kTestRRect, ClipOp::kIntersect, true);
 
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.transform(SkM44());
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.transformFullPerspective(1, 0, 0, 0,  //
+                                     0, 1, 0, 0,  //
+                                     0, 0, 1, 0,  //
+                                     0, 0, 0, 1);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.save();
-  builder2.clipRRect(kTestRRect, ClipOp::kIntersect, true);
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.save();
+  receiver2.clipRRect(kTestRRect, ClipOp::kIntersect, true);
 
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
-  builder2.transform(SkM44());
-  builder2.drawRect({0, 0, 100, 100});
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
+  receiver2.transformFullPerspective(1, 0, 0, 0,  //
+                                     0, 1, 0, 0,  //
+                                     0, 0, 1, 0,  //
+                                     0, 0, 0, 1);
+  receiver2.drawRect({0, 0, 100, 100});
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, ClipPathTriggersDeferredSave) {
+TEST_F(DisplayListTest, ClipPathTriggersDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.clipPath(kTestPath1, ClipOp::kIntersect, true);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.transform(SkM44());
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.clipPath(kTestPath1, ClipOp::kIntersect, true);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.transformFullPerspective(1, 0, 0, 0,  //
+                                     0, 1, 0, 0,  //
+                                     0, 0, 1, 0,  //
+                                     0, 0, 0, 1);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.save();
-  builder2.clipPath(kTestPath1, ClipOp::kIntersect, true);
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.restore();
-  builder2.transform(SkM44());
-  builder2.drawRect({0, 0, 100, 100});
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.save();
+  receiver2.clipPath(kTestPath1, ClipOp::kIntersect, true);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.restore();
+  receiver2.transformFullPerspective(1, 0, 0, 0,  //
+                                     0, 1, 0, 0,  //
+                                     0, 0, 1, 0,  //
+                                     0, 0, 0, 1);
+  receiver2.drawRect({0, 0, 100, 100});
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, NOPTranslateDoesNotTriggerDeferredSave) {
+TEST_F(DisplayListTest, NOPTranslateDoesNotTriggerDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.translate(0, 0);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.translate(0, 0);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.drawRect({0, 0, 100, 100});
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.drawRect({0, 0, 100, 100});
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, NOPScaleDoesNotTriggerDeferredSave) {
+TEST_F(DisplayListTest, NOPScaleDoesNotTriggerDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.scale(1.0, 1.0);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.scale(1.0, 1.0);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.drawRect({0, 0, 100, 100});
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.drawRect({0, 0, 100, 100});
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, NOPRotationDoesNotTriggerDeferredSave) {
+TEST_F(DisplayListTest, NOPRotationDoesNotTriggerDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.rotate(360);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.rotate(360);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.drawRect({0, 0, 100, 100});
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.drawRect({0, 0, 100, 100});
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, NOPSkewDoesNotTriggerDeferredSave) {
+TEST_F(DisplayListTest, NOPSkewDoesNotTriggerDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.skew(0, 0);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.skew(0, 0);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.drawRect({0, 0, 100, 100});
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.drawRect({0, 0, 100, 100});
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, NOPTransformDoesNotTriggerDeferredSave) {
+TEST_F(DisplayListTest, NOPTransformDoesNotTriggerDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.transform(SkM44());
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.transform(SkM44());
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.transformFullPerspective(1, 0, 0, 0,  //
+                                     0, 1, 0, 0,  //
+                                     0, 0, 1, 0,  //
+                                     0, 0, 0, 1);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.transformFullPerspective(1, 0, 0, 0,  //
+                                     0, 1, 0, 0,  //
+                                     0, 0, 1, 0,  //
+                                     0, 0, 0, 1);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.drawRect({0, 0, 100, 100});
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.drawRect({0, 0, 100, 100});
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, NOPTransform2DDoesNotTriggerDeferredSave) {
+TEST_F(DisplayListTest, NOPTransform2DDoesNotTriggerDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.transform2DAffine(1, 0, 0, 0, 1, 0);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.transform2DAffine(1, 0, 0, 0, 1, 0);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.drawRect({0, 0, 100, 100});
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.drawRect({0, 0, 100, 100});
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, NOPTransformFullPerspectiveDoesNotTriggerDeferredSave) {
+TEST_F(DisplayListTest, NOPTransformFullPerspectiveDoesNotTriggerDeferredSave) {
   {
     DisplayListBuilder builder1;
-    builder1.save();
-    builder1.save();
-    builder1.transformFullPerspective(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
-                                      0, 1);
-    builder1.drawRect({0, 0, 100, 100});
-    builder1.restore();
-    builder1.drawRect({0, 0, 100, 100});
-    builder1.restore();
+    DlOpReceiver& receiver1 = ToReceiver(builder1);
+    receiver1.save();
+    receiver1.save();
+    receiver1.transformFullPerspective(1, 0, 0, 0,  //
+                                       0, 1, 0, 0,  //
+                                       0, 0, 1, 0,  //
+                                       0, 0, 0, 1);
+    receiver1.drawRect({0, 0, 100, 100});
+    receiver1.restore();
+    receiver1.drawRect({0, 0, 100, 100});
+    receiver1.restore();
     auto display_list1 = builder1.Build();
 
     DisplayListBuilder builder2;
-    builder2.drawRect({0, 0, 100, 100});
-    builder2.drawRect({0, 0, 100, 100});
+    DlOpReceiver& receiver2 = ToReceiver(builder2);
+    receiver2.drawRect({0, 0, 100, 100});
+    receiver2.drawRect({0, 0, 100, 100});
     auto display_list2 = builder2.Build();
 
     ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
@@ -2240,23 +2436,27 @@
 
   {
     DisplayListBuilder builder1;
-    builder1.save();
-    builder1.save();
-    builder1.transformFullPerspective(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0,
-                                      0, 1);
-    builder1.transformReset();
-    builder1.drawRect({0, 0, 100, 100});
-    builder1.restore();
-    builder1.drawRect({0, 0, 100, 100});
-    builder1.restore();
+    DlOpReceiver& receiver1 = ToReceiver(builder1);
+    receiver1.save();
+    receiver1.save();
+    receiver1.transformFullPerspective(1, 0, 0, 0,  //
+                                       0, 1, 0, 0,  //
+                                       0, 0, 1, 0,  //
+                                       0, 0, 0, 1);
+    receiver1.transformReset();
+    receiver1.drawRect({0, 0, 100, 100});
+    receiver1.restore();
+    receiver1.drawRect({0, 0, 100, 100});
+    receiver1.restore();
     auto display_list1 = builder1.Build();
 
     DisplayListBuilder builder2;
-    builder2.save();
-    builder2.transformReset();
-    builder2.drawRect({0, 0, 100, 100});
-    builder2.restore();
-    builder2.drawRect({0, 0, 100, 100});
+    DlOpReceiver& receiver2 = ToReceiver(builder2);
+    receiver2.save();
+    receiver2.transformReset();
+    receiver2.drawRect({0, 0, 100, 100});
+    receiver2.restore();
+    receiver2.drawRect({0, 0, 100, 100});
 
     auto display_list2 = builder2.Build();
 
@@ -2264,40 +2464,42 @@
   }
 }
 
-TEST(DisplayList, NOPClipDoesNotTriggerDeferredSave) {
+TEST_F(DisplayListTest, NOPClipDoesNotTriggerDeferredSave) {
   DisplayListBuilder builder1;
-  builder1.save();
-  builder1.save();
-  builder1.clipRect(SkRect::MakeLTRB(0, SK_ScalarNaN, SK_ScalarNaN, 0),
-                    ClipOp::kIntersect, true);
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
-  builder1.drawRect({0, 0, 100, 100});
-  builder1.restore();
+  DlOpReceiver& receiver1 = ToReceiver(builder1);
+  receiver1.save();
+  receiver1.save();
+  receiver1.clipRect(SkRect::MakeLTRB(0, SK_ScalarNaN, SK_ScalarNaN, 0),
+                     ClipOp::kIntersect, true);
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
+  receiver1.drawRect({0, 0, 100, 100});
+  receiver1.restore();
   auto display_list1 = builder1.Build();
 
   DisplayListBuilder builder2;
-  builder2.drawRect({0, 0, 100, 100});
-  builder2.drawRect({0, 0, 100, 100});
+  DlOpReceiver& receiver2 = ToReceiver(builder2);
+  receiver2.drawRect({0, 0, 100, 100});
+  receiver2.drawRect({0, 0, 100, 100});
   auto display_list2 = builder2.Build();
 
   ASSERT_TRUE(DisplayListsEQ_Verbose(display_list1, display_list2));
 }
 
-TEST(DisplayList, RTreeOfClippedSaveLayerFilterScene) {
+TEST_F(DisplayListTest, RTreeOfClippedSaveLayerFilterScene) {
   DisplayListBuilder builder(/*prepare_rtree=*/true);
   // blur filter with sigma=1 expands by 30 on all sides
   auto filter = DlBlurImageFilter(10.0, 10.0, DlTileMode::kClamp);
   DlPaint default_paint = DlPaint();
   DlPaint filter_paint = DlPaint().setImageFilter(&filter);
   builder.DrawRect({10, 10, 20, 20}, default_paint);
-  builder.clipRect({50, 50, 60, 60}, ClipOp::kIntersect, false);
+  builder.ClipRect({50, 50, 60, 60}, ClipOp::kIntersect, false);
   builder.SaveLayer(nullptr, &filter_paint);
   // the following rectangle will be expanded to 23,23,87,87
   // by the saveLayer filter during the restore operation
   // but it will then be clipped to 50,50,60,60
   builder.DrawRect({53, 53, 57, 57}, default_paint);
-  builder.restore();
+  builder.Restore();
   auto display_list = builder.Build();
   auto rtree = display_list->rtree();
   std::vector<SkRect> rects = {
@@ -2321,12 +2523,13 @@
   test_rtree(rtree, {19, 19, 51, 51}, rects, {0, 1});
 }
 
-TEST(DisplayList, RTreeRenderCulling) {
+TEST_F(DisplayListTest, RTreeRenderCulling) {
   DisplayListBuilder main_builder(true);
-  main_builder.drawRect({0, 0, 10, 10});
-  main_builder.drawRect({20, 0, 30, 10});
-  main_builder.drawRect({0, 20, 10, 30});
-  main_builder.drawRect({20, 20, 30, 30});
+  DlOpReceiver& main_receiver = ToReceiver(main_builder);
+  main_receiver.drawRect({0, 0, 10, 10});
+  main_receiver.drawRect({20, 0, 30, 10});
+  main_receiver.drawRect({0, 20, 10, 30});
+  main_receiver.drawRect({20, 20, 30, 30});
   auto main = main_builder.Build();
 
   {  // No rects
@@ -2336,7 +2539,7 @@
     auto expected = expected_builder.Build();
 
     DisplayListBuilder culling_builder(cull_rect);
-    main->RenderTo(&culling_builder);
+    main->Dispatch(ToReceiver(culling_builder), cull_rect);
 
     EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
   }
@@ -2345,11 +2548,12 @@
     SkRect cull_rect = {9, 9, 19, 19};
 
     DisplayListBuilder expected_builder;
-    expected_builder.drawRect({0, 0, 10, 10});
+    DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
+    expected_receiver.drawRect({0, 0, 10, 10});
     auto expected = expected_builder.Build();
 
     DisplayListBuilder culling_builder(cull_rect);
-    main->RenderTo(&culling_builder);
+    main->Dispatch(ToReceiver(culling_builder), cull_rect);
 
     EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
   }
@@ -2358,11 +2562,12 @@
     SkRect cull_rect = {11, 9, 21, 19};
 
     DisplayListBuilder expected_builder;
-    expected_builder.drawRect({20, 0, 30, 10});
+    DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
+    expected_receiver.drawRect({20, 0, 30, 10});
     auto expected = expected_builder.Build();
 
     DisplayListBuilder culling_builder(cull_rect);
-    main->RenderTo(&culling_builder);
+    main->Dispatch(ToReceiver(culling_builder), cull_rect);
 
     EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
   }
@@ -2371,11 +2576,12 @@
     SkRect cull_rect = {9, 11, 19, 21};
 
     DisplayListBuilder expected_builder;
-    expected_builder.drawRect({0, 20, 10, 30});
+    DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
+    expected_receiver.drawRect({0, 20, 10, 30});
     auto expected = expected_builder.Build();
 
     DisplayListBuilder culling_builder(cull_rect);
-    main->RenderTo(&culling_builder);
+    main->Dispatch(ToReceiver(culling_builder), cull_rect);
 
     EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
   }
@@ -2384,11 +2590,12 @@
     SkRect cull_rect = {11, 11, 21, 21};
 
     DisplayListBuilder expected_builder;
-    expected_builder.drawRect({20, 20, 30, 30});
+    DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
+    expected_receiver.drawRect({20, 20, 30, 30});
     auto expected = expected_builder.Build();
 
     DisplayListBuilder culling_builder(cull_rect);
-    main->RenderTo(&culling_builder);
+    main->Dispatch(ToReceiver(culling_builder), cull_rect);
 
     EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
   }
@@ -2397,13 +2604,13 @@
     SkRect cull_rect = {9, 9, 21, 21};
 
     DisplayListBuilder culling_builder(cull_rect);
-    main->RenderTo(&culling_builder);
+    main->Dispatch(ToReceiver(culling_builder), cull_rect);
 
     EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), main));
   }
 }
 
-TEST(DisplayListTest, DrawSaveDrawCannotInheritOpacity) {
+TEST_F(DisplayListTest, DrawSaveDrawCannotInheritOpacity) {
   DisplayListBuilder builder;
   builder.DrawCircle({10, 10}, 5, DlPaint());
   builder.Save();
diff --git a/display_list/display_list_utils.cc b/display_list/display_list_utils.cc
index 2ffed31..e6c21d5 100644
--- a/display_list/display_list_utils.cc
+++ b/display_list/display_list_utils.cc
@@ -4,106 +4,8 @@
 
 #include "flutter/display_list/display_list_utils.h"
 
-#include <math.h>
-#include <optional>
-#include <type_traits>
-
-#include "flutter/display_list/display_list_blend_mode.h"
-#include "flutter/display_list/display_list_canvas_dispatcher.h"
-#include "flutter/fml/logging.h"
-#include "third_party/skia/include/core/SkMaskFilter.h"
-#include "third_party/skia/include/core/SkPath.h"
-#include "third_party/skia/include/core/SkRSXform.h"
-#include "third_party/skia/include/core/SkTextBlob.h"
-#include "third_party/skia/include/utils/SkShadowUtils.h"
-
 namespace flutter {
 
-// clang-format off
-constexpr float kInvertColorMatrix[20] = {
-  -1.0,    0,    0, 1.0, 0,
-     0, -1.0,    0, 1.0, 0,
-     0,    0, -1.0, 1.0, 0,
-   1.0,  1.0,  1.0, 1.0, 0
-};
-// clang-format on
-
-void SkPaintDispatchHelper::save_opacity(SkScalar child_opacity) {
-  save_stack_.emplace_back(opacity_);
-  set_opacity(child_opacity);
-}
-void SkPaintDispatchHelper::restore_opacity() {
-  if (save_stack_.empty()) {
-    return;
-  }
-  set_opacity(save_stack_.back().opacity);
-  save_stack_.pop_back();
-}
-
-void SkPaintDispatchHelper::setAntiAlias(bool aa) {
-  paint_.setAntiAlias(aa);
-}
-void SkPaintDispatchHelper::setDither(bool dither) {
-  paint_.setDither(dither);
-}
-void SkPaintDispatchHelper::setInvertColors(bool invert) {
-  invert_colors_ = invert;
-  paint_.setColorFilter(makeColorFilter());
-}
-void SkPaintDispatchHelper::setStrokeCap(DlStrokeCap cap) {
-  paint_.setStrokeCap(ToSk(cap));
-}
-void SkPaintDispatchHelper::setStrokeJoin(DlStrokeJoin join) {
-  paint_.setStrokeJoin(ToSk(join));
-}
-void SkPaintDispatchHelper::setStyle(DlDrawStyle style) {
-  paint_.setStyle(ToSk(style));
-}
-void SkPaintDispatchHelper::setStrokeWidth(SkScalar width) {
-  paint_.setStrokeWidth(width);
-}
-void SkPaintDispatchHelper::setStrokeMiter(SkScalar limit) {
-  paint_.setStrokeMiter(limit);
-}
-void SkPaintDispatchHelper::setColor(DlColor color) {
-  current_color_ = color;
-  paint_.setColor(color);
-  if (has_opacity()) {
-    paint_.setAlphaf(paint_.getAlphaf() * opacity());
-  }
-}
-void SkPaintDispatchHelper::setBlendMode(DlBlendMode mode) {
-  paint_.setBlendMode(ToSk(mode));
-}
-void SkPaintDispatchHelper::setColorSource(const DlColorSource* source) {
-  paint_.setShader(source ? source->skia_object() : nullptr);
-}
-void SkPaintDispatchHelper::setImageFilter(const DlImageFilter* filter) {
-  paint_.setImageFilter(filter ? filter->skia_object() : nullptr);
-}
-void SkPaintDispatchHelper::setColorFilter(const DlColorFilter* filter) {
-  color_filter_ = filter ? filter->shared() : nullptr;
-  paint_.setColorFilter(makeColorFilter());
-}
-void SkPaintDispatchHelper::setPathEffect(const DlPathEffect* effect) {
-  paint_.setPathEffect(effect ? effect->skia_object() : nullptr);
-}
-void SkPaintDispatchHelper::setMaskFilter(const DlMaskFilter* filter) {
-  paint_.setMaskFilter(filter ? filter->skia_object() : nullptr);
-}
-
-sk_sp<SkColorFilter> SkPaintDispatchHelper::makeColorFilter() const {
-  if (!invert_colors_) {
-    return color_filter_ ? color_filter_->skia_object() : nullptr;
-  }
-  sk_sp<SkColorFilter> invert_filter =
-      SkColorFilters::Matrix(kInvertColorMatrix);
-  if (color_filter_) {
-    invert_filter = invert_filter->makeComposed(color_filter_->skia_object());
-  }
-  return invert_filter;
-}
-
 void RectBoundsAccumulator::accumulate(const SkRect& r, int index) {
   if (r.fLeft < r.fRight && r.fTop < r.fBottom) {
     rect_.accumulate(r.fLeft, r.fTop);
diff --git a/display_list/display_list_utils.h b/display_list/display_list_utils.h
index c2bed14..152ae10 100644
--- a/display_list/display_list_utils.h
+++ b/display_list/display_list_utils.h
@@ -7,33 +7,24 @@
 
 #include <optional>
 
-#include "flutter/display_list/display_list.h"
-#include "flutter/display_list/display_list_blend_mode.h"
-#include "flutter/display_list/display_list_dispatcher.h"
-#include "flutter/display_list/display_list_flags.h"
 #include "flutter/display_list/display_list_rtree.h"
+#include "flutter/display_list/dl_op_receiver.h"
 #include "flutter/fml/logging.h"
-#include "flutter/fml/macros.h"
-#include "third_party/skia/include/core/SkMaskFilter.h"
 
 // This file contains various utility classes to ease implementing
-// a Flutter DisplayList Dispatcher, including:
+// a Flutter DisplayList DlOpReceiver, including:
 //
 // IgnoreAttributeDispatchHelper:
 // IgnoreClipDispatchHelper:
 // IgnoreTransformDispatchHelper
-//     Empty overrides of all of the associated methods of Dispatcher
-//     for dispatchers that only track some of the rendering operations
-//
-// SkPaintAttributeDispatchHelper:
-//     Tracks the attribute methods and maintains their state in an
-//     SkPaint object.
+//     Empty overrides of all of the associated methods of DlOpReceiver
+//     for receivers that only track some of the rendering operations
 
 namespace flutter {
 
-// A utility class that will ignore all Dispatcher methods relating
+// A utility class that will ignore all DlOpReceiver methods relating
 // to the setting of attributes.
-class IgnoreAttributeDispatchHelper : public virtual Dispatcher {
+class IgnoreAttributeDispatchHelper : public virtual DlOpReceiver {
  public:
   void setAntiAlias(bool aa) override {}
   void setDither(bool dither) override {}
@@ -52,9 +43,9 @@
   void setMaskFilter(const DlMaskFilter* filter) override {}
 };
 
-// A utility class that will ignore all Dispatcher methods relating
+// A utility class that will ignore all DlOpReceiver methods relating
 // to setting a clip.
-class IgnoreClipDispatchHelper : public virtual Dispatcher {
+class IgnoreClipDispatchHelper : public virtual DlOpReceiver {
   void clipRect(const SkRect& rect,
                 DlCanvas::ClipOp clip_op,
                 bool is_aa) override {}
@@ -66,9 +57,9 @@
                 bool is_aa) override {}
 };
 
-// A utility class that will ignore all Dispatcher methods relating
+// A utility class that will ignore all DlOpReceiver methods relating
 // to modifying the transform.
-class IgnoreTransformDispatchHelper : public virtual Dispatcher {
+class IgnoreTransformDispatchHelper : public virtual DlOpReceiver {
  public:
   void translate(SkScalar tx, SkScalar ty) override {}
   void scale(SkScalar sx, SkScalar sy) override {}
@@ -88,7 +79,7 @@
   void transformReset() override {}
 };
 
-class IgnoreDrawDispatchHelper : public virtual Dispatcher {
+class IgnoreDrawDispatchHelper : public virtual DlOpReceiver {
  public:
   void save() override {}
   void saveLayer(const SkRect* bounds,
@@ -121,7 +112,7 @@
                      const SkRect& dst,
                      DlImageSampling sampling,
                      bool render_with_attributes,
-                     SkCanvas::SrcRectConstraint constraint) override {}
+                     SrcRectConstraint constraint) override {}
   void drawImageNine(const sk_sp<DlImage> image,
                      const SkIRect& center,
                      const SkRect& dst,
@@ -136,7 +127,8 @@
                  DlImageSampling sampling,
                  const SkRect* cull_rect,
                  bool render_with_attributes) override {}
-  void drawDisplayList(const sk_sp<DisplayList> display_list) override {}
+  void drawDisplayList(const sk_sp<DisplayList> display_list,
+                       SkScalar opacity) override {}
   void drawTextBlob(const sk_sp<SkTextBlob> blob,
                     SkScalar x,
                     SkScalar y) override {}
@@ -147,77 +139,6 @@
                   SkScalar dpr) override {}
 };
 
-// A utility class that will monitor the Dispatcher methods relating
-// to the rendering attributes and accumulate them into an SkPaint
-// which can be accessed at any time via paint().
-class SkPaintDispatchHelper : public virtual Dispatcher {
- public:
-  SkPaintDispatchHelper(SkScalar opacity = SK_Scalar1)
-      : current_color_(SK_ColorBLACK), opacity_(opacity) {
-    if (opacity < SK_Scalar1) {
-      paint_.setAlphaf(opacity);
-    }
-  }
-
-  void setAntiAlias(bool aa) override;
-  void setDither(bool dither) override;
-  void setStyle(DlDrawStyle style) override;
-  void setColor(DlColor color) override;
-  void setStrokeWidth(SkScalar width) override;
-  void setStrokeMiter(SkScalar limit) override;
-  void setStrokeCap(DlStrokeCap cap) override;
-  void setStrokeJoin(DlStrokeJoin join) override;
-  void setColorSource(const DlColorSource* source) override;
-  void setColorFilter(const DlColorFilter* filter) override;
-  void setInvertColors(bool invert) override;
-  void setBlendMode(DlBlendMode mode) override;
-  void setPathEffect(const DlPathEffect* effect) override;
-  void setMaskFilter(const DlMaskFilter* filter) override;
-  void setImageFilter(const DlImageFilter* filter) override;
-
-  const SkPaint& paint() { return paint_; }
-
-  /// Returns the current opacity attribute which is used to reduce
-  /// the alpha of all setColor calls encountered in the streeam
-  SkScalar opacity() { return opacity_; }
-  /// Returns the combined opacity that includes both the current
-  /// opacity attribute and the alpha of the most recent color.
-  /// The most recently set color will have combined the two and
-  /// stored the combined value in the alpha of the paint.
-  SkScalar combined_opacity() { return paint_.getAlphaf(); }
-  /// Returns true iff the current opacity attribute is not opaque,
-  /// irrespective of the alpha of the current color
-  bool has_opacity() { return opacity_ < SK_Scalar1; }
-
- protected:
-  void save_opacity(SkScalar opacity_for_children);
-  void restore_opacity();
-
- private:
-  SkPaint paint_;
-  bool invert_colors_ = false;
-  std::shared_ptr<const DlColorFilter> color_filter_;
-
-  sk_sp<SkColorFilter> makeColorFilter() const;
-
-  struct SaveInfo {
-    SaveInfo(SkScalar opacity) : opacity(opacity) {}
-
-    SkScalar opacity;
-  };
-  std::vector<SaveInfo> save_stack_;
-
-  void set_opacity(SkScalar opacity) {
-    if (opacity_ != opacity) {
-      opacity_ = opacity;
-      setColor(current_color_);
-    }
-  }
-
-  SkColor current_color_;
-  SkScalar opacity_;
-};
-
 enum class BoundsAccumulatorType {
   kRect,
   kRTree,
diff --git a/display_list/display_list_vertices.cc b/display_list/display_list_vertices.cc
index 6c20c3a..e6a9b72 100644
--- a/display_list/display_list_vertices.cc
+++ b/display_list/display_list_vertices.cc
@@ -176,13 +176,6 @@
   FML_DCHECK((index_count_ != 0) == (indices() != nullptr));
 }
 
-sk_sp<SkVertices> DlVertices::skia_object() const {
-  const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors());
-  return SkVertices::MakeCopy(ToSk(mode_), vertex_count_, vertices(),
-                              texture_coordinates(), sk_colors, index_count_,
-                              indices());
-}
-
 bool DlVertices::operator==(DlVertices const& other) const {
   auto lists_equal = [](auto* a, auto* b, int count) {
     if (a == nullptr || b == nullptr) {
diff --git a/display_list/display_list_vertices.h b/display_list/display_list_vertices.h
index 885ef6c..04044ba 100644
--- a/display_list/display_list_vertices.h
+++ b/display_list/display_list_vertices.h
@@ -30,14 +30,6 @@
   kTriangleFan,
 };
 
-inline SkVertices::VertexMode ToSk(DlVertexMode dl_mode) {
-  return static_cast<SkVertices::VertexMode>(dl_mode);
-}
-
-inline DlVertexMode ToDl(SkVertices::VertexMode sk_mode) {
-  return static_cast<DlVertexMode>(sk_mode);
-}
-
 //------------------------------------------------------------------------------
 /// @brief      Holds all of the data (both required and optional) for a
 ///             DisplayList drawVertices call.
@@ -233,9 +225,6 @@
     return static_cast<const uint16_t*>(pod(indices_offset_));
   }
 
-  // Returns an equivalent sk_sp<SkVertices> analog to this object.
-  sk_sp<SkVertices> skia_object() const;
-
   bool operator==(DlVertices const& other) const;
 
   bool operator!=(DlVertices const& other) const { return !(*this == other); }
diff --git a/display_list/display_list_vertices_unittests.cc b/display_list/display_list_vertices_unittests.cc
index 0323b62..5a11dc2e 100644
--- a/display_list/display_list_vertices_unittests.cc
+++ b/display_list/display_list_vertices_unittests.cc
@@ -22,7 +22,6 @@
   EXPECT_EQ(vertices1->colors(), nullptr);
   EXPECT_EQ(vertices1->index_count(), 0);
   EXPECT_EQ(vertices1->indices(), nullptr);
-  EXPECT_NE(vertices1->skia_object(), nullptr);
 
   std::shared_ptr<const DlVertices> vertices2 = DlVertices::Make(
       DlVertexMode::kTriangles, -1, nullptr, nullptr, nullptr, -1, nullptr);
@@ -33,7 +32,6 @@
   EXPECT_EQ(vertices2->colors(), nullptr);
   EXPECT_EQ(vertices2->index_count(), 0);
   EXPECT_EQ(vertices2->indices(), nullptr);
-  EXPECT_NE(vertices2->skia_object(), nullptr);
 
   TestEquals(*vertices1, *vertices2);
 }
@@ -425,7 +423,6 @@
   EXPECT_EQ(vertices1->colors(), nullptr);
   EXPECT_EQ(vertices1->index_count(), 0);
   EXPECT_EQ(vertices1->indices(), nullptr);
-  EXPECT_NE(vertices1->skia_object(), nullptr);
 
   Builder builder2(DlVertexMode::kTriangles, -1, Builder::kNone, -1);
   EXPECT_TRUE(builder2.is_valid());
@@ -437,7 +434,6 @@
   EXPECT_EQ(vertices2->colors(), nullptr);
   EXPECT_EQ(vertices2->index_count(), 0);
   EXPECT_EQ(vertices2->indices(), nullptr);
-  EXPECT_NE(vertices2->skia_object(), nullptr);
 
   TestEquals(*vertices1, *vertices2);
 }
diff --git a/display_list/dl_canvas.cc b/display_list/dl_canvas.cc
new file mode 100644
index 0000000..7f9e3fc
--- /dev/null
+++ b/display_list/dl_canvas.cc
@@ -0,0 +1,23 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/display_list/dl_canvas.h"
+
+#include "third_party/skia/include/utils/SkShadowUtils.h"
+
+namespace flutter {
+
+SkRect DlCanvas::ComputeShadowBounds(const SkPath& path,
+                                     float elevation,
+                                     SkScalar dpr,
+                                     const SkMatrix& ctm) {
+  SkRect shadow_bounds(path.getBounds());
+  SkShadowUtils::GetLocalBounds(
+      ctm, path, SkPoint3::Make(0, 0, dpr * elevation),
+      SkPoint3::Make(0, -1, 1), kShadowLightRadius / kShadowLightHeight,
+      SkShadowFlags::kDirectionalLight_ShadowFlag, &shadow_bounds);
+  return shadow_bounds;
+}
+
+}  // namespace flutter
diff --git a/display_list/dl_canvas.h b/display_list/dl_canvas.h
index 5d8d97e..7d94352c 100644
--- a/display_list/dl_canvas.h
+++ b/display_list/dl_canvas.h
@@ -35,6 +35,11 @@
     kPolygon,  //!< draw each pair of overlapping points as a line segment
   };
 
+  enum class SrcRectConstraint {
+    kStrict,
+    kFast,
+  };
+
   virtual ~DlCanvas() = default;
 
   virtual SkISize GetBaseLayerSize() const = 0;
@@ -146,28 +151,29 @@
                          const SkPoint point,
                          DlImageSampling sampling,
                          const DlPaint* paint = nullptr) = 0;
-  virtual void DrawImageRect(const sk_sp<DlImage>& image,
-                             const SkRect& src,
-                             const SkRect& dst,
-                             DlImageSampling sampling,
-                             const DlPaint* paint = nullptr,
-                             bool enforce_src_edges = false) = 0;
-  virtual void DrawImageRect(const sk_sp<DlImage>& image,
-                             const SkIRect& src,
-                             const SkRect& dst,
-                             DlImageSampling sampling,
-                             const DlPaint* paint = nullptr,
-                             bool enforce_src_edges = false) {
-    DrawImageRect(image, SkRect::Make(src), dst, sampling, paint,
-                  enforce_src_edges);
+  virtual void DrawImageRect(
+      const sk_sp<DlImage>& image,
+      const SkRect& src,
+      const SkRect& dst,
+      DlImageSampling sampling,
+      const DlPaint* paint = nullptr,
+      SrcRectConstraint constraint = SrcRectConstraint::kFast) = 0;
+  virtual void DrawImageRect(
+      const sk_sp<DlImage>& image,
+      const SkIRect& src,
+      const SkRect& dst,
+      DlImageSampling sampling,
+      const DlPaint* paint = nullptr,
+      SrcRectConstraint constraint = SrcRectConstraint::kFast) {
+    DrawImageRect(image, SkRect::Make(src), dst, sampling, paint, constraint);
   }
-  virtual void DrawImageRect(const sk_sp<DlImage>& image,
-                             const SkRect& dst,
-                             DlImageSampling sampling,
-                             const DlPaint* paint = nullptr,
-                             bool enforce_src_edges = false) {
-    DrawImageRect(image, image->bounds(), dst, sampling, paint,
-                  enforce_src_edges);
+  virtual void DrawImageRect(
+      const sk_sp<DlImage>& image,
+      const SkRect& dst,
+      DlImageSampling sampling,
+      const DlPaint* paint = nullptr,
+      SrcRectConstraint constraint = SrcRectConstraint::kFast) {
+    DrawImageRect(image, image->bounds(), dst, sampling, paint, constraint);
   }
   virtual void DrawImageNine(const sk_sp<DlImage>& image,
                              const SkIRect& center,
@@ -196,6 +202,14 @@
                           SkScalar dpr) = 0;
 
   virtual void Flush() = 0;
+
+  static constexpr SkScalar kShadowLightHeight = 600;
+  static constexpr SkScalar kShadowLightRadius = 800;
+
+  static SkRect ComputeShadowBounds(const SkPath& path,
+                                    float elevation,
+                                    SkScalar dpr,
+                                    const SkMatrix& ctm);
 };
 
 class DlAutoCanvasRestore {
diff --git a/display_list/display_list_dispatcher.cc b/display_list/dl_op_receiver.cc
similarity index 64%
rename from display_list/display_list_dispatcher.cc
rename to display_list/dl_op_receiver.cc
index e321f02..b1b6c01 100644
--- a/display_list/display_list_dispatcher.cc
+++ b/display_list/dl_op_receiver.cc
@@ -2,10 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "flutter/display_list/display_list_dispatcher.h"
+#include "flutter/display_list/dl_op_receiver.h"
 
 namespace flutter {
 
-//
+// Just exists to ensure that the header can be cleanly imported.
 
 }  // namespace flutter
diff --git a/display_list/display_list_dispatcher.h b/display_list/dl_op_receiver.h
similarity index 96%
rename from display_list/display_list_dispatcher.h
rename to display_list/dl_op_receiver.h
index 00dc966..0f406f9 100644
--- a/display_list/display_list_dispatcher.h
+++ b/display_list/dl_op_receiver.h
@@ -28,10 +28,11 @@
 ///             through the DisplayListBuilder and also the methods that will be
 ///             invoked through the DisplayList::dispatch() method.
 ///
-class Dispatcher {
+class DlOpReceiver {
  protected:
   using ClipOp = DlCanvas::ClipOp;
   using PointMode = DlCanvas::PointMode;
+  using SrcRectConstraint = DlCanvas::SrcRectConstraint;
 
  public:
   // MaxDrawPointsCount * sizeof(SkPoint) must be less than 1 << 32
@@ -219,12 +220,13 @@
                          const SkPoint point,
                          DlImageSampling sampling,
                          bool render_with_attributes) = 0;
-  virtual void drawImageRect(const sk_sp<DlImage> image,
-                             const SkRect& src,
-                             const SkRect& dst,
-                             DlImageSampling sampling,
-                             bool render_with_attributes,
-                             SkCanvas::SrcRectConstraint constraint) = 0;
+  virtual void drawImageRect(
+      const sk_sp<DlImage> image,
+      const SkRect& src,
+      const SkRect& dst,
+      DlImageSampling sampling,
+      bool render_with_attributes,
+      SrcRectConstraint constraint = SrcRectConstraint::kFast) = 0;
   virtual void drawImageNine(const sk_sp<DlImage> image,
                              const SkIRect& center,
                              const SkRect& dst,
@@ -239,7 +241,8 @@
                          DlImageSampling sampling,
                          const SkRect* cull_rect,
                          bool render_with_attributes) = 0;
-  virtual void drawDisplayList(const sk_sp<DisplayList> display_list) = 0;
+  virtual void drawDisplayList(const sk_sp<DisplayList> display_list,
+                               SkScalar opacity = SK_Scalar1) = 0;
   virtual void drawTextBlob(const sk_sp<SkTextBlob> blob,
                             SkScalar x,
                             SkScalar y) = 0;
diff --git a/display_list/display_list_canvas_unittests.cc b/display_list/dl_rendering_unittests.cc
similarity index 85%
rename from display_list/display_list_canvas_unittests.cc
rename to display_list/dl_rendering_unittests.cc
index a6aed32..4f40d1f 100644
--- a/display_list/display_list_canvas_unittests.cc
+++ b/display_list/dl_rendering_unittests.cc
@@ -6,15 +6,16 @@
 
 #include "flutter/display_list/display_list.h"
 #include "flutter/display_list/display_list_builder.h"
-#include "flutter/display_list/display_list_canvas_dispatcher.h"
 #include "flutter/display_list/display_list_comparable.h"
 #include "flutter/display_list/display_list_flags.h"
 #include "flutter/display_list/display_list_sampling_options.h"
 #include "flutter/display_list/skia/dl_sk_canvas.h"
+#include "flutter/display_list/skia/dl_sk_dispatcher.h"
 #include "flutter/display_list/testing/dl_test_surface_provider.h"
 #include "flutter/fml/math.h"
 #include "flutter/testing/display_list_testing.h"
 #include "flutter/testing/testing.h"
+#include "third_party/skia/include/core/SkColorFilter.h"
 #include "third_party/skia/include/core/SkPictureRecorder.h"
 #include "third_party/skia/include/core/SkSurface.h"
 
@@ -135,6 +136,18 @@
     (sizeof(kHorizontalMiterDiamondPoints) /
      sizeof(kHorizontalMiterDiamondPoints[0]));
 
+class SkImageSampling {
+ public:
+  static constexpr SkSamplingOptions kNearestNeighbor =
+      SkSamplingOptions(SkFilterMode::kNearest);
+  static constexpr SkSamplingOptions kLinear =
+      SkSamplingOptions(SkFilterMode::kLinear);
+  static constexpr SkSamplingOptions kMipmapLinear =
+      SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
+  static constexpr SkSamplingOptions kCubic =
+      SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f});
+};
+
 // A class to specify how much tolerance to allow in bounds estimates.
 // For some attributes, the machinery must make some conservative
 // assumptions as to the extent of the bounds, but some of our test
@@ -409,7 +422,7 @@
       : display_list_(std::move(display_list)) {}
 
   void Render(SkCanvas* canvas, const RenderJobInfo& info) {
-    display_list_->RenderTo(canvas, info.opacity);
+    DlSkCanvasAdapter(canvas).DrawDisplayList(display_list_, info.opacity);
   }
 
  private:
@@ -998,12 +1011,12 @@
           RenderEnvironment::MakeN32(env.provider());
       SkSetup sk_backdrop_setup = [=](SkCanvas* cv, SkPaint& p) {
         SkPaint setup_p;
-        setup_p.setShader(kTestImageColorSource.skia_object());
+        setup_p.setShader(kTestSkImageColorSource);
         cv->drawPaint(setup_p);
       };
       DlSetup dl_backdrop_setup = [=](DlCanvas* cv, DlPaint& p) {
         DlPaint setup_p;
-        setup_p.setColorSource(&kTestImageColorSource);
+        setup_p.setColorSource(&kTestDlImageColorSource);
         cv->DrawPaint(setup_p);
       };
       SkSetup sk_content_setup = [=](SkCanvas* cv, SkPaint& p) {
@@ -1016,38 +1029,39 @@
                             dl_backdrop_setup, testP.dl_renderer());
       quickCompareToReference(backdrop_env, "backdrop");
 
-      DlBlurImageFilter backdrop(5, 5, DlTileMode::kDecal);
+      DlBlurImageFilter dl_backdrop(5, 5, DlTileMode::kDecal);
+      auto sk_backdrop =
+          SkImageFilters::Blur(5, 5, SkTileMode::kDecal, nullptr);
       RenderWith(testP, backdrop_env, tolerance,
                  CaseParameters(
                      "saveLayer with backdrop",
                      [=](SkCanvas* cv, SkPaint& p) {
                        sk_backdrop_setup(cv, p);
                        cv->saveLayer(SkCanvas::SaveLayerRec(
-                           nullptr, nullptr, backdrop.skia_object().get(), 0));
+                           nullptr, nullptr, sk_backdrop.get(), 0));
                        sk_content_setup(cv, p);
                      },
                      [=](DlCanvas* cv, DlPaint& p) {
                        dl_backdrop_setup(cv, p);
-                       cv->SaveLayer(nullptr, nullptr, &backdrop);
+                       cv->SaveLayer(nullptr, nullptr, &dl_backdrop);
                        dl_content_setup(cv, p);
                      })
                      .with_restore(sk_safe_restore, dl_safe_restore, true));
-      RenderWith(
-          testP, backdrop_env, tolerance,
-          CaseParameters(
-              "saveLayer with bounds and backdrop",
-              [=](SkCanvas* cv, SkPaint& p) {
-                sk_backdrop_setup(cv, p);
-                cv->saveLayer(SkCanvas::SaveLayerRec(
-                    &layer_bounds, nullptr, backdrop.skia_object().get(), 0));
-                sk_content_setup(cv, p);
-              },
-              [=](DlCanvas* cv, DlPaint& p) {
-                dl_backdrop_setup(cv, p);
-                cv->SaveLayer(&layer_bounds, nullptr, &backdrop);
-                dl_content_setup(cv, p);
-              })
-              .with_restore(sk_safe_restore, dl_safe_restore, true));
+      RenderWith(testP, backdrop_env, tolerance,
+                 CaseParameters(
+                     "saveLayer with bounds and backdrop",
+                     [=](SkCanvas* cv, SkPaint& p) {
+                       sk_backdrop_setup(cv, p);
+                       cv->saveLayer(SkCanvas::SaveLayerRec(
+                           &layer_bounds, nullptr, sk_backdrop.get(), 0));
+                       sk_content_setup(cv, p);
+                     },
+                     [=](DlCanvas* cv, DlPaint& p) {
+                       dl_backdrop_setup(cv, p);
+                       cv->SaveLayer(&layer_bounds, nullptr, &dl_backdrop);
+                       dl_content_setup(cv, p);
+                     })
+                     .with_restore(sk_safe_restore, dl_safe_restore, true));
       RenderWith(testP, backdrop_env, tolerance,
                  CaseParameters(
                      "clipped saveLayer with backdrop",
@@ -1055,13 +1069,13 @@
                        sk_backdrop_setup(cv, p);
                        cv->clipRect(layer_bounds);
                        cv->saveLayer(SkCanvas::SaveLayerRec(
-                           nullptr, nullptr, backdrop.skia_object().get(), 0));
+                           nullptr, nullptr, sk_backdrop.get(), 0));
                        sk_content_setup(cv, p);
                      },
                      [=](DlCanvas* cv, DlPaint& p) {
                        dl_backdrop_setup(cv, p);
                        cv->ClipRect(layer_bounds);
-                       cv->SaveLayer(nullptr, nullptr, &backdrop);
+                       cv->SaveLayer(nullptr, nullptr, &dl_backdrop);
                        dl_content_setup(cv, p);
                      })
                      .with_restore(sk_safe_restore, dl_safe_restore, true));
@@ -1076,20 +1090,22 @@
           0, 0, 0, 0.5, 0,
       };
       // clang-format on
-      DlMatrixColorFilter filter(rotate_alpha_color_matrix);
+      DlMatrixColorFilter dl_alpha_rotate_filter(rotate_alpha_color_matrix);
+      auto sk_alpha_rotate_filter =
+          SkColorFilters::Matrix(rotate_alpha_color_matrix);
       {
         RenderWith(testP, env, tolerance,
                    CaseParameters(
                        "saveLayer ColorFilter, no bounds",
                        [=](SkCanvas* cv, SkPaint& p) {
                          SkPaint save_p;
-                         save_p.setColorFilter(filter.skia_object());
+                         save_p.setColorFilter(sk_alpha_rotate_filter);
                          cv->saveLayer(nullptr, &save_p);
                          p.setStrokeWidth(5.0);
                        },
                        [=](DlCanvas* cv, DlPaint& p) {
                          DlPaint save_p;
-                         save_p.setColorFilter(&filter);
+                         save_p.setColorFilter(&dl_alpha_rotate_filter);
                          cv->SaveLayer(nullptr, &save_p);
                          p.setStrokeWidth(5.0);
                        })
@@ -1101,13 +1117,13 @@
                        "saveLayer ColorFilter and bounds",
                        [=](SkCanvas* cv, SkPaint& p) {
                          SkPaint save_p;
-                         save_p.setColorFilter(filter.skia_object());
+                         save_p.setColorFilter(sk_alpha_rotate_filter);
                          cv->saveLayer(kRenderBounds, &save_p);
                          p.setStrokeWidth(5.0);
                        },
                        [=](DlCanvas* cv, DlPaint& p) {
                          DlPaint save_p;
-                         save_p.setColorFilter(&filter);
+                         save_p.setColorFilter(&dl_alpha_rotate_filter);
                          cv->SaveLayer(&kRenderBounds, &save_p);
                          p.setStrokeWidth(5.0);
                        })
@@ -1124,21 +1140,23 @@
           0, 0, 0, 1, 0,
       };
       // clang-format on
-      DlMatrixColorFilter color_filter(color_matrix);
-      DlColorFilterImageFilter filter(color_filter);
+      DlMatrixColorFilter dl_color_filter(color_matrix);
+      DlColorFilterImageFilter dl_cf_image_filter(dl_color_filter);
+      auto sk_cf_image_filter = SkImageFilters::ColorFilter(
+          SkColorFilters::Matrix(color_matrix), nullptr);
       {
         RenderWith(testP, env, tolerance,
                    CaseParameters(
                        "saveLayer ImageFilter, no bounds",
                        [=](SkCanvas* cv, SkPaint& p) {
                          SkPaint save_p;
-                         save_p.setImageFilter(filter.skia_object());
+                         save_p.setImageFilter(sk_cf_image_filter);
                          cv->saveLayer(nullptr, &save_p);
                          p.setStrokeWidth(5.0);
                        },
                        [=](DlCanvas* cv, DlPaint& p) {
                          DlPaint save_p;
-                         save_p.setImageFilter(&filter);
+                         save_p.setImageFilter(&dl_cf_image_filter);
                          cv->SaveLayer(nullptr, &save_p);
                          p.setStrokeWidth(5.0);
                        })
@@ -1150,13 +1168,13 @@
                        "saveLayer ImageFilter and bounds",
                        [=](SkCanvas* cv, SkPaint& p) {
                          SkPaint save_p;
-                         save_p.setImageFilter(filter.skia_object());
+                         save_p.setImageFilter(sk_cf_image_filter);
                          cv->saveLayer(kRenderBounds, &save_p);
                          p.setStrokeWidth(5.0);
                        },
                        [=](DlCanvas* cv, DlPaint& p) {
                          DlPaint save_p;
-                         save_p.setImageFilter(&filter);
+                         save_p.setImageFilter(&dl_cf_image_filter);
                          cv->SaveLayer(&kRenderBounds, &save_p);
                          p.setStrokeWidth(5.0);
                        })
@@ -1225,12 +1243,12 @@
       } else {
         DlColor dither_bg = DlColor::kBlack();
         SkSetup sk_dither_setup = [=](SkCanvas*, SkPaint& p) {
-          p.setShader(kTestImageColorSource.skia_object());
+          p.setShader(kTestSkImageColorSource);
           p.setAlpha(0xf0);
           p.setStrokeWidth(5.0);
         };
         DlSetup dl_dither_setup = [=](DlCanvas*, DlPaint& p) {
-          p.setColorSource(&kTestImageColorSource);
+          p.setColorSource(&kTestDlImageColorSource);
           p.setAlpha(0xf0);
           p.setStrokeWidth(5.0);
         };
@@ -1316,17 +1334,19 @@
       // (for drawPaint) so we create a new environment for these tests.
       RenderEnvironment blur_env = RenderEnvironment::MakeN32(env.provider());
       SkSetup sk_blur_setup = [=](SkCanvas*, SkPaint& p) {
-        p.setShader(kTestImageColorSource.skia_object());
+        p.setShader(kTestSkImageColorSource);
         p.setStrokeWidth(5.0);
       };
       DlSetup dl_blur_setup = [=](DlCanvas*, DlPaint& p) {
-        p.setColorSource(&kTestImageColorSource);
+        p.setColorSource(&kTestDlImageColorSource);
         p.setStrokeWidth(5.0);
       };
       blur_env.init_ref(sk_blur_setup, testP.sk_renderer(),  //
                         dl_blur_setup, testP.dl_renderer());
       quickCompareToReference(blur_env, "blur");
-      DlBlurImageFilter filter_decal_5(5.0, 5.0, DlTileMode::kDecal);
+      DlBlurImageFilter dl_filter_decal_5(5.0, 5.0, DlTileMode::kDecal);
+      auto sk_filter_decal_5 =
+          SkImageFilters::Blur(5.0, 5.0, SkTileMode::kDecal, nullptr);
       BoundsTolerance blur_5_tolerance = tolerance.addBoundsPadding(4, 4);
       {
         RenderWith(testP, blur_env, blur_5_tolerance,
@@ -1334,25 +1354,27 @@
                        "ImageFilter == Decal Blur 5",
                        [=](SkCanvas* cv, SkPaint& p) {
                          sk_blur_setup(cv, p);
-                         p.setImageFilter(filter_decal_5.skia_object());
+                         p.setImageFilter(sk_filter_decal_5);
                        },
                        [=](DlCanvas* cv, DlPaint& p) {
                          dl_blur_setup(cv, p);
-                         p.setImageFilter(&filter_decal_5);
+                         p.setImageFilter(&dl_filter_decal_5);
                        }));
       }
-      DlBlurImageFilter filter_clamp_5(5.0, 5.0, DlTileMode::kClamp);
+      DlBlurImageFilter dl_filter_clamp_5(5.0, 5.0, DlTileMode::kClamp);
+      auto sk_filter_clamp_5 =
+          SkImageFilters::Blur(5.0, 5.0, SkTileMode::kClamp, nullptr);
       {
         RenderWith(testP, blur_env, blur_5_tolerance,
                    CaseParameters(
                        "ImageFilter == Clamp Blur 5",
                        [=](SkCanvas* cv, SkPaint& p) {
                          sk_blur_setup(cv, p);
-                         p.setImageFilter(filter_clamp_5.skia_object());
+                         p.setImageFilter(sk_filter_clamp_5);
                        },
                        [=](DlCanvas* cv, DlPaint& p) {
                          dl_blur_setup(cv, p);
-                         p.setImageFilter(&filter_clamp_5);
+                         p.setImageFilter(&dl_filter_clamp_5);
                        }));
       }
     }
@@ -1363,27 +1385,28 @@
       // (for drawPaint) so we create a new environment for these tests.
       RenderEnvironment dilate_env = RenderEnvironment::MakeN32(env.provider());
       SkSetup sk_dilate_setup = [=](SkCanvas*, SkPaint& p) {
-        p.setShader(kTestImageColorSource.skia_object());
+        p.setShader(kTestSkImageColorSource);
         p.setStrokeWidth(5.0);
       };
       DlSetup dl_dilate_setup = [=](DlCanvas*, DlPaint& p) {
-        p.setColorSource(&kTestImageColorSource);
+        p.setColorSource(&kTestDlImageColorSource);
         p.setStrokeWidth(5.0);
       };
       dilate_env.init_ref(sk_dilate_setup, testP.sk_renderer(),  //
                           dl_dilate_setup, testP.dl_renderer());
       quickCompareToReference(dilate_env, "dilate");
-      DlDilateImageFilter filter_5(5.0, 5.0);
+      DlDilateImageFilter dl_dilate_filter_5(5.0, 5.0);
+      auto sk_dilate_filter_5 = SkImageFilters::Dilate(5.0, 5.0, nullptr);
       RenderWith(testP, dilate_env, tolerance,
                  CaseParameters(
                      "ImageFilter == Dilate 5",
                      [=](SkCanvas* cv, SkPaint& p) {
                        sk_dilate_setup(cv, p);
-                       p.setImageFilter(filter_5.skia_object());
+                       p.setImageFilter(sk_dilate_filter_5);
                      },
                      [=](DlCanvas* cv, DlPaint& p) {
                        dl_dilate_setup(cv, p);
-                       p.setImageFilter(&filter_5);
+                       p.setImageFilter(&dl_dilate_filter_5);
                      }));
     }
 
@@ -1393,11 +1416,11 @@
       // (for drawPaint) so we create a new environment for these tests.
       RenderEnvironment erode_env = RenderEnvironment::MakeN32(env.provider());
       SkSetup sk_erode_setup = [=](SkCanvas*, SkPaint& p) {
-        p.setShader(kTestImageColorSource.skia_object());
+        p.setShader(kTestSkImageColorSource);
         p.setStrokeWidth(6.0);
       };
       DlSetup dl_erode_setup = [=](DlCanvas*, DlPaint& p) {
-        p.setColorSource(&kTestImageColorSource);
+        p.setColorSource(&kTestDlImageColorSource);
         p.setStrokeWidth(6.0);
       };
       erode_env.init_ref(sk_erode_setup, testP.sk_renderer(),  //
@@ -1405,17 +1428,18 @@
       quickCompareToReference(erode_env, "erode");
       // do not erode too much, because some tests assert there are enough
       // pixels that are changed.
-      DlErodeImageFilter filter_1(1.0, 1.0);
+      DlErodeImageFilter dl_erode_filter_1(1.0, 1.0);
+      auto sk_erode_filter_1 = SkImageFilters::Erode(1.0, 1.0, nullptr);
       RenderWith(testP, erode_env, tolerance,
                  CaseParameters(
                      "ImageFilter == Erode 1",
                      [=](SkCanvas* cv, SkPaint& p) {
                        sk_erode_setup(cv, p);
-                       p.setImageFilter(filter_1.skia_object());
+                       p.setImageFilter(sk_erode_filter_1);
                      },
                      [=](DlCanvas* cv, DlPaint& p) {
                        dl_erode_setup(cv, p);
-                       p.setImageFilter(&filter_1);
+                       p.setImageFilter(&dl_erode_filter_1);
                      }));
     }
 
@@ -1434,7 +1458,8 @@
          1.0,  1.0,  1.0, 1.0,   0,
       };
       // clang-format on
-      DlMatrixColorFilter filter(rotate_color_matrix);
+      DlMatrixColorFilter dl_color_filter(rotate_color_matrix);
+      auto sk_color_filter = SkColorFilters::Matrix(rotate_color_matrix);
       {
         DlColor bg = DlColor::kWhite();
         RenderWith(testP, env, tolerance,
@@ -1442,34 +1467,35 @@
                        "ColorFilter == RotateRGB",
                        [=](SkCanvas*, SkPaint& p) {
                          p.setColor(DlColor::kYellow());
-                         p.setColorFilter(filter.skia_object());
+                         p.setColorFilter(sk_color_filter);
                        },
                        [=](DlCanvas*, DlPaint& p) {
                          p.setColor(DlColor::kYellow());
-                         p.setColorFilter(&filter);
+                         p.setColorFilter(&dl_color_filter);
                        })
                        .with_bg(bg));
       }
-      filter = DlMatrixColorFilter(invert_color_matrix);
       {
         DlColor bg = DlColor::kWhite();
-        RenderWith(testP, env, tolerance,
-                   CaseParameters(
-                       "ColorFilter == Invert",
-                       [=](SkCanvas*, SkPaint& p) {
-                         p.setColor(DlColor::kYellow());
-                         p.setColorFilter(filter.skia_object());
-                       },
-                       [=](DlCanvas*, DlPaint& p) {
-                         p.setColor(DlColor::kYellow());
-                         p.setInvertColors(true);
-                       })
-                       .with_bg(bg));
+        RenderWith(
+            testP, env, tolerance,
+            CaseParameters(
+                "ColorFilter == Invert",
+                [=](SkCanvas*, SkPaint& p) {
+                  p.setColor(DlColor::kYellow());
+                  p.setColorFilter(SkColorFilters::Matrix(invert_color_matrix));
+                },
+                [=](DlCanvas*, DlPaint& p) {
+                  p.setColor(DlColor::kYellow());
+                  p.setInvertColors(true);
+                })
+                .with_bg(bg));
       }
     }
 
     {
-      const DlBlurMaskFilter filter(kNormal_SkBlurStyle, 5.0);
+      const DlBlurMaskFilter dl_mask_filter(kNormal_SkBlurStyle, 5.0);
+      auto sk_mask_filter = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, 5.0);
       BoundsTolerance blur_5_tolerance = tolerance.addBoundsPadding(4, 4);
       {
         // Stroked primitives need some non-trivial stroke size to be blurred
@@ -1478,11 +1504,11 @@
                        "MaskFilter == Blur 5",
                        [=](SkCanvas*, SkPaint& p) {
                          p.setStrokeWidth(5.0);
-                         p.setMaskFilter(filter.skia_object());
+                         p.setMaskFilter(sk_mask_filter);
                        },
                        [=](DlCanvas*, DlPaint& p) {
                          p.setStrokeWidth(5.0);
-                         p.setMaskFilter(&filter);
+                         p.setMaskFilter(&dl_mask_filter);
                        }));
       }
     }
@@ -1492,27 +1518,33 @@
           SkPoint::Make(kRenderBounds.fLeft, kRenderBounds.fTop),
           SkPoint::Make(kRenderBounds.fRight, kRenderBounds.fBottom),
       };
-      DlColor colors[] = {
+      DlColor dl_colors[] = {
           DlColor::kGreen(),
           DlColor::kYellow().withAlpha(0x7f),
           DlColor::kBlue(),
       };
+      SkColor sk_colors[] = {
+          SK_ColorGREEN,
+          SkColorSetA(SK_ColorYELLOW, 0x7f),
+          SK_ColorBLUE,
+      };
       float stops[] = {
           0.0,
           0.5,
           1.0,
       };
-      std::shared_ptr<DlColorSource> source = DlColorSource::MakeLinear(
-          end_points[0], end_points[1], 3, colors, stops, DlTileMode::kMirror);
+      auto dl_gradient =
+          DlColorSource::MakeLinear(end_points[0], end_points[1], 3, dl_colors,
+                                    stops, DlTileMode::kMirror);
+      auto sk_gradient = SkGradientShader::MakeLinear(
+          end_points, sk_colors, stops, 3, SkTileMode::kMirror, 0, nullptr);
       {
         RenderWith(
             testP, env, tolerance,
             CaseParameters(
                 "LinearGradient GYB",
-                [=](SkCanvas*, SkPaint& p) {
-                  p.setShader(source->skia_object());
-                },
-                [=](DlCanvas*, DlPaint& p) { p.setColorSource(source); }));
+                [=](SkCanvas*, SkPaint& p) { p.setShader(sk_gradient); },
+                [=](DlCanvas*, DlPaint& p) { p.setColorSource(dl_gradient); }));
       }
     }
   }
@@ -1697,7 +1729,8 @@
     {
       const SkScalar test_dashes_1[] = {29.0, 2.0};
       const SkScalar test_dashes_2[] = {17.0, 1.5};
-      auto effect = DlDashPathEffect::Make(test_dashes_1, 2, 0.0f);
+      auto dl_dash_effect = DlDashPathEffect::Make(test_dashes_1, 2, 0.0f);
+      auto sk_dash_effect = SkDashPathEffect::Make(test_dashes_1, 2, 0.0f);
       {
         RenderWith(testP, stroke_base_env, tolerance,
                    CaseParameters(
@@ -1705,12 +1738,12 @@
                        [=](SkCanvas*, SkPaint& p) {
                          // Provide some non-trivial stroke size to get dashed
                          p.setStrokeWidth(5.0);
-                         p.setPathEffect(effect->skia_object());
+                         p.setPathEffect(sk_dash_effect);
                        },
                        [=](DlCanvas*, DlPaint& p) {
                          // Provide some non-trivial stroke size to get dashed
                          p.setStrokeWidth(5.0);
-                         p.setPathEffect(effect);
+                         p.setPathEffect(dl_dash_effect);
                        }));
       }
       {
@@ -1722,17 +1755,18 @@
                          p.setStyle(SkPaint::kStroke_Style);
                          // Provide some non-trivial stroke size to get dashed
                          p.setStrokeWidth(5.0);
-                         p.setPathEffect(effect->skia_object());
+                         p.setPathEffect(sk_dash_effect);
                        },
                        [=](DlCanvas*, DlPaint& p) {
                          // Need stroke style to see dashing properly
                          p.setDrawStyle(DlDrawStyle::kStroke);
                          // Provide some non-trivial stroke size to get dashed
                          p.setStrokeWidth(5.0);
-                         p.setPathEffect(effect);
+                         p.setPathEffect(dl_dash_effect);
                        }));
       }
-      effect = DlDashPathEffect::Make(test_dashes_2, 2, 0.0f);
+      dl_dash_effect = DlDashPathEffect::Make(test_dashes_2, 2, 0.0f);
+      sk_dash_effect = SkDashPathEffect::Make(test_dashes_2, 2, 0.0f);
       {
         RenderWith(testP, stroke_base_env, tolerance,
                    CaseParameters(
@@ -1742,14 +1776,14 @@
                          p.setStyle(SkPaint::kStroke_Style);
                          // Provide some non-trivial stroke size to get dashed
                          p.setStrokeWidth(5.0);
-                         p.setPathEffect(effect->skia_object());
+                         p.setPathEffect(sk_dash_effect);
                        },
                        [=](DlCanvas*, DlPaint& p) {
                          // Need stroke style to see dashing properly
                          p.setDrawStyle(DlDrawStyle::kStroke);
                          // Provide some non-trivial stroke size to get dashed
                          p.setStrokeWidth(5.0);
-                         p.setPathEffect(effect);
+                         p.setPathEffect(dl_dash_effect);
                        }));
       }
     }
@@ -2178,6 +2212,23 @@
     ASSERT_GT(pixels_touched, 0) << info;
   }
 
+  static int countModifiedTransparentPixels(const RenderResult* ref_result,
+                                            const RenderResult* test_result) {
+    int count = 0;
+    for (int y = 0; y < kTestHeight; y++) {
+      const uint32_t* ref_row = ref_result->addr32(0, y);
+      const uint32_t* test_row = test_result->addr32(0, y);
+      for (int x = 0; x < kTestWidth; x++) {
+        if (ref_row[x] != test_row[x]) {
+          if (ref_row[x] == 0) {
+            count++;
+          }
+        }
+      }
+    }
+    return count;
+  }
+
   static void quickCompareToReference(const RenderEnvironment& env,
                                       const std::string& info) {
     quickCompareToReference(env.ref_sk_result(), env.ref_dl_result(), true,
@@ -2198,7 +2249,7 @@
       const uint32_t* test_row = test_result->addr32(0, y);
       for (int x = 0; x < w; x++) {
         if (ref_row[x] != test_row[x]) {
-          if (should_match) {
+          if (should_match && pixels_different < 5) {
             FML_LOG(ERROR) << std::hex << ref_row[x] << " != " << test_row[x];
           }
           pixels_different++;
@@ -2258,7 +2309,7 @@
         bool match = fuzzyCompares ? fuzzyCompare(test_row[x], ref_row[x], 1)
                                    : test_row[x] == ref_row[x];
         if (!match) {
-          if (printMismatches) {
+          if (printMismatches && pixels_different < 5) {
             FML_LOG(ERROR) << "pix[" << x << ", " << y
                            << "] mismatch: " << std::hex << test_row[x]
                            << "(test) != (ref)" << ref_row[x] << std::dec;
@@ -2347,7 +2398,8 @@
     return surface->makeImageSnapshot();
   }
 
-  static const DlImageColorSource kTestImageColorSource;
+  static const DlImageColorSource kTestDlImageColorSource;
+  static const sk_sp<SkShader> kTestSkImageColorSource;
 
   static sk_sp<SkTextBlob> MakeTextBlob(const std::string& string,
                                         SkScalar font_height) {
@@ -2365,11 +2417,15 @@
     BoundsTolerance().addAbsolutePadding(1, 1);
 
 const sk_sp<SkImage> CanvasCompareTester::kTestImage = makeTestImage();
-const DlImageColorSource CanvasCompareTester::kTestImageColorSource(
+const DlImageColorSource CanvasCompareTester::kTestDlImageColorSource(
     DlImage::Make(kTestImage),
     DlTileMode::kRepeat,
     DlTileMode::kRepeat,
     DlImageSampling::kLinear);
+const sk_sp<SkShader> CanvasCompareTester::kTestSkImageColorSource =
+    kTestImage->makeShader(SkTileMode::kRepeat,
+                           SkTileMode::kRepeat,
+                           SkImageSampling::kLinear);
 
 // Eventually this bare bones testing::Test fixture will subsume the
 // CanvasCompareTester and the TestParameters could then become just
@@ -2870,21 +2926,27 @@
       SkPoint::Make(kRenderLeft, kRenderCenterY),
       SkPoint::Make(kRenderRight, kRenderBottom),
   };
-  const DlColor colors[6] = {
+  const DlColor dl_colors[6] = {
+      DlColor::kRed(),  DlColor::kBlue(),   DlColor::kGreen(),
+      DlColor::kCyan(), DlColor::kYellow(), DlColor::kMagenta(),
+  };
+  const SkColor sk_colors[6] = {
       SK_ColorRED,  SK_ColorBLUE,   SK_ColorGREEN,
       SK_ColorCYAN, SK_ColorYELLOW, SK_ColorMAGENTA,
   };
-  const std::shared_ptr<DlVertices> vertices =
-      DlVertices::Make(DlVertexMode::kTriangles, 6, pts, nullptr, colors);
+  const std::shared_ptr<DlVertices> dl_vertices =
+      DlVertices::Make(DlVertexMode::kTriangles, 6, pts, nullptr, dl_colors);
+  const auto sk_vertices =
+      SkVertices::MakeCopy(SkVertices::VertexMode::kTriangles_VertexMode, 6,
+                           pts, nullptr, sk_colors);
 
   CanvasCompareTester::RenderAll(  //
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {  //
-            canvas->drawVertices(vertices->skia_object(), SkBlendMode::kSrcOver,
-                                 paint);
+            canvas->drawVertices(sk_vertices, SkBlendMode::kSrcOver, paint);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {  //
-            canvas->DrawVertices(vertices, DlBlendMode::kSrcOver, paint);
+            canvas->DrawVertices(dl_vertices, DlBlendMode::kSrcOver, paint);
           },
           kDrawVerticesFlags));
 }
@@ -2917,27 +2979,27 @@
       SkPoint::Make(0, 0),
       SkPoint::Make(kRenderWidth, 0),
   };
-  const std::shared_ptr<DlVertices> vertices =
+  const std::shared_ptr<DlVertices> dl_vertices =
       DlVertices::Make(DlVertexMode::kTriangles, 6, pts, tex, nullptr);
+  const auto sk_vertices = SkVertices::MakeCopy(
+      SkVertices::VertexMode::kTriangles_VertexMode, 6, pts, tex, nullptr);
 
   CanvasCompareTester::RenderAll(  //
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {  //
             SkPaint v_paint = paint;
             if (v_paint.getShader() == nullptr) {
-              v_paint.setShader(
-                  CanvasCompareTester::kTestImageColorSource.skia_object());
+              v_paint.setShader(CanvasCompareTester::kTestSkImageColorSource);
             }
-            canvas->drawVertices(vertices->skia_object(), SkBlendMode::kSrcOver,
-                                 v_paint);
+            canvas->drawVertices(sk_vertices, SkBlendMode::kSrcOver, v_paint);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {  //
             DlPaint v_paint = paint;
             if (v_paint.getColorSource() == nullptr) {
               v_paint.setColorSource(
-                  &CanvasCompareTester::kTestImageColorSource);
+                  &CanvasCompareTester::kTestDlImageColorSource);
             }
-            canvas->DrawVertices(vertices, DlBlendMode::kSrcOver, v_paint);
+            canvas->DrawVertices(dl_vertices, DlBlendMode::kSrcOver, v_paint);
           },
           kDrawVerticesFlags));
 }
@@ -2948,7 +3010,7 @@
           [=](SkCanvas* canvas, const SkPaint& paint) {         //
             canvas->drawImage(CanvasCompareTester::kTestImage,  //
                               kRenderLeft, kRenderTop,
-                              ToSk(DlImageSampling::kNearestNeighbor), &paint);
+                              SkImageSampling::kNearestNeighbor, &paint);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {
             canvas->DrawImage(DlImage::Make(CanvasCompareTester::kTestImage),
@@ -2964,7 +3026,7 @@
           [=](SkCanvas* canvas, const SkPaint& paint) {         //
             canvas->drawImage(CanvasCompareTester::kTestImage,  //
                               kRenderLeft, kRenderTop,
-                              ToSk(DlImageSampling::kNearestNeighbor), nullptr);
+                              SkImageSampling::kNearestNeighbor, nullptr);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {
             canvas->DrawImage(DlImage::Make(CanvasCompareTester::kTestImage),
@@ -2979,8 +3041,8 @@
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {         //
             canvas->drawImage(CanvasCompareTester::kTestImage,  //
-                              kRenderLeft, kRenderTop,
-                              ToSk(DlImageSampling::kLinear), &paint);
+                              kRenderLeft, kRenderTop, SkImageSampling::kLinear,
+                              &paint);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {
             canvas->DrawImage(DlImage::Make(CanvasCompareTester::kTestImage),
@@ -2997,13 +3059,14 @@
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {  //
             canvas->drawImageRect(CanvasCompareTester::kTestImage, src, dst,
-                                  ToSk(DlImageSampling::kNearestNeighbor),
-                                  &paint, SkCanvas::kFast_SrcRectConstraint);
+                                  SkImageSampling::kNearestNeighbor, &paint,
+                                  SkCanvas::kFast_SrcRectConstraint);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {  //
             canvas->DrawImageRect(
                 DlImage::Make(CanvasCompareTester::kTestImage), src, dst,
-                DlImageSampling::kNearestNeighbor, &paint, false);
+                DlImageSampling::kNearestNeighbor, &paint,
+                DlCanvas::SrcRectConstraint::kFast);
           },
           kDrawImageRectWithPaintFlags));
 }
@@ -3015,13 +3078,14 @@
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {  //
             canvas->drawImageRect(CanvasCompareTester::kTestImage, src, dst,
-                                  ToSk(DlImageSampling::kNearestNeighbor),
-                                  nullptr, SkCanvas::kFast_SrcRectConstraint);
+                                  SkImageSampling::kNearestNeighbor, nullptr,
+                                  SkCanvas::kFast_SrcRectConstraint);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {  //
             canvas->DrawImageRect(
                 DlImage::Make(CanvasCompareTester::kTestImage), src, dst,
-                DlImageSampling::kNearestNeighbor, nullptr, false);
+                DlImageSampling::kNearestNeighbor, nullptr,
+                DlCanvas::SrcRectConstraint::kFast);
           },
           kDrawImageRectFlags));
 }
@@ -3033,13 +3097,14 @@
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {  //
             canvas->drawImageRect(CanvasCompareTester::kTestImage, src, dst,
-                                  ToSk(DlImageSampling::kLinear), &paint,
+                                  SkImageSampling::kLinear, &paint,
                                   SkCanvas::kFast_SrcRectConstraint);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {  //
             canvas->DrawImageRect(
                 DlImage::Make(CanvasCompareTester::kTestImage), src, dst,
-                DlImageSampling::kLinear, &paint, false);
+                DlImageSampling::kLinear, &paint,
+                DlCanvas::SrcRectConstraint::kFast);
           },
           kDrawImageRectWithPaintFlags));
 }
@@ -3125,17 +3190,19 @@
       DlColor::kMagenta(),
   };
   const sk_sp<SkImage> image = CanvasCompareTester::kTestImage;
-  const DlImageSampling sampling = DlImageSampling::kNearestNeighbor;
+  const DlImageSampling dl_sampling = DlImageSampling::kNearestNeighbor;
+  const SkSamplingOptions sk_sampling = SkImageSampling::kNearestNeighbor;
   CanvasCompareTester::RenderAll(  //
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {
             canvas->drawAtlas(image.get(), xform, tex, sk_colors, 4,
-                              SkBlendMode::kSrcOver, ToSk(sampling), nullptr,
+                              SkBlendMode::kSrcOver, sk_sampling, nullptr,
                               &paint);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {
             canvas->DrawAtlas(DlImage::Make(image), xform, tex, dl_colors, 4,
-                              DlBlendMode::kSrcOver, sampling, nullptr, &paint);
+                              DlBlendMode::kSrcOver, dl_sampling, nullptr,
+                              &paint);
           },
           kDrawAtlasWithPaintFlags));
 }
@@ -3170,17 +3237,18 @@
       DlColor::kMagenta(),
   };
   const sk_sp<SkImage> image = CanvasCompareTester::kTestImage;
-  const DlImageSampling sampling = DlImageSampling::kNearestNeighbor;
+  const DlImageSampling dl_sampling = DlImageSampling::kNearestNeighbor;
+  const SkSamplingOptions sk_sampling = SkImageSampling::kNearestNeighbor;
   CanvasCompareTester::RenderAll(  //
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {
             canvas->drawAtlas(image.get(), xform, tex, sk_colors, 4,
-                              SkBlendMode::kSrcOver, ToSk(sampling),  //
+                              SkBlendMode::kSrcOver, sk_sampling,  //
                               nullptr, nullptr);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {
             canvas->DrawAtlas(DlImage::Make(image), xform, tex, dl_colors, 4,
-                              DlBlendMode::kSrcOver, sampling, nullptr,
+                              DlBlendMode::kSrcOver, dl_sampling, nullptr,
                               nullptr);
           },
           kDrawAtlasFlags));
@@ -3216,34 +3284,39 @@
       DlColor::kMagenta(),
   };
   const sk_sp<SkImage> image = CanvasCompareTester::kTestImage;
-  const DlImageSampling sampling = DlImageSampling::kLinear;
+  const DlImageSampling dl_sampling = DlImageSampling::kLinear;
+  const SkSamplingOptions sk_sampling = SkImageSampling::kLinear;
   CanvasCompareTester::RenderAll(  //
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {
             canvas->drawAtlas(image.get(), xform, tex, sk_colors, 2,  //
-                              SkBlendMode::kSrcOver, ToSk(sampling), nullptr,
+                              SkBlendMode::kSrcOver, sk_sampling, nullptr,
                               &paint);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {
             canvas->DrawAtlas(DlImage::Make(image), xform, tex, dl_colors, 2,
-                              DlBlendMode::kSrcOver, sampling, nullptr, &paint);
+                              DlBlendMode::kSrcOver, dl_sampling, nullptr,
+                              &paint);
           },
           kDrawAtlasWithPaintFlags));
 }
 
 sk_sp<DisplayList> makeTestDisplayList() {
   DisplayListBuilder builder;
-  builder.setStyle(DlDrawStyle::kFill);
-  builder.setColor(SK_ColorRED);
-  builder.drawRect({kRenderLeft, kRenderTop, kRenderCenterX, kRenderCenterY});
-  builder.setColor(SK_ColorBLUE);
-  builder.drawRect({kRenderCenterX, kRenderTop, kRenderRight, kRenderCenterY});
-  builder.setColor(SK_ColorGREEN);
-  builder.drawRect(
-      {kRenderLeft, kRenderCenterY, kRenderCenterX, kRenderBottom});
-  builder.setColor(SK_ColorYELLOW);
-  builder.drawRect(
-      {kRenderCenterX, kRenderCenterY, kRenderRight, kRenderBottom});
+  DlPaint paint;
+  paint.setDrawStyle(DlDrawStyle::kFill);
+  paint.setColor(SK_ColorRED);
+  builder.DrawRect({kRenderLeft, kRenderTop, kRenderCenterX, kRenderCenterY},
+                   paint);
+  paint.setColor(SK_ColorBLUE);
+  builder.DrawRect({kRenderCenterX, kRenderTop, kRenderRight, kRenderCenterY},
+                   paint);
+  paint.setColor(SK_ColorGREEN);
+  builder.DrawRect({kRenderLeft, kRenderCenterY, kRenderCenterX, kRenderBottom},
+                   paint);
+  paint.setColor(SK_ColorYELLOW);
+  builder.DrawRect(
+      {kRenderCenterX, kRenderCenterY, kRenderRight, kRenderBottom}, paint);
   return builder.Build();
 }
 
@@ -3252,7 +3325,7 @@
   CanvasCompareTester::RenderAll(  //
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {  //
-            display_list->RenderTo(canvas);
+            DlSkCanvasAdapter(canvas).DrawDisplayList(display_list);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {  //
             canvas->DrawDisplayList(display_list);
@@ -3310,8 +3383,8 @@
   CanvasCompareTester::RenderAll(  //
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {  //
-            DisplayListCanvasDispatcher::DrawShadow(canvas, path, color,
-                                                    elevation, false, 1.0);
+            DlSkCanvasDispatcher::DrawShadow(canvas, path, color, elevation,
+                                             false, 1.0);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {  //
             canvas->DrawShadow(path, color, elevation, false, 1.0);
@@ -3336,8 +3409,8 @@
   CanvasCompareTester::RenderAll(  //
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {  //
-            DisplayListCanvasDispatcher::DrawShadow(canvas, path, color,
-                                                    elevation, true, 1.0);
+            DlSkCanvasDispatcher::DrawShadow(canvas, path, color, elevation,
+                                             true, 1.0);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {  //
             canvas->DrawShadow(path, color, elevation, true, 1.0);
@@ -3362,8 +3435,8 @@
   CanvasCompareTester::RenderAll(  //
       TestParameters(
           [=](SkCanvas* canvas, const SkPaint& paint) {  //
-            DisplayListCanvasDispatcher::DrawShadow(canvas, path, color,
-                                                    elevation, false, 1.5);
+            DlSkCanvasDispatcher::DrawShadow(canvas, path, color, elevation,
+                                             false, 1.5);
           },
           [=](DlCanvas* canvas, const DlPaint& paint) {  //
             canvas->DrawShadow(path, color, elevation, false, 1.5);
@@ -3555,5 +3628,317 @@
   }
 }
 
+TEST_F(DisplayListCanvas, MatrixColorFilterModifyTransparencyCheck) {
+  std::vector<std::unique_ptr<RenderEnvironment>> environments;
+  for (auto& provider : CanvasCompareTester::kTestProviders) {
+    auto env = std::make_unique<RenderEnvironment>(
+        provider.get(), PixelFormat::kN32Premul_PixelFormat);
+    environments.push_back(std::move(env));
+  }
+
+  auto test_matrix = [&environments](int element, SkScalar value) {
+    // clang-format off
+    float matrix[] = {
+        1, 0, 0, 0, 0,
+        0, 1, 0, 0, 0,
+        0, 0, 1, 0, 0,
+        0, 0, 0, 1, 0,
+    };
+    // clang-format on
+    std::string desc =
+        "matrix[" + std::to_string(element) + "] = " + std::to_string(value);
+    float original_value = matrix[element];
+    matrix[element] = value;
+    DlMatrixColorFilter filter(matrix);
+    auto dl_filter = DlMatrixColorFilter::Make(matrix);
+    bool is_identity = (dl_filter == nullptr || original_value == value);
+
+    DlPaint paint(0x7f7f7f7f);
+    DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
+
+    DisplayListBuilder builder1;
+    builder1.Translate(kTestCenter.fX, kTestCenter.fY);
+    builder1.Rotate(45);
+    builder1.Translate(-kTestCenter.fX, -kTestCenter.fY);
+    builder1.DrawRect(kRenderBounds, paint);
+    auto display_list1 = builder1.Build();
+
+    DisplayListBuilder builder2;
+    builder2.Translate(kTestCenter.fX, kTestCenter.fY);
+    builder2.Rotate(45);
+    builder2.Translate(-kTestCenter.fX, -kTestCenter.fY);
+    builder2.SaveLayer(&kTestBounds, &filter_save_paint);
+    builder2.DrawRect(kRenderBounds, paint);
+    builder2.Restore();
+    auto display_list2 = builder2.Build();
+
+    for (auto& env : environments) {
+      auto results1 = env->getResult(display_list1);
+      auto results2 = env->getResult(display_list2);
+      CanvasCompareTester::quickCompareToReference(
+          results1.get(), results2.get(), is_identity,
+          desc + " filter affects rendering");
+      int modified_transparent_pixels =
+          CanvasCompareTester::countModifiedTransparentPixels(results1.get(),
+                                                              results2.get());
+      EXPECT_EQ(filter.modifies_transparent_black(),
+                modified_transparent_pixels != 0)
+          << desc;
+    }
+  };
+
+  // Tests identity (matrix[0] already == 1 in an identity filter)
+  test_matrix(0, 1);
+  // test_matrix(19, 1);
+  for (int i = 0; i < 20; i++) {
+    test_matrix(i, -0.25);
+    test_matrix(i, 0);
+    test_matrix(i, 0.25);
+    test_matrix(i, 1);
+    test_matrix(i, 1.25);
+    test_matrix(i, SK_ScalarNaN);
+    test_matrix(i, SK_ScalarInfinity);
+    test_matrix(i, -SK_ScalarInfinity);
+  }
+}
+
+TEST_F(DisplayListCanvas, MatrixColorFilterOpacityCommuteCheck) {
+  std::vector<std::unique_ptr<RenderEnvironment>> environments;
+  for (auto& provider : CanvasCompareTester::kTestProviders) {
+    auto env = std::make_unique<RenderEnvironment>(
+        provider.get(), PixelFormat::kN32Premul_PixelFormat);
+    environments.push_back(std::move(env));
+  }
+
+  auto test_matrix = [&environments](int element, SkScalar value) {
+    // clang-format off
+    float matrix[] = {
+        1, 0, 0, 0, 0,
+        0, 1, 0, 0, 0,
+        0, 0, 1, 0, 0,
+        0, 0, 0, 1, 0,
+    };
+    // clang-format on
+    std::string desc =
+        "matrix[" + std::to_string(element) + "] = " + std::to_string(value);
+    matrix[element] = value;
+    auto filter = DlMatrixColorFilter::Make(matrix);
+    EXPECT_EQ(SkScalarIsFinite(value), filter != nullptr);
+
+    DlPaint paint(0x80808080);
+    DlPaint opacity_save_paint = DlPaint().setOpacity(0.5);
+    DlPaint filter_save_paint = DlPaint().setColorFilter(filter);
+
+    DisplayListBuilder builder1;
+    builder1.SaveLayer(&kTestBounds, &opacity_save_paint);
+    builder1.SaveLayer(&kTestBounds, &filter_save_paint);
+    // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
+    builder1.DrawRect(kRenderBounds, paint);
+    builder1.Restore();
+    builder1.Restore();
+    auto display_list1 = builder1.Build();
+
+    DisplayListBuilder builder2;
+    builder2.SaveLayer(&kTestBounds, &filter_save_paint);
+    builder2.SaveLayer(&kTestBounds, &opacity_save_paint);
+    // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
+    builder2.DrawRect(kRenderBounds, paint);
+    builder2.Restore();
+    builder2.Restore();
+    auto display_list2 = builder2.Build();
+
+    for (auto& env : environments) {
+      auto results1 = env->getResult(display_list1);
+      auto results2 = env->getResult(display_list2);
+      if (!filter || filter->can_commute_with_opacity()) {
+        CanvasCompareTester::compareToReference(
+            results2.get(), results1.get(), desc, nullptr, nullptr,
+            DlColor::kTransparent(), true, kTestWidth, kTestHeight, true);
+      } else {
+        CanvasCompareTester::quickCompareToReference(
+            results1.get(), results2.get(), false, desc);
+      }
+    }
+  };
+
+  // Tests identity (matrix[0] already == 1 in an identity filter)
+  test_matrix(0, 1);
+  // test_matrix(19, 1);
+  for (int i = 0; i < 20; i++) {
+    test_matrix(i, -0.25);
+    test_matrix(i, 0);
+    test_matrix(i, 0.25);
+    test_matrix(i, 1);
+    test_matrix(i, 1.1);
+    test_matrix(i, SK_ScalarNaN);
+    test_matrix(i, SK_ScalarInfinity);
+    test_matrix(i, -SK_ScalarInfinity);
+  }
+}
+
+#define FOR_EACH_BLEND_MODE_ENUM(FUNC) \
+  FUNC(kSrc)                           \
+  FUNC(kClear)                         \
+  FUNC(kSrc)                           \
+  FUNC(kDst)                           \
+  FUNC(kSrcOver)                       \
+  FUNC(kDstOver)                       \
+  FUNC(kSrcIn)                         \
+  FUNC(kDstIn)                         \
+  FUNC(kSrcOut)                        \
+  FUNC(kDstOut)                        \
+  FUNC(kSrcATop)                       \
+  FUNC(kDstATop)                       \
+  FUNC(kXor)                           \
+  FUNC(kPlus)                          \
+  FUNC(kModulate)                      \
+  FUNC(kScreen)                        \
+  FUNC(kOverlay)                       \
+  FUNC(kDarken)                        \
+  FUNC(kLighten)                       \
+  FUNC(kColorDodge)                    \
+  FUNC(kColorBurn)                     \
+  FUNC(kHardLight)                     \
+  FUNC(kSoftLight)                     \
+  FUNC(kDifference)                    \
+  FUNC(kExclusion)                     \
+  FUNC(kMultiply)                      \
+  FUNC(kHue)                           \
+  FUNC(kSaturation)                    \
+  FUNC(kColor)                         \
+  FUNC(kLuminosity)
+
+TEST_F(DisplayListCanvas, BlendColorFilterModifyTransparencyCheck) {
+  std::vector<std::unique_ptr<RenderEnvironment>> environments;
+  for (auto& provider : CanvasCompareTester::kTestProviders) {
+    auto env = std::make_unique<RenderEnvironment>(
+        provider.get(), PixelFormat::kN32Premul_PixelFormat);
+    environments.push_back(std::move(env));
+  }
+
+  auto test_mode_color = [&environments](DlBlendMode mode, DlColor color) {
+    std::stringstream desc_str;
+    desc_str << "blend[" << mode << ", " << color << "]";
+    std::string desc = desc_str.str();
+    DlBlendColorFilter filter(color, mode);
+    if (filter.modifies_transparent_black()) {
+      ASSERT_NE(DlBlendColorFilter::Make(color, mode), nullptr) << desc;
+    }
+
+    DlPaint paint(0x7f7f7f7f);
+    DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
+
+    DisplayListBuilder builder1;
+    builder1.Translate(kTestCenter.fX, kTestCenter.fY);
+    builder1.Rotate(45);
+    builder1.Translate(-kTestCenter.fX, -kTestCenter.fY);
+    builder1.DrawRect(kRenderBounds, paint);
+    auto display_list1 = builder1.Build();
+
+    DisplayListBuilder builder2;
+    builder2.Translate(kTestCenter.fX, kTestCenter.fY);
+    builder2.Rotate(45);
+    builder2.Translate(-kTestCenter.fX, -kTestCenter.fY);
+    builder2.SaveLayer(&kTestBounds, &filter_save_paint);
+    builder2.DrawRect(kRenderBounds, paint);
+    builder2.Restore();
+    auto display_list2 = builder2.Build();
+
+    for (auto& env : environments) {
+      auto results1 = env->getResult(display_list1);
+      auto results2 = env->getResult(display_list2);
+      int modified_transparent_pixels =
+          CanvasCompareTester::countModifiedTransparentPixels(results1.get(),
+                                                              results2.get());
+      EXPECT_EQ(filter.modifies_transparent_black(),
+                modified_transparent_pixels != 0)
+          << desc;
+    }
+  };
+
+  auto test_mode = [&test_mode_color](DlBlendMode mode) {
+    test_mode_color(mode, DlColor::kTransparent());
+    test_mode_color(mode, DlColor::kWhite());
+    test_mode_color(mode, DlColor::kWhite().modulateOpacity(0.5));
+    test_mode_color(mode, DlColor::kBlack());
+    test_mode_color(mode, DlColor::kBlack().modulateOpacity(0.5));
+  };
+
+#define TEST_MODE(V) test_mode(DlBlendMode::V);
+  FOR_EACH_BLEND_MODE_ENUM(TEST_MODE)
+#undef TEST_MODE
+}
+
+TEST_F(DisplayListCanvas, BlendColorFilterOpacityCommuteCheck) {
+  std::vector<std::unique_ptr<RenderEnvironment>> environments;
+  for (auto& provider : CanvasCompareTester::kTestProviders) {
+    auto env = std::make_unique<RenderEnvironment>(
+        provider.get(), PixelFormat::kN32Premul_PixelFormat);
+    environments.push_back(std::move(env));
+  }
+
+  auto test_mode_color = [&environments](DlBlendMode mode, DlColor color) {
+    std::stringstream desc_str;
+    desc_str << "blend[" << mode << ", " << color << "]";
+    std::string desc = desc_str.str();
+    DlBlendColorFilter filter(color, mode);
+    if (filter.can_commute_with_opacity()) {
+      // If it can commute with opacity, then it might also be a NOP,
+      // so we won't necessarily get a non-null return from |::Make()|
+    } else {
+      ASSERT_NE(DlBlendColorFilter::Make(color, mode), nullptr) << desc;
+    }
+
+    DlPaint paint(0x80808080);
+    DlPaint opacity_save_paint = DlPaint().setOpacity(0.5);
+    DlPaint filter_save_paint = DlPaint().setColorFilter(&filter);
+
+    DisplayListBuilder builder1;
+    builder1.SaveLayer(&kTestBounds, &opacity_save_paint);
+    builder1.SaveLayer(&kTestBounds, &filter_save_paint);
+    // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
+    builder1.DrawRect(kRenderBounds, paint);
+    builder1.Restore();
+    builder1.Restore();
+    auto display_list1 = builder1.Build();
+
+    DisplayListBuilder builder2;
+    builder2.SaveLayer(&kTestBounds, &filter_save_paint);
+    builder2.SaveLayer(&kTestBounds, &opacity_save_paint);
+    // builder1.DrawRect(kRenderBounds.makeOffset(20, 20), DlPaint());
+    builder2.DrawRect(kRenderBounds, paint);
+    builder2.Restore();
+    builder2.Restore();
+    auto display_list2 = builder2.Build();
+
+    for (auto& env : environments) {
+      auto results1 = env->getResult(display_list1);
+      auto results2 = env->getResult(display_list2);
+      if (filter.can_commute_with_opacity()) {
+        CanvasCompareTester::compareToReference(
+            results2.get(), results1.get(), desc, nullptr, nullptr,
+            DlColor::kTransparent(), true, kTestWidth, kTestHeight, true);
+      } else {
+        CanvasCompareTester::quickCompareToReference(
+            results1.get(), results2.get(), false, desc);
+      }
+    }
+  };
+
+  auto test_mode = [&test_mode_color](DlBlendMode mode) {
+    test_mode_color(mode, DlColor::kTransparent());
+    test_mode_color(mode, DlColor::kWhite());
+    test_mode_color(mode, DlColor::kWhite().modulateOpacity(0.5));
+    test_mode_color(mode, DlColor::kBlack());
+    test_mode_color(mode, DlColor::kBlack().modulateOpacity(0.5));
+  };
+
+#define TEST_MODE(V) test_mode(DlBlendMode::V);
+  FOR_EACH_BLEND_MODE_ENUM(TEST_MODE)
+#undef TEST_MODE
+}
+
+#undef FOR_EACH_ENUM
+
 }  // namespace testing
 }  // namespace flutter
diff --git a/display_list/skia/dl_sk_canvas.cc b/display_list/skia/dl_sk_canvas.cc
index f7a0de8..7f73e05 100644
--- a/display_list/skia/dl_sk_canvas.cc
+++ b/display_list/skia/dl_sk_canvas.cc
@@ -4,43 +4,12 @@
 
 #include "flutter/display_list/skia/dl_sk_canvas.h"
 
-#include "flutter/display_list/display_list_canvas_dispatcher.h"
+#include "flutter/display_list/skia/dl_sk_conversions.h"
+#include "flutter/display_list/skia/dl_sk_dispatcher.h"
+#include "flutter/fml/trace_event.h"
 
 namespace flutter {
 
-static sk_sp<SkShader> ToSk(const DlColorSource* source) {
-  return source ? source->skia_object() : nullptr;
-}
-
-static sk_sp<SkImageFilter> ToSk(const DlImageFilter* filter) {
-  return filter ? filter->skia_object() : nullptr;
-}
-
-static sk_sp<SkColorFilter> ToSk(const DlColorFilter* filter) {
-  return filter ? filter->skia_object() : nullptr;
-}
-
-static sk_sp<SkMaskFilter> ToSk(const DlMaskFilter* filter) {
-  return filter ? filter->skia_object() : nullptr;
-}
-
-static sk_sp<SkPathEffect> ToSk(const DlPathEffect* effect) {
-  return effect ? effect->skia_object() : nullptr;
-}
-
-static SkCanvas::SrcRectConstraint ToSkConstraint(bool enforce_edges) {
-  return enforce_edges ? SkCanvas::kStrict_SrcRectConstraint
-                       : SkCanvas::kFast_SrcRectConstraint;
-}
-
-static SkClipOp ToSk(DlCanvas::ClipOp op) {
-  return static_cast<SkClipOp>(op);
-}
-
-static SkCanvas::PointMode ToSk(DlCanvas::PointMode mode) {
-  return static_cast<SkCanvas::PointMode>(mode);
-}
-
 // clang-format off
 constexpr float kInvertColorMatrix[20] = {
   -1.0,    0,    0, 1.0, 0,
@@ -119,10 +88,11 @@
 void DlSkCanvasAdapter::SaveLayer(const SkRect* bounds,
                                   const DlPaint* paint,
                                   const DlImageFilter* backdrop) {
-  sk_sp<SkImageFilter> sk_filter = backdrop ? backdrop->skia_object() : nullptr;
+  sk_sp<SkImageFilter> sk_backdrop = ToSk(backdrop);
   SkOptionalPaint sk_paint(paint);
+  TRACE_EVENT0("flutter", "Canvas::saveLayer");
   delegate_->saveLayer(
-      SkCanvas::SaveLayerRec{bounds, sk_paint(), sk_filter.get(), 0});
+      SkCanvas::SaveLayerRec{bounds, sk_paint(), sk_backdrop.get(), 0});
 }
 
 void DlSkCanvasAdapter::Restore() {
@@ -309,7 +279,7 @@
 void DlSkCanvasAdapter::DrawVertices(const DlVertices* vertices,
                                      DlBlendMode mode,
                                      const DlPaint& paint) {
-  delegate_->drawVertices(vertices->skia_object(), ToSk(mode), ToSk(paint));
+  delegate_->drawVertices(ToSk(vertices), ToSk(mode), ToSk(paint));
 }
 
 void DlSkCanvasAdapter::DrawImage(const sk_sp<DlImage>& image,
@@ -327,11 +297,11 @@
                                       const SkRect& dst,
                                       DlImageSampling sampling,
                                       const DlPaint* paint,
-                                      bool enforce_src_edges) {
+                                      SrcRectConstraint constraint) {
   SkOptionalPaint sk_paint(paint);
   sk_sp<SkImage> sk_image = image->skia_image();
   delegate_->drawImageRect(sk_image.get(), src, dst, ToSk(sampling), sk_paint(),
-                           ToSkConstraint(enforce_src_edges));
+                           ToSk(constraint));
 }
 
 void DlSkCanvasAdapter::DrawImageNine(const sk_sp<DlImage>& image,
@@ -363,7 +333,26 @@
 
 void DlSkCanvasAdapter::DrawDisplayList(const sk_sp<DisplayList> display_list,
                                         SkScalar opacity) {
-  display_list->RenderTo(delegate_, opacity);
+  const int restore_count = delegate_->getSaveCount();
+
+  // Figure out whether we can apply the opacity during dispatch or
+  // if we need a saveLayer.
+  if (opacity < SK_Scalar1 && !display_list->can_apply_group_opacity()) {
+    TRACE_EVENT0("flutter", "Canvas::saveLayer");
+    delegate_->saveLayerAlphaf(&display_list->bounds(), opacity);
+    opacity = SK_Scalar1;
+  } else {
+    delegate_->save();
+  }
+
+  DlSkCanvasDispatcher dispatcher(delegate_, opacity);
+  if (display_list->has_rtree()) {
+    display_list->Dispatch(dispatcher, delegate_->getLocalClipBounds());
+  } else {
+    display_list->Dispatch(dispatcher);
+  }
+
+  delegate_->restoreToCount(restore_count);
 }
 
 void DlSkCanvasAdapter::DrawTextBlob(const sk_sp<SkTextBlob>& blob,
@@ -378,8 +367,8 @@
                                    const SkScalar elevation,
                                    bool transparent_occluder,
                                    SkScalar dpr) {
-  DisplayListCanvasDispatcher::DrawShadow(delegate_, path, color, elevation,
-                                          transparent_occluder, dpr);
+  DlSkCanvasDispatcher::DrawShadow(delegate_, path, color, elevation,
+                                   transparent_occluder, dpr);
 }
 
 void DlSkCanvasAdapter::Flush() {
diff --git a/display_list/skia/dl_sk_canvas.h b/display_list/skia/dl_sk_canvas.h
index 249e518..fc66bcc 100644
--- a/display_list/skia/dl_sk_canvas.h
+++ b/display_list/skia/dl_sk_canvas.h
@@ -114,12 +114,13 @@
                  const SkPoint point,
                  DlImageSampling sampling,
                  const DlPaint* paint = nullptr) override;
-  void DrawImageRect(const sk_sp<DlImage>& image,
-                     const SkRect& src,
-                     const SkRect& dst,
-                     DlImageSampling sampling,
-                     const DlPaint* paint = nullptr,
-                     bool enforce_src_edges = false) override;
+  void DrawImageRect(
+      const sk_sp<DlImage>& image,
+      const SkRect& src,
+      const SkRect& dst,
+      DlImageSampling sampling,
+      const DlPaint* paint = nullptr,
+      SrcRectConstraint constraint = SrcRectConstraint::kFast) override;
   void DrawImageNine(const sk_sp<DlImage>& image,
                      const SkIRect& center,
                      const SkRect& dst,
@@ -135,7 +136,7 @@
                  const SkRect* cullRect,
                  const DlPaint* paint = nullptr) override;
   void DrawDisplayList(const sk_sp<DisplayList> display_list,
-                       SkScalar opacity) override;
+                       SkScalar opacity = SK_Scalar1) override;
   void DrawTextBlob(const sk_sp<SkTextBlob>& blob,
                     SkScalar x,
                     SkScalar y,
diff --git a/display_list/skia/dl_sk_conversions.cc b/display_list/skia/dl_sk_conversions.cc
new file mode 100644
index 0000000..e8dd4cf
--- /dev/null
+++ b/display_list/skia/dl_sk_conversions.cc
@@ -0,0 +1,240 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/display_list/skia/dl_sk_conversions.h"
+
+namespace flutter {
+
+sk_sp<SkShader> ToSk(const DlColorSource* source) {
+  if (!source) {
+    return nullptr;
+  }
+  static auto ToSkColors = [](const DlGradientColorSourceBase* gradient) {
+    return reinterpret_cast<const SkColor*>(gradient->colors());
+  };
+  switch (source->type()) {
+    case DlColorSourceType::kColor: {
+      const DlColorColorSource* color_source = source->asColor();
+      FML_DCHECK(color_source != nullptr);
+      return SkShaders::Color(color_source->color());
+    }
+    case DlColorSourceType::kImage: {
+      const DlImageColorSource* image_source = source->asImage();
+      FML_DCHECK(image_source != nullptr);
+      auto image = image_source->image();
+      if (!image || !image->skia_image()) {
+        return nullptr;
+      }
+      return image->skia_image()->makeShader(
+          ToSk(image_source->horizontal_tile_mode()),
+          ToSk(image_source->vertical_tile_mode()),
+          ToSk(image_source->sampling()), image_source->matrix_ptr());
+    }
+    case DlColorSourceType::kLinearGradient: {
+      const DlLinearGradientColorSource* linear_source =
+          source->asLinearGradient();
+      FML_DCHECK(linear_source != nullptr);
+      SkPoint pts[] = {linear_source->start_point(),
+                       linear_source->end_point()};
+      return SkGradientShader::MakeLinear(
+          pts, ToSkColors(linear_source), linear_source->stops(),
+          linear_source->stop_count(), ToSk(linear_source->tile_mode()), 0,
+          linear_source->matrix_ptr());
+    }
+    case DlColorSourceType::kRadialGradient: {
+      const DlRadialGradientColorSource* radial_source =
+          source->asRadialGradient();
+      FML_DCHECK(radial_source != nullptr);
+      return SkGradientShader::MakeRadial(
+          radial_source->center(), radial_source->radius(),
+          ToSkColors(radial_source), radial_source->stops(),
+          radial_source->stop_count(), ToSk(radial_source->tile_mode()), 0,
+          radial_source->matrix_ptr());
+    }
+    case DlColorSourceType::kConicalGradient: {
+      const DlConicalGradientColorSource* conical_source =
+          source->asConicalGradient();
+      FML_DCHECK(conical_source != nullptr);
+      return SkGradientShader::MakeTwoPointConical(
+          conical_source->start_center(), conical_source->start_radius(),
+          conical_source->end_center(), conical_source->end_radius(),
+          ToSkColors(conical_source), conical_source->stops(),
+          conical_source->stop_count(), ToSk(conical_source->tile_mode()), 0,
+          conical_source->matrix_ptr());
+    }
+    case DlColorSourceType::kSweepGradient: {
+      const DlSweepGradientColorSource* sweep_source =
+          source->asSweepGradient();
+      FML_DCHECK(sweep_source != nullptr);
+      return SkGradientShader::MakeSweep(
+          sweep_source->center().x(), sweep_source->center().y(),
+          ToSkColors(sweep_source), sweep_source->stops(),
+          sweep_source->stop_count(), ToSk(sweep_source->tile_mode()),
+          sweep_source->start(), sweep_source->end(), 0,
+          sweep_source->matrix_ptr());
+    }
+    case DlColorSourceType::kRuntimeEffect: {
+      const DlRuntimeEffectColorSource* runtime_source =
+          source->asRuntimeEffect();
+      FML_DCHECK(runtime_source != nullptr);
+      auto runtime_effect = runtime_source->runtime_effect();
+      if (!runtime_effect || !runtime_effect->skia_runtime_effect()) {
+        return nullptr;
+      }
+
+      auto samplers = runtime_source->samplers();
+      std::vector<sk_sp<SkShader>> sk_samplers(samplers.size());
+      for (size_t i = 0; i < samplers.size(); i++) {
+        auto sampler = samplers[i];
+        if (sampler == nullptr) {
+          return nullptr;
+        }
+        sk_samplers[i] = ToSk(sampler);
+      }
+
+      auto uniform_data = runtime_source->uniform_data();
+      auto ref = new std::shared_ptr<std::vector<uint8_t>>(uniform_data);
+      auto sk_uniform_data = SkData::MakeWithProc(
+          uniform_data->data(), uniform_data->size(),
+          [](const void* ptr, void* context) {
+            delete reinterpret_cast<std::shared_ptr<std::vector<uint8_t>>*>(
+                context);
+          },
+          ref);
+
+      return runtime_effect->skia_runtime_effect()->makeShader(
+          sk_uniform_data, sk_samplers.data(), sk_samplers.size());
+    }
+#ifdef IMPELLER_ENABLE_3D
+    case DlColorSourceType::kScene: {
+      return nullptr;
+    }
+#endif  // IMPELLER_ENABLE_3D
+  }
+}
+
+sk_sp<SkImageFilter> ToSk(const DlImageFilter* filter) {
+  if (!filter) {
+    return nullptr;
+  }
+  switch (filter->type()) {
+    case DlImageFilterType::kBlur: {
+      const DlBlurImageFilter* blur_filter = filter->asBlur();
+      FML_DCHECK(blur_filter != nullptr);
+      return SkImageFilters::Blur(blur_filter->sigma_x(),
+                                  blur_filter->sigma_y(),
+                                  ToSk(blur_filter->tile_mode()), nullptr);
+    }
+    case DlImageFilterType::kDilate: {
+      const DlDilateImageFilter* dilate_filter = filter->asDilate();
+      FML_DCHECK(dilate_filter != nullptr);
+      return SkImageFilters::Dilate(dilate_filter->radius_x(),
+                                    dilate_filter->radius_y(), nullptr);
+    }
+    case DlImageFilterType::kErode: {
+      const DlErodeImageFilter* erode_filter = filter->asErode();
+      FML_DCHECK(erode_filter != nullptr);
+      return SkImageFilters::Erode(erode_filter->radius_x(),
+                                   erode_filter->radius_y(), nullptr);
+    }
+    case DlImageFilterType::kMatrix: {
+      const DlMatrixImageFilter* matrix_filter = filter->asMatrix();
+      FML_DCHECK(matrix_filter != nullptr);
+      return SkImageFilters::MatrixTransform(
+          matrix_filter->matrix(), ToSk(matrix_filter->sampling()), nullptr);
+    }
+    case DlImageFilterType::kCompose: {
+      const DlComposeImageFilter* compose_filter = filter->asCompose();
+      FML_DCHECK(compose_filter != nullptr);
+      return SkImageFilters::Compose(ToSk(compose_filter->outer()),
+                                     ToSk(compose_filter->inner()));
+    }
+    case DlImageFilterType::kColorFilter: {
+      const DlColorFilterImageFilter* cf_filter = filter->asColorFilter();
+      FML_DCHECK(cf_filter != nullptr);
+      return SkImageFilters::ColorFilter(ToSk(cf_filter->color_filter()),
+                                         nullptr);
+    }
+    case DlImageFilterType::kLocalMatrix: {
+      const DlLocalMatrixImageFilter* lm_filter = filter->asLocalMatrix();
+      FML_DCHECK(lm_filter != nullptr);
+      sk_sp<SkImageFilter> skia_filter = ToSk(lm_filter->image_filter());
+      // The image_filter property itself might have been null, or the
+      // construction of the SkImageFilter might be optimized to null
+      // for any number of reasons. In any case, if the filter is null
+      // or optimizaed away, let's then optimize away this local matrix
+      // case by returning null.
+      if (!skia_filter) {
+        return nullptr;
+      }
+      return skia_filter->makeWithLocalMatrix(lm_filter->matrix());
+    }
+  }
+}
+
+sk_sp<SkColorFilter> ToSk(const DlColorFilter* filter) {
+  if (!filter) {
+    return nullptr;
+  }
+  switch (filter->type()) {
+    case DlColorFilterType::kBlend: {
+      const DlBlendColorFilter* blend_filter = filter->asBlend();
+      FML_DCHECK(blend_filter != nullptr);
+      return SkColorFilters::Blend(blend_filter->color(),
+                                   ToSk(blend_filter->mode()));
+    }
+    case DlColorFilterType::kMatrix: {
+      const DlMatrixColorFilter* matrix_filter = filter->asMatrix();
+      FML_DCHECK(matrix_filter != nullptr);
+      float matrix[20];
+      matrix_filter->get_matrix(matrix);
+      return SkColorFilters::Matrix(matrix);
+    }
+    case DlColorFilterType::kSrgbToLinearGamma: {
+      return SkColorFilters::SRGBToLinearGamma();
+    }
+    case DlColorFilterType::kLinearToSrgbGamma: {
+      return SkColorFilters::LinearToSRGBGamma();
+    }
+  }
+}
+
+sk_sp<SkMaskFilter> ToSk(const DlMaskFilter* filter) {
+  if (!filter) {
+    return nullptr;
+  }
+  switch (filter->type()) {
+    case DlMaskFilterType::kBlur: {
+      const DlBlurMaskFilter* blur_filter = filter->asBlur();
+      FML_DCHECK(blur_filter != nullptr);
+      return SkMaskFilter::MakeBlur(blur_filter->style(), blur_filter->sigma(),
+                                    blur_filter->respectCTM());
+    }
+  }
+}
+
+sk_sp<SkPathEffect> ToSk(const DlPathEffect* effect) {
+  if (!effect) {
+    return nullptr;
+  }
+  switch (effect->type()) {
+    case DlPathEffectType::kDash: {
+      const DlDashPathEffect* dash_effect = effect->asDash();
+      FML_DCHECK(dash_effect != nullptr);
+      return SkDashPathEffect::Make(dash_effect->intervals(),
+                                    dash_effect->count(), dash_effect->phase());
+    }
+  }
+}
+
+sk_sp<SkVertices> ToSk(const DlVertices* vertices) {
+  const SkColor* sk_colors =
+      reinterpret_cast<const SkColor*>(vertices->colors());
+  return SkVertices::MakeCopy(ToSk(vertices->mode()), vertices->vertex_count(),
+                              vertices->vertices(),
+                              vertices->texture_coordinates(), sk_colors,
+                              vertices->index_count(), vertices->indices());
+}
+
+}  // namespace flutter
diff --git a/display_list/skia/dl_sk_conversions.h b/display_list/skia/dl_sk_conversions.h
new file mode 100644
index 0000000..54056c4
--- /dev/null
+++ b/display_list/skia/dl_sk_conversions.h
@@ -0,0 +1,116 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_DISPLAY_LIST_SKIA_DL_SK_CONVERSIONS_H_
+#define FLUTTER_DISPLAY_LIST_SKIA_DL_SK_CONVERSIONS_H_
+
+#include "flutter/display_list/dl_op_receiver.h"
+
+namespace flutter {
+
+inline SkBlendMode ToSk(DlBlendMode mode) {
+  return static_cast<SkBlendMode>(mode);
+}
+
+inline SkPaint::Style ToSk(DlDrawStyle style) {
+  return static_cast<SkPaint::Style>(style);
+}
+
+inline SkPaint::Cap ToSk(DlStrokeCap cap) {
+  return static_cast<SkPaint::Cap>(cap);
+}
+
+inline SkPaint::Join ToSk(DlStrokeJoin join) {
+  return static_cast<SkPaint::Join>(join);
+}
+
+inline SkTileMode ToSk(DlTileMode dl_mode) {
+  return static_cast<SkTileMode>(dl_mode);
+}
+
+inline SkFilterMode ToSk(const DlFilterMode filter_mode) {
+  return static_cast<SkFilterMode>(filter_mode);
+}
+
+inline SkVertices::VertexMode ToSk(DlVertexMode dl_mode) {
+  return static_cast<SkVertices::VertexMode>(dl_mode);
+}
+
+inline SkSamplingOptions ToSk(DlImageSampling sampling) {
+  switch (sampling) {
+    case DlImageSampling::kCubic:
+      return SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f});
+    case DlImageSampling::kLinear:
+      return SkSamplingOptions(SkFilterMode::kLinear);
+    case DlImageSampling::kMipmapLinear:
+      return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear);
+    case DlImageSampling::kNearestNeighbor:
+      return SkSamplingOptions(SkFilterMode::kNearest);
+  }
+}
+
+inline SkCanvas::SrcRectConstraint ToSk(
+    DlCanvas::SrcRectConstraint constraint) {
+  return static_cast<SkCanvas::SrcRectConstraint>(constraint);
+}
+
+inline SkClipOp ToSk(DlCanvas::ClipOp op) {
+  return static_cast<SkClipOp>(op);
+}
+
+inline SkCanvas::PointMode ToSk(DlCanvas::PointMode mode) {
+  return static_cast<SkCanvas::PointMode>(mode);
+}
+
+extern sk_sp<SkShader> ToSk(const DlColorSource* source);
+inline sk_sp<SkShader> ToSk(std::shared_ptr<const DlColorSource> source) {
+  return ToSk(source.get());
+}
+inline sk_sp<SkShader> ToSk(const DlColorSource& source) {
+  return ToSk(&source);
+}
+
+extern sk_sp<SkImageFilter> ToSk(const DlImageFilter* filter);
+inline sk_sp<SkImageFilter> ToSk(std::shared_ptr<const DlImageFilter> filter) {
+  return ToSk(filter.get());
+}
+inline sk_sp<SkImageFilter> ToSk(const DlImageFilter& filter) {
+  return ToSk(&filter);
+}
+
+extern sk_sp<SkColorFilter> ToSk(const DlColorFilter* filter);
+inline sk_sp<SkColorFilter> ToSk(std::shared_ptr<const DlColorFilter> filter) {
+  return ToSk(filter.get());
+}
+inline sk_sp<SkColorFilter> ToSk(const DlColorFilter& filter) {
+  return ToSk(&filter);
+}
+
+extern sk_sp<SkMaskFilter> ToSk(const DlMaskFilter* filter);
+inline sk_sp<SkMaskFilter> ToSk(std::shared_ptr<const DlMaskFilter> filter) {
+  return ToSk(filter.get());
+}
+inline sk_sp<SkMaskFilter> ToSk(const DlMaskFilter& filter) {
+  return ToSk(&filter);
+}
+
+extern sk_sp<SkPathEffect> ToSk(const DlPathEffect* effect);
+inline sk_sp<SkPathEffect> ToSk(std::shared_ptr<const DlPathEffect> effect) {
+  return ToSk(effect.get());
+}
+inline sk_sp<SkPathEffect> ToSk(const DlPathEffect& effect) {
+  return ToSk(&effect);
+}
+
+extern sk_sp<SkVertices> ToSk(const DlVertices* vertices);
+inline sk_sp<SkVertices> ToSk(std::shared_ptr<const DlVertices> vertices) {
+  return ToSk(vertices.get());
+}
+inline sk_sp<SkVertices> ToSk(const DlVertices& vertices) {
+  return ToSk(&vertices);
+}
+
+}  // namespace flutter
+
+#endif  // FLUTTER_DISPLAY_LIST_SKIA_DL_SK_CONVERSIONS_H_
diff --git a/display_list/skia/dl_sk_conversions_unittests.cc b/display_list/skia/dl_sk_conversions_unittests.cc
new file mode 100644
index 0000000..c35a3c2
--- /dev/null
+++ b/display_list/skia/dl_sk_conversions_unittests.cc
@@ -0,0 +1,267 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/display_list/skia/dl_sk_conversions.h"
+
+#include "flutter/display_list/display_list_blend_mode.h"
+#include "flutter/display_list/display_list_paint.h"
+#include "flutter/display_list/display_list_sampling_options.h"
+#include "flutter/display_list/display_list_tile_mode.h"
+#include "flutter/display_list/display_list_vertices.h"
+#include "flutter/display_list/types.h"
+#include "gtest/gtest.h"
+#include "include/core/SkSamplingOptions.h"
+
+namespace flutter {
+namespace testing {
+
+TEST(DisplayListImageFilter, LocalImageSkiaNull) {
+  auto blur_filter =
+      std::make_shared<DlBlurImageFilter>(0, 0, DlTileMode::kClamp);
+  DlLocalMatrixImageFilter dl_local_matrix_filter(SkMatrix::RotateDeg(45),
+                                                  blur_filter);
+  // With sigmas set to zero on the blur filter, Skia will return a null filter.
+  // The local matrix filter should return nullptr instead of crashing.
+  ASSERT_EQ(ToSk(dl_local_matrix_filter), nullptr);
+}
+
+TEST(DisplayListSkConversions, ToSkTileMode) {
+  ASSERT_EQ(ToSk(DlTileMode::kClamp), SkTileMode::kClamp);
+  ASSERT_EQ(ToSk(DlTileMode::kRepeat), SkTileMode::kRepeat);
+  ASSERT_EQ(ToSk(DlTileMode::kMirror), SkTileMode::kMirror);
+  ASSERT_EQ(ToSk(DlTileMode::kDecal), SkTileMode::kDecal);
+}
+
+TEST(DisplayListSkConversions, ToSkDrawStyle) {
+  ASSERT_EQ(ToSk(DlDrawStyle::kFill), SkPaint::Style::kFill_Style);
+  ASSERT_EQ(ToSk(DlDrawStyle::kStroke), SkPaint::Style::kStroke_Style);
+  ASSERT_EQ(ToSk(DlDrawStyle::kStrokeAndFill),
+            SkPaint::Style::kStrokeAndFill_Style);
+}
+
+TEST(DisplayListSkConversions, ToSkStrokeCap) {
+  ASSERT_EQ(ToSk(DlStrokeCap::kButt), SkPaint::Cap::kButt_Cap);
+  ASSERT_EQ(ToSk(DlStrokeCap::kRound), SkPaint::Cap::kRound_Cap);
+  ASSERT_EQ(ToSk(DlStrokeCap::kSquare), SkPaint::Cap::kSquare_Cap);
+}
+
+TEST(DisplayListSkConversions, ToSkStrokeJoin) {
+  ASSERT_EQ(ToSk(DlStrokeJoin::kMiter), SkPaint::Join::kMiter_Join);
+  ASSERT_EQ(ToSk(DlStrokeJoin::kRound), SkPaint::Join::kRound_Join);
+  ASSERT_EQ(ToSk(DlStrokeJoin::kBevel), SkPaint::Join::kBevel_Join);
+}
+
+TEST(DisplayListSkConversions, ToSkVertexMode) {
+  ASSERT_EQ(ToSk(DlVertexMode::kTriangles),
+            SkVertices::VertexMode::kTriangles_VertexMode);
+  ASSERT_EQ(ToSk(DlVertexMode::kTriangleStrip),
+            SkVertices::VertexMode::kTriangleStrip_VertexMode);
+  ASSERT_EQ(ToSk(DlVertexMode::kTriangleFan),
+            SkVertices::VertexMode::kTriangleFan_VertexMode);
+}
+
+TEST(DisplayListSkConversions, ToSkFilterMode) {
+  ASSERT_EQ(ToSk(DlFilterMode::kLinear), SkFilterMode::kLinear);
+  ASSERT_EQ(ToSk(DlFilterMode::kNearest), SkFilterMode::kNearest);
+  ASSERT_EQ(ToSk(DlFilterMode::kLast), SkFilterMode::kLast);
+}
+
+TEST(DisplayListSkConversions, ToSkSrcRectConstraint) {
+  ASSERT_EQ(ToSk(DlCanvas::SrcRectConstraint::kFast),
+            SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint);
+  ASSERT_EQ(ToSk(DlCanvas::SrcRectConstraint::kStrict),
+            SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint);
+}
+
+TEST(DisplayListSkConversions, ToSkSamplingOptions) {
+  ASSERT_EQ(ToSk(DlImageSampling::kLinear),
+            SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone));
+  ASSERT_EQ(ToSk(DlImageSampling::kMipmapLinear),
+            SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kLinear));
+  ASSERT_EQ(ToSk(DlImageSampling::kNearestNeighbor),
+            SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone));
+  ASSERT_EQ(ToSk(DlImageSampling::kCubic),
+            SkSamplingOptions(SkCubicResampler{1 / 3.0f, 1 / 3.0f}));
+}
+
+#define FOR_EACH_BLEND_MODE_ENUM(FUNC) \
+  FUNC(kSrc)                           \
+  FUNC(kClear)                         \
+  FUNC(kSrc)                           \
+  FUNC(kDst)                           \
+  FUNC(kSrcOver)                       \
+  FUNC(kDstOver)                       \
+  FUNC(kSrcIn)                         \
+  FUNC(kDstIn)                         \
+  FUNC(kSrcOut)                        \
+  FUNC(kDstOut)                        \
+  FUNC(kSrcATop)                       \
+  FUNC(kDstATop)                       \
+  FUNC(kXor)                           \
+  FUNC(kPlus)                          \
+  FUNC(kModulate)                      \
+  FUNC(kScreen)                        \
+  FUNC(kOverlay)                       \
+  FUNC(kDarken)                        \
+  FUNC(kLighten)                       \
+  FUNC(kColorDodge)                    \
+  FUNC(kColorBurn)                     \
+  FUNC(kHardLight)                     \
+  FUNC(kSoftLight)                     \
+  FUNC(kDifference)                    \
+  FUNC(kExclusion)                     \
+  FUNC(kMultiply)                      \
+  FUNC(kHue)                           \
+  FUNC(kSaturation)                    \
+  FUNC(kColor)                         \
+  FUNC(kLuminosity)                    \
+  FUNC(kLastCoeffMode)                 \
+  FUNC(kLastSeparableMode)             \
+  FUNC(kLastMode)
+
+TEST(DisplayListSkConversions, ToSkBlendMode) {
+#define CHECK_TO_SKENUM(V) ASSERT_EQ(ToSk(DlBlendMode::V), SkBlendMode::V);
+  FOR_EACH_BLEND_MODE_ENUM(CHECK_TO_SKENUM)
+#undef CHECK_TO_SKENUM
+}
+
+TEST(DisplayListSkConversions, BlendColorFilterModifiesTransparency) {
+  auto test_mode_color = [](DlBlendMode mode, DlColor color) {
+    std::stringstream desc_str;
+    desc_str << "blend[" << static_cast<int>(mode) << ", " << color << "]";
+    std::string desc = desc_str.str();
+    DlBlendColorFilter filter(color, mode);
+    if (filter.modifies_transparent_black()) {
+      auto dl_filter = DlBlendColorFilter::Make(color, mode);
+      auto sk_filter = ToSk(filter);
+      ASSERT_NE(dl_filter, nullptr) << desc;
+      ASSERT_NE(sk_filter, nullptr) << desc;
+      ASSERT_TRUE(sk_filter->filterColor(0) != 0) << desc;
+    } else {
+      auto dl_filter = DlBlendColorFilter::Make(color, mode);
+      auto sk_filter = ToSk(filter);
+      EXPECT_EQ(dl_filter == nullptr, sk_filter == nullptr) << desc;
+      ASSERT_TRUE(sk_filter == nullptr || sk_filter->filterColor(0) == 0)
+          << desc;
+    }
+  };
+
+  auto test_mode = [&test_mode_color](DlBlendMode mode) {
+    test_mode_color(mode, DlColor::kTransparent());
+    test_mode_color(mode, DlColor::kWhite());
+    test_mode_color(mode, DlColor::kWhite().modulateOpacity(0.5));
+    test_mode_color(mode, DlColor::kBlack());
+    test_mode_color(mode, DlColor::kBlack().modulateOpacity(0.5));
+  };
+
+#define TEST_MODE(V) test_mode(DlBlendMode::V);
+  FOR_EACH_BLEND_MODE_ENUM(TEST_MODE)
+#undef TEST_MODE
+}
+
+#undef FOR_EACH_BLEND_MODE_ENUM
+
+TEST(DisplayListSkConversions, ConvertWithZeroAndNegativeVerticesAndIndices) {
+  std::shared_ptr<const DlVertices> vertices1 = DlVertices::Make(
+      DlVertexMode::kTriangles, 0, nullptr, nullptr, nullptr, 0, nullptr);
+  EXPECT_NE(vertices1, nullptr);
+  EXPECT_NE(ToSk(vertices1), nullptr);
+
+  std::shared_ptr<const DlVertices> vertices2 = DlVertices::Make(
+      DlVertexMode::kTriangles, -1, nullptr, nullptr, nullptr, -1, nullptr);
+  EXPECT_NE(vertices2, nullptr);
+  EXPECT_NE(ToSk(vertices2), nullptr);
+}
+
+TEST(DisplayListVertices, ConvertWithZeroAndNegativeVerticesAndIndices) {
+  DlVertices::Builder builder1(DlVertexMode::kTriangles, 0,
+                               DlVertices::Builder::kNone, 0);
+  EXPECT_TRUE(builder1.is_valid());
+  std::shared_ptr<DlVertices> vertices1 = builder1.build();
+  EXPECT_NE(vertices1, nullptr);
+  EXPECT_NE(ToSk(vertices1), nullptr);
+
+  DlVertices::Builder builder2(DlVertexMode::kTriangles, -1,
+                               DlVertices::Builder::kNone, -1);
+  EXPECT_TRUE(builder2.is_valid());
+  std::shared_ptr<DlVertices> vertices2 = builder2.build();
+  EXPECT_NE(vertices2, nullptr);
+  EXPECT_NE(ToSk(vertices2), nullptr);
+}
+
+TEST(DisplayListColorSource, ConvertRuntimeEffect) {
+  const sk_sp<DlRuntimeEffect> kTestRuntimeEffect1 = DlRuntimeEffect::MakeSkia(
+      SkRuntimeEffect::MakeForShader(
+          SkString("vec4 main(vec2 p) { return vec4(0); }"))
+          .effect);
+  const sk_sp<DlRuntimeEffect> kTestRuntimeEffect2 = DlRuntimeEffect::MakeSkia(
+      SkRuntimeEffect::MakeForShader(
+          SkString("vec4 main(vec2 p) { return vec4(1); }"))
+          .effect);
+  std::shared_ptr<DlRuntimeEffectColorSource> source1 =
+      DlColorSource::MakeRuntimeEffect(
+          kTestRuntimeEffect1, {}, std::make_shared<std::vector<uint8_t>>());
+  std::shared_ptr<DlRuntimeEffectColorSource> source2 =
+      DlColorSource::MakeRuntimeEffect(
+          kTestRuntimeEffect2, {}, std::make_shared<std::vector<uint8_t>>());
+  std::shared_ptr<DlRuntimeEffectColorSource> source3 =
+      DlColorSource::MakeRuntimeEffect(
+          nullptr, {}, std::make_shared<std::vector<uint8_t>>());
+
+  ASSERT_NE(ToSk(source1), nullptr);
+  ASSERT_NE(ToSk(source2), nullptr);
+  ASSERT_EQ(ToSk(source3), nullptr);
+}
+
+TEST(DisplayListColorSource, ConvertRuntimeEffectWithNullSampler) {
+  const sk_sp<DlRuntimeEffect> kTestRuntimeEffect1 = DlRuntimeEffect::MakeSkia(
+      SkRuntimeEffect::MakeForShader(
+          SkString("vec4 main(vec2 p) { return vec4(0); }"))
+          .effect);
+  std::shared_ptr<DlRuntimeEffectColorSource> source1 =
+      DlColorSource::MakeRuntimeEffect(
+          kTestRuntimeEffect1, {nullptr},
+          std::make_shared<std::vector<uint8_t>>());
+
+  ASSERT_EQ(ToSk(source1), nullptr);
+}
+
+TEST(DisplayListSkConversions, MatrixColorFilterModifiesTransparency) {
+  auto test_matrix = [](int element, SkScalar value) {
+    // clang-format off
+    float matrix[] = {
+        1, 0, 0, 0, 0,
+        0, 1, 0, 0, 0,
+        0, 0, 1, 0, 0,
+        0, 0, 0, 1, 0,
+    };
+    // clang-format on
+    std::string desc =
+        "matrix[" + std::to_string(element) + "] = " + std::to_string(value);
+    matrix[element] = value;
+    DlMatrixColorFilter filter(matrix);
+    auto dl_filter = DlMatrixColorFilter::Make(matrix);
+    auto sk_filter = ToSk(filter);
+    EXPECT_EQ(dl_filter == nullptr, sk_filter == nullptr);
+    EXPECT_EQ(filter.modifies_transparent_black(),
+              sk_filter && sk_filter->filterColor(0) != 0);
+  };
+
+  // Tests identity (matrix[0] already == 1 in an identity filter)
+  test_matrix(0, 1);
+  // test_matrix(19, 1);
+  for (int i = 0; i < 20; i++) {
+    test_matrix(i, -0.25);
+    test_matrix(i, 0);
+    test_matrix(i, 0.25);
+    test_matrix(i, 1);
+    test_matrix(i, 1.25);
+    test_matrix(i, SK_ScalarNaN);
+    test_matrix(i, SK_ScalarInfinity);
+    test_matrix(i, -SK_ScalarInfinity);
+  }
+}
+
+}  // namespace testing
+}  // namespace flutter
diff --git a/display_list/skia/dl_sk_dispatcher.cc b/display_list/skia/dl_sk_dispatcher.cc
new file mode 100644
index 0000000..a808464
--- /dev/null
+++ b/display_list/skia/dl_sk_dispatcher.cc
@@ -0,0 +1,304 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/display_list/skia/dl_sk_dispatcher.h"
+
+#include "flutter/display_list/display_list_blend_mode.h"
+#include "flutter/display_list/skia/dl_sk_conversions.h"
+#include "flutter/fml/trace_event.h"
+#include "third_party/skia/include/utils/SkShadowUtils.h"
+
+namespace flutter {
+
+const SkPaint* DlSkCanvasDispatcher::safe_paint(bool use_attributes) {
+  if (use_attributes) {
+    // The accumulated SkPaint object will already have incorporated
+    // any attribute overrides.
+    return &paint();
+  } else if (has_opacity()) {
+    temp_paint_.setAlphaf(opacity());
+    return &temp_paint_;
+  } else {
+    return nullptr;
+  }
+}
+
+void DlSkCanvasDispatcher::save() {
+  canvas_->save();
+  // save has no impact on attributes, but it needs to register a record
+  // on the restore stack so that the eventual call to restore() will
+  // know what to do at that time. We could annotate the restore record
+  // with a flag that the record came from a save call, but it is simpler
+  // to just pass in the current opacity value as the value to be used by
+  // the children and let the utility calls notice that it didn't change.
+  save_opacity(opacity());
+}
+void DlSkCanvasDispatcher::restore() {
+  canvas_->restore();
+  restore_opacity();
+}
+void DlSkCanvasDispatcher::saveLayer(const SkRect* bounds,
+                                     const SaveLayerOptions options,
+                                     const DlImageFilter* backdrop) {
+  if (bounds == nullptr && options.can_distribute_opacity() &&
+      backdrop == nullptr) {
+    // We know that:
+    // - no bounds is needed for clipping here
+    // - no backdrop filter is used to initialize the layer
+    // - the current attributes only have an alpha
+    // - the children are compatible with individually rendering with
+    //   an inherited opacity
+    // Therefore we can just use a save instead of a saveLayer and pass the
+    // intended opacity to the children.
+    canvas_->save();
+    // If the saveLayer does not use attributes, the children should continue
+    // to render with the inherited opacity unmodified. If attributes are to
+    // be applied, the children should render with the combination of the
+    // inherited opacity combined with the alpha from the current color.
+    save_opacity(options.renders_with_attributes() ? combined_opacity()
+                                                   : opacity());
+  } else {
+    TRACE_EVENT0("flutter", "Canvas::saveLayer");
+    const SkPaint* paint = safe_paint(options.renders_with_attributes());
+    const sk_sp<SkImageFilter> sk_backdrop = ToSk(backdrop);
+    canvas_->saveLayer(
+        SkCanvas::SaveLayerRec(bounds, paint, sk_backdrop.get(), 0));
+    // saveLayer will apply the current opacity on behalf of the children
+    // so they will inherit an opaque opacity.
+    save_opacity(SK_Scalar1);
+  }
+}
+
+void DlSkCanvasDispatcher::translate(SkScalar tx, SkScalar ty) {
+  canvas_->translate(tx, ty);
+}
+void DlSkCanvasDispatcher::scale(SkScalar sx, SkScalar sy) {
+  canvas_->scale(sx, sy);
+}
+void DlSkCanvasDispatcher::rotate(SkScalar degrees) {
+  canvas_->rotate(degrees);
+}
+void DlSkCanvasDispatcher::skew(SkScalar sx, SkScalar sy) {
+  canvas_->skew(sx, sy);
+}
+// clang-format off
+// 2x3 2D affine subset of a 4x4 transform in row major order
+void DlSkCanvasDispatcher::transform2DAffine(
+    SkScalar mxx, SkScalar mxy, SkScalar mxt,
+    SkScalar myx, SkScalar myy, SkScalar myt) {
+  // Internally concat(SkMatrix) gets redirected to concat(SkM44)
+  // so we just jump directly to the SkM44 version
+  canvas_->concat(SkM44(mxx, mxy, 0, mxt,
+                        myx, myy, 0, myt,
+                         0,   0,  1,  0,
+                         0,   0,  0,  1));
+}
+// full 4x4 transform in row major order
+void DlSkCanvasDispatcher::transformFullPerspective(
+    SkScalar mxx, SkScalar mxy, SkScalar mxz, SkScalar mxt,
+    SkScalar myx, SkScalar myy, SkScalar myz, SkScalar myt,
+    SkScalar mzx, SkScalar mzy, SkScalar mzz, SkScalar mzt,
+    SkScalar mwx, SkScalar mwy, SkScalar mwz, SkScalar mwt) {
+  canvas_->concat(SkM44(mxx, mxy, mxz, mxt,
+                        myx, myy, myz, myt,
+                        mzx, mzy, mzz, mzt,
+                        mwx, mwy, mwz, mwt));
+}
+// clang-format on
+void DlSkCanvasDispatcher::transformReset() {
+  canvas_->setMatrix(original_transform_);
+}
+
+void DlSkCanvasDispatcher::clipRect(const SkRect& rect,
+                                    ClipOp clip_op,
+                                    bool is_aa) {
+  canvas_->clipRect(rect, ToSk(clip_op), is_aa);
+}
+void DlSkCanvasDispatcher::clipRRect(const SkRRect& rrect,
+                                     ClipOp clip_op,
+                                     bool is_aa) {
+  canvas_->clipRRect(rrect, ToSk(clip_op), is_aa);
+}
+void DlSkCanvasDispatcher::clipPath(const SkPath& path,
+                                    ClipOp clip_op,
+                                    bool is_aa) {
+  canvas_->clipPath(path, ToSk(clip_op), is_aa);
+}
+
+void DlSkCanvasDispatcher::drawPaint() {
+  const SkPaint& sk_paint = paint();
+  SkImageFilter* filter = sk_paint.getImageFilter();
+  if (filter && !filter->asColorFilter(nullptr)) {
+    // drawPaint does an implicit saveLayer if an SkImageFilter is
+    // present that cannot be replaced by an SkColorFilter.
+    TRACE_EVENT0("flutter", "Canvas::saveLayer");
+  }
+  canvas_->drawPaint(sk_paint);
+}
+void DlSkCanvasDispatcher::drawColor(DlColor color, DlBlendMode mode) {
+  // SkCanvas::drawColor(SkColor) does the following conversion anyway
+  // We do it here manually to increase precision on applying opacity
+  SkColor4f color4f = SkColor4f::FromColor(color);
+  color4f.fA *= opacity();
+  canvas_->drawColor(color4f, ToSk(mode));
+}
+void DlSkCanvasDispatcher::drawLine(const SkPoint& p0, const SkPoint& p1) {
+  canvas_->drawLine(p0, p1, paint());
+}
+void DlSkCanvasDispatcher::drawRect(const SkRect& rect) {
+  canvas_->drawRect(rect, paint());
+}
+void DlSkCanvasDispatcher::drawOval(const SkRect& bounds) {
+  canvas_->drawOval(bounds, paint());
+}
+void DlSkCanvasDispatcher::drawCircle(const SkPoint& center, SkScalar radius) {
+  canvas_->drawCircle(center, radius, paint());
+}
+void DlSkCanvasDispatcher::drawRRect(const SkRRect& rrect) {
+  canvas_->drawRRect(rrect, paint());
+}
+void DlSkCanvasDispatcher::drawDRRect(const SkRRect& outer,
+                                      const SkRRect& inner) {
+  canvas_->drawDRRect(outer, inner, paint());
+}
+void DlSkCanvasDispatcher::drawPath(const SkPath& path) {
+  canvas_->drawPath(path, paint());
+}
+void DlSkCanvasDispatcher::drawArc(const SkRect& bounds,
+                                   SkScalar start,
+                                   SkScalar sweep,
+                                   bool useCenter) {
+  canvas_->drawArc(bounds, start, sweep, useCenter, paint());
+}
+void DlSkCanvasDispatcher::drawPoints(PointMode mode,
+                                      uint32_t count,
+                                      const SkPoint pts[]) {
+  canvas_->drawPoints(ToSk(mode), count, pts, paint());
+}
+void DlSkCanvasDispatcher::drawVertices(const DlVertices* vertices,
+                                        DlBlendMode mode) {
+  canvas_->drawVertices(ToSk(vertices), ToSk(mode), paint());
+}
+void DlSkCanvasDispatcher::drawImage(const sk_sp<DlImage> image,
+                                     const SkPoint point,
+                                     DlImageSampling sampling,
+                                     bool render_with_attributes) {
+  canvas_->drawImage(image ? image->skia_image() : nullptr, point.fX, point.fY,
+                     ToSk(sampling), safe_paint(render_with_attributes));
+}
+void DlSkCanvasDispatcher::drawImageRect(const sk_sp<DlImage> image,
+                                         const SkRect& src,
+                                         const SkRect& dst,
+                                         DlImageSampling sampling,
+                                         bool render_with_attributes,
+                                         SrcRectConstraint constraint) {
+  canvas_->drawImageRect(image ? image->skia_image() : nullptr, src, dst,
+                         ToSk(sampling), safe_paint(render_with_attributes),
+                         ToSk(constraint));
+}
+void DlSkCanvasDispatcher::drawImageNine(const sk_sp<DlImage> image,
+                                         const SkIRect& center,
+                                         const SkRect& dst,
+                                         DlFilterMode filter,
+                                         bool render_with_attributes) {
+  if (!image) {
+    return;
+  }
+  auto skia_image = image->skia_image();
+  if (!skia_image) {
+    return;
+  }
+  canvas_->drawImageNine(skia_image.get(), center, dst, ToSk(filter),
+                         safe_paint(render_with_attributes));
+}
+void DlSkCanvasDispatcher::drawAtlas(const sk_sp<DlImage> atlas,
+                                     const SkRSXform xform[],
+                                     const SkRect tex[],
+                                     const DlColor colors[],
+                                     int count,
+                                     DlBlendMode mode,
+                                     DlImageSampling sampling,
+                                     const SkRect* cullRect,
+                                     bool render_with_attributes) {
+  if (!atlas) {
+    return;
+  }
+  auto skia_atlas = atlas->skia_image();
+  if (!skia_atlas) {
+    return;
+  }
+  const SkColor* sk_colors = reinterpret_cast<const SkColor*>(colors);
+  canvas_->drawAtlas(skia_atlas.get(), xform, tex, sk_colors, count, ToSk(mode),
+                     ToSk(sampling), cullRect,
+                     safe_paint(render_with_attributes));
+}
+void DlSkCanvasDispatcher::drawDisplayList(
+    const sk_sp<DisplayList> display_list,
+    SkScalar opacity) {
+  const int restore_count = canvas_->getSaveCount();
+
+  // Compute combined opacity and figure out whether we can apply it
+  // during dispatch or if we need a saveLayer.
+  SkScalar combined_opacity = opacity * this->opacity();
+  if (combined_opacity < SK_Scalar1 &&
+      !display_list->can_apply_group_opacity()) {
+    TRACE_EVENT0("flutter", "Canvas::saveLayer");
+    canvas_->saveLayerAlphaf(&display_list->bounds(), combined_opacity);
+    combined_opacity = SK_Scalar1;
+  } else {
+    canvas_->save();
+  }
+
+  // Create a new CanvasDispatcher to isolate the actions of the
+  // display_list from the current environment.
+  DlSkCanvasDispatcher dispatcher(canvas_, combined_opacity);
+  if (display_list->rtree()) {
+    display_list->Dispatch(dispatcher, canvas_->getLocalClipBounds());
+  } else {
+    display_list->Dispatch(dispatcher);
+  }
+
+  // Restore canvas state to what it was before dispatching.
+  canvas_->restoreToCount(restore_count);
+}
+void DlSkCanvasDispatcher::drawTextBlob(const sk_sp<SkTextBlob> blob,
+                                        SkScalar x,
+                                        SkScalar y) {
+  canvas_->drawTextBlob(blob, x, y, paint());
+}
+
+void DlSkCanvasDispatcher::DrawShadow(SkCanvas* canvas,
+                                      const SkPath& path,
+                                      DlColor color,
+                                      float elevation,
+                                      bool transparentOccluder,
+                                      SkScalar dpr) {
+  const SkScalar kAmbientAlpha = 0.039f;
+  const SkScalar kSpotAlpha = 0.25f;
+
+  uint32_t flags = transparentOccluder
+                       ? SkShadowFlags::kTransparentOccluder_ShadowFlag
+                       : SkShadowFlags::kNone_ShadowFlag;
+  flags |= SkShadowFlags::kDirectionalLight_ShadowFlag;
+  SkColor in_ambient = SkColorSetA(color, kAmbientAlpha * SkColorGetA(color));
+  SkColor in_spot = SkColorSetA(color, kSpotAlpha * SkColorGetA(color));
+  SkColor ambient_color, spot_color;
+  SkShadowUtils::ComputeTonalColors(in_ambient, in_spot, &ambient_color,
+                                    &spot_color);
+  SkShadowUtils::DrawShadow(
+      canvas, path, SkPoint3::Make(0, 0, dpr * elevation),
+      SkPoint3::Make(0, -1, 1),
+      DlCanvas::kShadowLightRadius / DlCanvas::kShadowLightHeight,
+      ambient_color, spot_color, flags);
+}
+
+void DlSkCanvasDispatcher::drawShadow(const SkPath& path,
+                                      const DlColor color,
+                                      const SkScalar elevation,
+                                      bool transparent_occluder,
+                                      SkScalar dpr) {
+  DrawShadow(canvas_, path, color, elevation, transparent_occluder, dpr);
+}
+
+}  // namespace flutter
diff --git a/display_list/display_list_canvas_dispatcher.h b/display_list/skia/dl_sk_dispatcher.h
similarity index 85%
rename from display_list/display_list_canvas_dispatcher.h
rename to display_list/skia/dl_sk_dispatcher.h
index 993b5b4..1edf2d4 100644
--- a/display_list/display_list_canvas_dispatcher.h
+++ b/display_list/skia/dl_sk_dispatcher.h
@@ -6,9 +6,8 @@
 #define FLUTTER_DISPLAY_LIST_DISPLAY_LIST_CANVAS_DISPATCHER_H_
 
 #include "flutter/display_list/display_list.h"
-#include "flutter/display_list/display_list_blend_mode.h"
-#include "flutter/display_list/display_list_dispatcher.h"
-#include "flutter/display_list/display_list_utils.h"
+#include "flutter/display_list/dl_op_receiver.h"
+#include "flutter/display_list/skia/dl_sk_utils.h"
 #include "flutter/fml/macros.h"
 
 namespace flutter {
@@ -19,11 +18,10 @@
 ///
 /// Receives all methods on Dispatcher and sends them to an SkCanvas
 ///
-class DisplayListCanvasDispatcher : public virtual Dispatcher,
-                                    public SkPaintDispatchHelper {
+class DlSkCanvasDispatcher : public virtual DlOpReceiver,
+                             public SkPaintDispatchHelper {
  public:
-  explicit DisplayListCanvasDispatcher(SkCanvas* canvas,
-                                       SkScalar opacity = SK_Scalar1)
+  explicit DlSkCanvasDispatcher(SkCanvas* canvas, SkScalar opacity = SK_Scalar1)
       : SkPaintDispatchHelper(opacity),
         canvas_(canvas),
         original_transform_(canvas->getLocalToDevice()) {}
@@ -81,7 +79,7 @@
                      const SkRect& dst,
                      DlImageSampling sampling,
                      bool render_with_attributes,
-                     SkCanvas::SrcRectConstraint constraint) override;
+                     SrcRectConstraint constraint) override;
   void drawImageNine(const sk_sp<DlImage> image,
                      const SkIRect& center,
                      const SkRect& dst,
@@ -96,7 +94,8 @@
                  DlImageSampling sampling,
                  const SkRect* cullRect,
                  bool render_with_attributes) override;
-  void drawDisplayList(const sk_sp<DisplayList> display_list) override;
+  void drawDisplayList(const sk_sp<DisplayList> display_list,
+                       SkScalar opacity) override;
   void drawTextBlob(const sk_sp<SkTextBlob> blob,
                     SkScalar x,
                     SkScalar y) override;
@@ -106,11 +105,6 @@
                   bool transparent_occluder,
                   SkScalar dpr) override;
 
-  static SkRect ComputeShadowBounds(const SkPath& path,
-                                    float elevation,
-                                    SkScalar dpr,
-                                    const SkMatrix& ctm);
-
   static void DrawShadow(SkCanvas* canvas,
                          const SkPath& path,
                          DlColor color,
diff --git a/display_list/skia/dl_sk_utils.cc b/display_list/skia/dl_sk_utils.cc
new file mode 100644
index 0000000..3476ab3
--- /dev/null
+++ b/display_list/skia/dl_sk_utils.cc
@@ -0,0 +1,102 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/display_list/skia/dl_sk_utils.h"
+
+#include <math.h>
+#include <optional>
+#include <type_traits>
+
+#include "flutter/display_list/display_list_blend_mode.h"
+#include "flutter/display_list/skia/dl_sk_conversions.h"
+#include "flutter/fml/logging.h"
+
+namespace flutter {
+
+// clang-format off
+constexpr float kInvertColorMatrix[20] = {
+  -1.0,    0,    0, 1.0, 0,
+     0, -1.0,    0, 1.0, 0,
+     0,    0, -1.0, 1.0, 0,
+   1.0,  1.0,  1.0, 1.0, 0
+};
+// clang-format on
+
+void SkPaintDispatchHelper::save_opacity(SkScalar child_opacity) {
+  save_stack_.emplace_back(opacity_);
+  set_opacity(child_opacity);
+}
+void SkPaintDispatchHelper::restore_opacity() {
+  if (save_stack_.empty()) {
+    return;
+  }
+  set_opacity(save_stack_.back().opacity);
+  save_stack_.pop_back();
+}
+
+void SkPaintDispatchHelper::setAntiAlias(bool aa) {
+  paint_.setAntiAlias(aa);
+}
+void SkPaintDispatchHelper::setDither(bool dither) {
+  paint_.setDither(dither);
+}
+void SkPaintDispatchHelper::setInvertColors(bool invert) {
+  invert_colors_ = invert;
+  paint_.setColorFilter(makeColorFilter());
+}
+void SkPaintDispatchHelper::setStrokeCap(DlStrokeCap cap) {
+  paint_.setStrokeCap(ToSk(cap));
+}
+void SkPaintDispatchHelper::setStrokeJoin(DlStrokeJoin join) {
+  paint_.setStrokeJoin(ToSk(join));
+}
+void SkPaintDispatchHelper::setStyle(DlDrawStyle style) {
+  paint_.setStyle(ToSk(style));
+}
+void SkPaintDispatchHelper::setStrokeWidth(SkScalar width) {
+  paint_.setStrokeWidth(width);
+}
+void SkPaintDispatchHelper::setStrokeMiter(SkScalar limit) {
+  paint_.setStrokeMiter(limit);
+}
+void SkPaintDispatchHelper::setColor(DlColor color) {
+  current_color_ = color;
+  paint_.setColor(color);
+  if (has_opacity()) {
+    paint_.setAlphaf(paint_.getAlphaf() * opacity());
+  }
+}
+void SkPaintDispatchHelper::setBlendMode(DlBlendMode mode) {
+  paint_.setBlendMode(ToSk(mode));
+}
+void SkPaintDispatchHelper::setColorSource(const DlColorSource* source) {
+  paint_.setShader(ToSk(source));
+}
+void SkPaintDispatchHelper::setImageFilter(const DlImageFilter* filter) {
+  paint_.setImageFilter(ToSk(filter));
+}
+void SkPaintDispatchHelper::setColorFilter(const DlColorFilter* filter) {
+  sk_color_filter_ = ToSk(filter);
+  paint_.setColorFilter(makeColorFilter());
+}
+void SkPaintDispatchHelper::setPathEffect(const DlPathEffect* effect) {
+  paint_.setPathEffect(ToSk(effect));
+}
+void SkPaintDispatchHelper::setMaskFilter(const DlMaskFilter* filter) {
+  paint_.setMaskFilter(ToSk(filter));
+}
+
+sk_sp<SkColorFilter> SkPaintDispatchHelper::makeColorFilter() const {
+  if (!invert_colors_) {
+    return sk_color_filter_;
+  }
+  sk_sp<SkColorFilter> invert_filter =
+      SkColorFilters::Matrix(kInvertColorMatrix);
+  if (sk_color_filter_) {
+    invert_filter = invert_filter->makeComposed(sk_color_filter_);
+  }
+  return invert_filter;
+}
+
+}  // namespace flutter
diff --git a/display_list/skia/dl_sk_utils.h b/display_list/skia/dl_sk_utils.h
new file mode 100644
index 0000000..e61848d
--- /dev/null
+++ b/display_list/skia/dl_sk_utils.h
@@ -0,0 +1,85 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_DISPLAY_LIST_SKIA_DL_SK_UTILS_H_
+#define FLUTTER_DISPLAY_LIST_SKIA_DL_SK_UTILS_H_
+
+#include "flutter/display_list/dl_op_receiver.h"
+
+namespace flutter {
+
+// A utility class that will monitor the DlOpReceiver methods relating
+// to the rendering attributes and accumulate them into an SkPaint
+// which can be accessed at any time via paint().
+class SkPaintDispatchHelper : public virtual DlOpReceiver {
+ public:
+  SkPaintDispatchHelper(SkScalar opacity = SK_Scalar1)
+      : current_color_(SK_ColorBLACK), opacity_(opacity) {
+    if (opacity < SK_Scalar1) {
+      paint_.setAlphaf(opacity);
+    }
+  }
+
+  void setAntiAlias(bool aa) override;
+  void setDither(bool dither) override;
+  void setStyle(DlDrawStyle style) override;
+  void setColor(DlColor color) override;
+  void setStrokeWidth(SkScalar width) override;
+  void setStrokeMiter(SkScalar limit) override;
+  void setStrokeCap(DlStrokeCap cap) override;
+  void setStrokeJoin(DlStrokeJoin join) override;
+  void setColorSource(const DlColorSource* source) override;
+  void setColorFilter(const DlColorFilter* filter) override;
+  void setInvertColors(bool invert) override;
+  void setBlendMode(DlBlendMode mode) override;
+  void setPathEffect(const DlPathEffect* effect) override;
+  void setMaskFilter(const DlMaskFilter* filter) override;
+  void setImageFilter(const DlImageFilter* filter) override;
+
+  const SkPaint& paint() { return paint_; }
+
+  /// Returns the current opacity attribute which is used to reduce
+  /// the alpha of all setColor calls encountered in the streeam
+  SkScalar opacity() { return opacity_; }
+  /// Returns the combined opacity that includes both the current
+  /// opacity attribute and the alpha of the most recent color.
+  /// The most recently set color will have combined the two and
+  /// stored the combined value in the alpha of the paint.
+  SkScalar combined_opacity() { return paint_.getAlphaf(); }
+  /// Returns true iff the current opacity attribute is not opaque,
+  /// irrespective of the alpha of the current color
+  bool has_opacity() { return opacity_ < SK_Scalar1; }
+
+ protected:
+  void save_opacity(SkScalar opacity_for_children);
+  void restore_opacity();
+
+ private:
+  SkPaint paint_;
+  bool invert_colors_ = false;
+  sk_sp<SkColorFilter> sk_color_filter_;
+
+  sk_sp<SkColorFilter> makeColorFilter() const;
+
+  struct SaveInfo {
+    SaveInfo(SkScalar opacity) : opacity(opacity) {}
+
+    SkScalar opacity;
+  };
+  std::vector<SaveInfo> save_stack_;
+
+  void set_opacity(SkScalar opacity) {
+    if (opacity_ != opacity) {
+      opacity_ = opacity;
+      setColor(current_color_);
+    }
+  }
+
+  SkColor current_color_;
+  SkScalar opacity_;
+};
+
+}  // namespace flutter
+
+#endif  // FLUTTER_DISPLAY_LIST_SKIA_DL_SK_UTILS_H_
diff --git a/display_list/display_list_utils_unittests.cc b/display_list/skia/dl_sk_utils_unittests.cc
similarity index 89%
rename from display_list/display_list_utils_unittests.cc
rename to display_list/skia/dl_sk_utils_unittests.cc
index 9f77c94..ccab201 100644
--- a/display_list/display_list_utils_unittests.cc
+++ b/display_list/skia/dl_sk_utils_unittests.cc
@@ -3,12 +3,13 @@
 // found in the LICENSE file.
 
 #include "flutter/display_list/display_list_utils.h"
+#include "flutter/display_list/skia/dl_sk_utils.h"
 #include "gtest/gtest.h"
 
 namespace flutter {
 namespace testing {
 
-class MockDispatchHelper final : public virtual Dispatcher,
+class MockDispatchHelper final : public virtual DlOpReceiver,
                                  public SkPaintDispatchHelper,
                                  public IgnoreClipDispatchHelper,
                                  public IgnoreTransformDispatchHelper,
diff --git a/display_list/testing/dl_test_snippets.cc b/display_list/testing/dl_test_snippets.cc
index 741b0db..2116cf9 100644
--- a/display_list/testing/dl_test_snippets.cc
+++ b/display_list/testing/dl_test_snippets.cc
@@ -4,34 +4,35 @@
 
 #include "flutter/display_list/testing/dl_test_snippets.h"
 #include "flutter/display_list/display_list_builder.h"
+#include "flutter/display_list/dl_op_receiver.h"
 
 namespace flutter {
 namespace testing {
 
 sk_sp<DisplayList> GetSampleDisplayList() {
   DisplayListBuilder builder(SkRect::MakeWH(150, 100));
-  builder.setColor(SK_ColorRED);
-  builder.drawRect(SkRect::MakeXYWH(10, 10, 80, 80));
+  builder.DrawRect(SkRect::MakeXYWH(10, 10, 80, 80), DlPaint(DlColor::kRed()));
   return builder.Build();
 }
 
 sk_sp<DisplayList> GetSampleNestedDisplayList() {
   DisplayListBuilder builder(SkRect::MakeWH(150, 100));
+  DlPaint paint;
   for (int y = 10; y <= 60; y += 10) {
     for (int x = 10; x <= 60; x += 10) {
-      builder.setColor(((x + y) % 20) == 10 ? SK_ColorRED : SK_ColorBLUE);
-      builder.drawRect(SkRect::MakeXYWH(x, y, 80, 80));
+      paint.setColor(((x + y) % 20) == 10 ? SK_ColorRED : SK_ColorBLUE);
+      builder.DrawRect(SkRect::MakeXYWH(x, y, 80, 80), paint);
     }
   }
   DisplayListBuilder outer_builder(SkRect::MakeWH(150, 100));
-  outer_builder.drawDisplayList(builder.Build());
+  outer_builder.DrawDisplayList(builder.Build());
   return outer_builder.Build();
 }
 
 sk_sp<DisplayList> GetSampleDisplayList(int ops) {
   DisplayListBuilder builder(SkRect::MakeWH(150, 100));
   for (int i = 0; i < ops; i++) {
-    builder.drawColor(SK_ColorRED, DlBlendMode::kSrc);
+    builder.DrawColor(DlColor::kRed(), DlBlendMode::kSrc);
   }
   return builder.Build();
 }
@@ -44,192 +45,148 @@
   return {
       {"SetAntiAlias",
        {
-           {0, 8, 0, 0, [](DisplayListBuilder& b) { b.setAntiAlias(true); }},
-           {0, 0, 0, 0, [](DisplayListBuilder& b) { b.setAntiAlias(false); }},
+           {0, 8, 0, 0, [](DlOpReceiver& r) { r.setAntiAlias(true); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.setAntiAlias(false); }},
        }},
       {"SetDither",
        {
-           {0, 8, 0, 0, [](DisplayListBuilder& b) { b.setDither(true); }},
-           {0, 0, 0, 0, [](DisplayListBuilder& b) { b.setDither(false); }},
+           {0, 8, 0, 0, [](DlOpReceiver& r) { r.setDither(true); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.setDither(false); }},
        }},
       {"SetInvertColors",
        {
-           {0, 8, 0, 0, [](DisplayListBuilder& b) { b.setInvertColors(true); }},
-           {0, 0, 0, 0,
-            [](DisplayListBuilder& b) { b.setInvertColors(false); }},
+           {0, 8, 0, 0, [](DlOpReceiver& r) { r.setInvertColors(true); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.setInvertColors(false); }},
        }},
       {"SetStrokeCap",
        {
            {0, 8, 0, 0,
-            [](DisplayListBuilder& b) { b.setStrokeCap(DlStrokeCap::kRound); }},
+            [](DlOpReceiver& r) { r.setStrokeCap(DlStrokeCap::kRound); }},
            {0, 8, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setStrokeCap(DlStrokeCap::kSquare);
-            }},
+            [](DlOpReceiver& r) { r.setStrokeCap(DlStrokeCap::kSquare); }},
            {0, 0, 0, 0,
-            [](DisplayListBuilder& b) { b.setStrokeCap(DlStrokeCap::kButt); }},
+            [](DlOpReceiver& r) { r.setStrokeCap(DlStrokeCap::kButt); }},
        }},
       {"SetStrokeJoin",
        {
            {0, 8, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setStrokeJoin(DlStrokeJoin::kBevel);
-            }},
+            [](DlOpReceiver& r) { r.setStrokeJoin(DlStrokeJoin::kBevel); }},
            {0, 8, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setStrokeJoin(DlStrokeJoin::kRound);
-            }},
+            [](DlOpReceiver& r) { r.setStrokeJoin(DlStrokeJoin::kRound); }},
            {0, 0, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setStrokeJoin(DlStrokeJoin::kMiter);
-            }},
+            [](DlOpReceiver& r) { r.setStrokeJoin(DlStrokeJoin::kMiter); }},
        }},
       {"SetStyle",
        {
            {0, 8, 0, 0,
-            [](DisplayListBuilder& b) { b.setStyle(DlDrawStyle::kStroke); }},
+            [](DlOpReceiver& r) { r.setStyle(DlDrawStyle::kStroke); }},
            {0, 8, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setStyle(DlDrawStyle::kStrokeAndFill);
-            }},
+            [](DlOpReceiver& r) { r.setStyle(DlDrawStyle::kStrokeAndFill); }},
            {0, 0, 0, 0,
-            [](DisplayListBuilder& b) { b.setStyle(DlDrawStyle::kFill); }},
+            [](DlOpReceiver& r) { r.setStyle(DlDrawStyle::kFill); }},
        }},
       {"SetStrokeWidth",
        {
-           {0, 8, 0, 0, [](DisplayListBuilder& b) { b.setStrokeWidth(1.0); }},
-           {0, 8, 0, 0, [](DisplayListBuilder& b) { b.setStrokeWidth(5.0); }},
-           {0, 0, 0, 0, [](DisplayListBuilder& b) { b.setStrokeWidth(0.0); }},
+           {0, 8, 0, 0, [](DlOpReceiver& r) { r.setStrokeWidth(1.0); }},
+           {0, 8, 0, 0, [](DlOpReceiver& r) { r.setStrokeWidth(5.0); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.setStrokeWidth(0.0); }},
        }},
       {"SetStrokeMiter",
        {
-           {0, 8, 0, 0, [](DisplayListBuilder& b) { b.setStrokeMiter(0.0); }},
-           {0, 8, 0, 0, [](DisplayListBuilder& b) { b.setStrokeMiter(5.0); }},
-           {0, 0, 0, 0, [](DisplayListBuilder& b) { b.setStrokeMiter(4.0); }},
+           {0, 8, 0, 0, [](DlOpReceiver& r) { r.setStrokeMiter(0.0); }},
+           {0, 8, 0, 0, [](DlOpReceiver& r) { r.setStrokeMiter(5.0); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.setStrokeMiter(4.0); }},
        }},
       {"SetColor",
        {
-           {0, 8, 0, 0,
-            [](DisplayListBuilder& b) { b.setColor(SK_ColorGREEN); }},
-           {0, 8, 0, 0,
-            [](DisplayListBuilder& b) { b.setColor(SK_ColorBLUE); }},
-           {0, 0, 0, 0,
-            [](DisplayListBuilder& b) { b.setColor(SK_ColorBLACK); }},
+           {0, 8, 0, 0, [](DlOpReceiver& r) { r.setColor(SK_ColorGREEN); }},
+           {0, 8, 0, 0, [](DlOpReceiver& r) { r.setColor(SK_ColorBLUE); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.setColor(SK_ColorBLACK); }},
        }},
       {"SetBlendMode",
        {
            {0, 8, 0, 0,
-            [](DisplayListBuilder& b) { b.setBlendMode(DlBlendMode::kSrcIn); }},
+            [](DlOpReceiver& r) { r.setBlendMode(DlBlendMode::kSrcIn); }},
            {0, 8, 0, 0,
-            [](DisplayListBuilder& b) { b.setBlendMode(DlBlendMode::kDstIn); }},
+            [](DlOpReceiver& r) { r.setBlendMode(DlBlendMode::kDstIn); }},
            {0, 0, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setBlendMode(DlBlendMode::kSrcOver);
-            }},
+            [](DlOpReceiver& r) { r.setBlendMode(DlBlendMode::kSrcOver); }},
        }},
       {"SetColorSource",
        {
            {0, 96, 0, 0,
-            [](DisplayListBuilder& b) { b.setColorSource(&kTestSource1); }},
+            [](DlOpReceiver& r) { r.setColorSource(&kTestSource1); }},
            // stop_count * (sizeof(float) + sizeof(uint32_t)) = 80
            {0, 80 + 6 * 4, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setColorSource(kTestSource2.get());
-            }},
+            [](DlOpReceiver& r) { r.setColorSource(kTestSource2.get()); }},
            {0, 80 + 6 * 4, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setColorSource(kTestSource3.get());
-            }},
+            [](DlOpReceiver& r) { r.setColorSource(kTestSource3.get()); }},
            {0, 88 + 6 * 4, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setColorSource(kTestSource4.get());
-            }},
+            [](DlOpReceiver& r) { r.setColorSource(kTestSource4.get()); }},
            {0, 80 + 6 * 4, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setColorSource(kTestSource5.get());
-            }},
-           {0, 0, 0, 0,
-            [](DisplayListBuilder& b) { b.setColorSource(nullptr); }},
+            [](DlOpReceiver& r) { r.setColorSource(kTestSource5.get()); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.setColorSource(nullptr); }},
        }},
       {"SetImageFilter",
        {
            {0, 32, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestBlurImageFilter1);
-            }},
+            [](DlOpReceiver& r) { r.setImageFilter(&kTestBlurImageFilter1); }},
            {0, 32, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestBlurImageFilter2);
-            }},
+            [](DlOpReceiver& r) { r.setImageFilter(&kTestBlurImageFilter2); }},
            {0, 32, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestBlurImageFilter3);
-            }},
+            [](DlOpReceiver& r) { r.setImageFilter(&kTestBlurImageFilter3); }},
            {0, 32, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestBlurImageFilter4);
+            [](DlOpReceiver& r) { r.setImageFilter(&kTestBlurImageFilter4); }},
+           {0, 24, 0, 0,
+            [](DlOpReceiver& r) {
+              r.setImageFilter(&kTestDilateImageFilter1);
             }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestDilateImageFilter1);
+            [](DlOpReceiver& r) {
+              r.setImageFilter(&kTestDilateImageFilter2);
             }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestDilateImageFilter2);
+            [](DlOpReceiver& r) {
+              r.setImageFilter(&kTestDilateImageFilter3);
             }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestDilateImageFilter3);
-            }},
+            [](DlOpReceiver& r) { r.setImageFilter(&kTestErodeImageFilter1); }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestErodeImageFilter1);
-            }},
+            [](DlOpReceiver& r) { r.setImageFilter(&kTestErodeImageFilter2); }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestErodeImageFilter2);
-            }},
-           {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestErodeImageFilter3);
+            [](DlOpReceiver& r) { r.setImageFilter(&kTestErodeImageFilter3); }},
+           {0, 64, 0, 0,
+            [](DlOpReceiver& r) {
+              r.setImageFilter(&kTestMatrixImageFilter1);
             }},
            {0, 64, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestMatrixImageFilter1);
+            [](DlOpReceiver& r) {
+              r.setImageFilter(&kTestMatrixImageFilter2);
             }},
            {0, 64, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestMatrixImageFilter2);
-            }},
-           {0, 64, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestMatrixImageFilter3);
+            [](DlOpReceiver& r) {
+              r.setImageFilter(&kTestMatrixImageFilter3);
             }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestComposeImageFilter1);
+            [](DlOpReceiver& r) {
+              r.setImageFilter(&kTestComposeImageFilter1);
             }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestComposeImageFilter2);
+            [](DlOpReceiver& r) {
+              r.setImageFilter(&kTestComposeImageFilter2);
             }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestComposeImageFilter3);
+            [](DlOpReceiver& r) {
+              r.setImageFilter(&kTestComposeImageFilter3);
             }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestCFImageFilter1);
-            }},
+            [](DlOpReceiver& r) { r.setImageFilter(&kTestCFImageFilter1); }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(&kTestCFImageFilter2);
-            }},
-           {0, 0, 0, 0,
-            [](DisplayListBuilder& b) { b.setImageFilter(nullptr); }},
+            [](DlOpReceiver& r) { r.setImageFilter(&kTestCFImageFilter2); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.setImageFilter(nullptr); }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setImageFilter(
+            [](DlOpReceiver& r) {
+              r.setImageFilter(
                   kTestBlurImageFilter1
                       .makeWithLocalMatrix(SkMatrix::Translate(2, 2))
                       .get());
@@ -238,64 +195,51 @@
       {"SetColorFilter",
        {
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setColorFilter(&kTestBlendColorFilter1);
-            }},
+            [](DlOpReceiver& r) { r.setColorFilter(&kTestBlendColorFilter1); }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setColorFilter(&kTestBlendColorFilter2);
-            }},
+            [](DlOpReceiver& r) { r.setColorFilter(&kTestBlendColorFilter2); }},
            {0, 24, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setColorFilter(&kTestBlendColorFilter3);
+            [](DlOpReceiver& r) { r.setColorFilter(&kTestBlendColorFilter3); }},
+           {0, 96, 0, 0,
+            [](DlOpReceiver& r) {
+              r.setColorFilter(&kTestMatrixColorFilter1);
             }},
            {0, 96, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setColorFilter(&kTestMatrixColorFilter1);
-            }},
-           {0, 96, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setColorFilter(&kTestMatrixColorFilter2);
+            [](DlOpReceiver& r) {
+              r.setColorFilter(&kTestMatrixColorFilter2);
             }},
            {0, 16, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setColorFilter(DlSrgbToLinearGammaColorFilter::instance.get());
+            [](DlOpReceiver& r) {
+              r.setColorFilter(DlSrgbToLinearGammaColorFilter::instance.get());
             }},
            {0, 16, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setColorFilter(DlLinearToSrgbGammaColorFilter::instance.get());
+            [](DlOpReceiver& r) {
+              r.setColorFilter(DlLinearToSrgbGammaColorFilter::instance.get());
             }},
-           {0, 0, 0, 0,
-            [](DisplayListBuilder& b) { b.setColorFilter(nullptr); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.setColorFilter(nullptr); }},
        }},
       {"SetPathEffect",
        {
            // sizeof(DlDashPathEffect) + 2 * sizeof(SkScalar)
            {0, 32, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setPathEffect(kTestPathEffect1.get());
-            }},
+            [](DlOpReceiver& r) { r.setPathEffect(kTestPathEffect1.get()); }},
            {0, 32, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.setPathEffect(kTestPathEffect2.get());
-            }},
-           {0, 0, 0, 0,
-            [](DisplayListBuilder& b) { b.setPathEffect(nullptr); }},
+            [](DlOpReceiver& r) { r.setPathEffect(kTestPathEffect2.get()); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.setPathEffect(nullptr); }},
        }},
       {"SetMaskFilter",
        {
            {0, 32, 0, 0,
-            [](DisplayListBuilder& b) { b.setMaskFilter(&kTestMaskFilter1); }},
+            [](DlOpReceiver& r) { r.setMaskFilter(&kTestMaskFilter1); }},
            {0, 32, 0, 0,
-            [](DisplayListBuilder& b) { b.setMaskFilter(&kTestMaskFilter2); }},
+            [](DlOpReceiver& r) { r.setMaskFilter(&kTestMaskFilter2); }},
            {0, 32, 0, 0,
-            [](DisplayListBuilder& b) { b.setMaskFilter(&kTestMaskFilter3); }},
+            [](DlOpReceiver& r) { r.setMaskFilter(&kTestMaskFilter3); }},
            {0, 32, 0, 0,
-            [](DisplayListBuilder& b) { b.setMaskFilter(&kTestMaskFilter4); }},
+            [](DlOpReceiver& r) { r.setMaskFilter(&kTestMaskFilter4); }},
            {0, 32, 0, 0,
-            [](DisplayListBuilder& b) { b.setMaskFilter(&kTestMaskFilter5); }},
-           {0, 0, 0, 0,
-            [](DisplayListBuilder& b) { b.setMaskFilter(nullptr); }},
+            [](DlOpReceiver& r) { r.setMaskFilter(&kTestMaskFilter5); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.setMaskFilter(nullptr); }},
        }},
   };
 }
@@ -305,13 +249,13 @@
       {"Save(Layer)+Restore",
        {
            {5, 112, 5, 112,
-            [](DisplayListBuilder& b) {
-              b.saveLayer(nullptr, SaveLayerOptions::kNoAttributes,
+            [](DlOpReceiver& r) {
+              r.saveLayer(nullptr, SaveLayerOptions::kNoAttributes,
                           &kTestCFImageFilter1);
-              b.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
-              b.drawRect({5, 5, 15, 15});
-              b.drawRect({10, 10, 20, 20});
-              b.restore();
+              r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
+              r.drawRect({5, 5, 15, 15});
+              r.drawRect({10, 10, 20, 20});
+              r.restore();
             }},
            // There are many reasons that save and restore can elide content,
            // including whether or not there are any draw operations between
@@ -321,80 +265,80 @@
            // cases we include at least one clip operation and 2 overlapping
            // rendering primitives between each save/restore pair.
            {5, 96, 5, 96,
-            [](DisplayListBuilder& b) {
-              b.save();
-              b.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
-              b.drawRect({5, 5, 15, 15});
-              b.drawRect({10, 10, 20, 20});
-              b.restore();
+            [](DlOpReceiver& r) {
+              r.save();
+              r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
+              r.drawRect({5, 5, 15, 15});
+              r.drawRect({10, 10, 20, 20});
+              r.restore();
             }},
            {5, 96, 5, 96,
-            [](DisplayListBuilder& b) {
-              b.saveLayer(nullptr, false);
-              b.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
-              b.drawRect({5, 5, 15, 15});
-              b.drawRect({10, 10, 20, 20});
-              b.restore();
+            [](DlOpReceiver& r) {
+              r.saveLayer(nullptr, SaveLayerOptions::kNoAttributes);
+              r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
+              r.drawRect({5, 5, 15, 15});
+              r.drawRect({10, 10, 20, 20});
+              r.restore();
             }},
            {5, 96, 5, 96,
-            [](DisplayListBuilder& b) {
-              b.saveLayer(nullptr, true);
-              b.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
-              b.drawRect({5, 5, 15, 15});
-              b.drawRect({10, 10, 20, 20});
-              b.restore();
+            [](DlOpReceiver& r) {
+              r.saveLayer(nullptr, SaveLayerOptions::kWithAttributes);
+              r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
+              r.drawRect({5, 5, 15, 15});
+              r.drawRect({10, 10, 20, 20});
+              r.restore();
             }},
            {5, 112, 5, 112,
-            [](DisplayListBuilder& b) {
-              b.saveLayer(&kTestBounds, false);
-              b.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
-              b.drawRect({5, 5, 15, 15});
-              b.drawRect({10, 10, 20, 20});
-              b.restore();
+            [](DlOpReceiver& r) {
+              r.saveLayer(&kTestBounds, SaveLayerOptions::kNoAttributes);
+              r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
+              r.drawRect({5, 5, 15, 15});
+              r.drawRect({10, 10, 20, 20});
+              r.restore();
             }},
            {5, 112, 5, 112,
-            [](DisplayListBuilder& b) {
-              b.saveLayer(&kTestBounds, true);
-              b.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
-              b.drawRect({5, 5, 15, 15});
-              b.drawRect({10, 10, 20, 20});
-              b.restore();
+            [](DlOpReceiver& r) {
+              r.saveLayer(&kTestBounds, SaveLayerOptions::kWithAttributes);
+              r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
+              r.drawRect({5, 5, 15, 15});
+              r.drawRect({10, 10, 20, 20});
+              r.restore();
             }},
            // backdrop variants - using the TestCFImageFilter because it can be
            // reconstituted in the DL->SkCanvas->DL stream
-           // {5, 104, 5, 104, [](DisplayListBuilder& b) {
-           //   b.saveLayer(nullptr, SaveLayerOptions::kNoAttributes,
-           //   &kTestCFImageFilter1); b.clipRect({0, 0, 25, 25},
-           //   SkClipOp::kIntersect, true); b.drawRect({5, 5, 15, 15});
-           //   b.drawRect({10, 10, 20, 20});
-           //   b.restore();
+           // {5, 104, 5, 104, [](DlOpReceiver& r) {
+           //   r.saveLayer(nullptr, SaveLayerOptions::kNoAttributes,
+           //   &kTestCFImageFilter1); r.clipRect({0, 0, 25, 25},
+           //   SkClipOp::kIntersect, true); r.drawRect({5, 5, 15, 15});
+           //   r.drawRect({10, 10, 20, 20});
+           //   r.restore();
            // }},
            {5, 112, 5, 112,
-            [](DisplayListBuilder& b) {
-              b.saveLayer(nullptr, SaveLayerOptions::kWithAttributes,
+            [](DlOpReceiver& r) {
+              r.saveLayer(nullptr, SaveLayerOptions::kWithAttributes,
                           &kTestCFImageFilter1);
-              b.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
-              b.drawRect({5, 5, 15, 15});
-              b.drawRect({10, 10, 20, 20});
-              b.restore();
+              r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
+              r.drawRect({5, 5, 15, 15});
+              r.drawRect({10, 10, 20, 20});
+              r.restore();
             }},
            {5, 128, 5, 128,
-            [](DisplayListBuilder& b) {
-              b.saveLayer(&kTestBounds, SaveLayerOptions::kNoAttributes,
+            [](DlOpReceiver& r) {
+              r.saveLayer(&kTestBounds, SaveLayerOptions::kNoAttributes,
                           &kTestCFImageFilter1);
-              b.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
-              b.drawRect({5, 5, 15, 15});
-              b.drawRect({10, 10, 20, 20});
-              b.restore();
+              r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
+              r.drawRect({5, 5, 15, 15});
+              r.drawRect({10, 10, 20, 20});
+              r.restore();
             }},
            {5, 128, 5, 128,
-            [](DisplayListBuilder& b) {
-              b.saveLayer(&kTestBounds, SaveLayerOptions::kWithAttributes,
+            [](DlOpReceiver& r) {
+              r.saveLayer(&kTestBounds, SaveLayerOptions::kWithAttributes,
                           &kTestCFImageFilter1);
-              b.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
-              b.drawRect({5, 5, 15, 15});
-              b.drawRect({10, 10, 20, 20});
-              b.restore();
+              r.clipRect({0, 0, 25, 25}, DlCanvas::ClipOp::kIntersect, true);
+              r.drawRect({5, 5, 15, 15});
+              r.drawRect({10, 10, 20, 20});
+              r.restore();
             }},
        }},
   };
@@ -405,65 +349,61 @@
       {"Translate",
        {
            // cv.translate(0, 0) is ignored
-           {1, 16, 1, 16, [](DisplayListBuilder& b) { b.translate(10, 10); }},
-           {1, 16, 1, 16, [](DisplayListBuilder& b) { b.translate(10, 15); }},
-           {1, 16, 1, 16, [](DisplayListBuilder& b) { b.translate(15, 10); }},
-           {0, 0, 0, 0, [](DisplayListBuilder& b) { b.translate(0, 0); }},
+           {1, 16, 1, 16, [](DlOpReceiver& r) { r.translate(10, 10); }},
+           {1, 16, 1, 16, [](DlOpReceiver& r) { r.translate(10, 15); }},
+           {1, 16, 1, 16, [](DlOpReceiver& r) { r.translate(15, 10); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.translate(0, 0); }},
        }},
       {"Scale",
        {
            // cv.scale(1, 1) is ignored
-           {1, 16, 1, 16, [](DisplayListBuilder& b) { b.scale(2, 2); }},
-           {1, 16, 1, 16, [](DisplayListBuilder& b) { b.scale(2, 3); }},
-           {1, 16, 1, 16, [](DisplayListBuilder& b) { b.scale(3, 2); }},
-           {0, 0, 0, 0, [](DisplayListBuilder& b) { b.scale(1, 1); }},
+           {1, 16, 1, 16, [](DlOpReceiver& r) { r.scale(2, 2); }},
+           {1, 16, 1, 16, [](DlOpReceiver& r) { r.scale(2, 3); }},
+           {1, 16, 1, 16, [](DlOpReceiver& r) { r.scale(3, 2); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.scale(1, 1); }},
        }},
       {"Rotate",
        {
            // cv.rotate(0) is ignored, otherwise expressed as concat(rotmatrix)
-           {1, 8, 1, 32, [](DisplayListBuilder& b) { b.rotate(30); }},
-           {1, 8, 1, 32, [](DisplayListBuilder& b) { b.rotate(45); }},
-           {0, 0, 0, 0, [](DisplayListBuilder& b) { b.rotate(0); }},
-           {0, 0, 0, 0, [](DisplayListBuilder& b) { b.rotate(360); }},
+           {1, 8, 1, 32, [](DlOpReceiver& r) { r.rotate(30); }},
+           {1, 8, 1, 32, [](DlOpReceiver& r) { r.rotate(45); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.rotate(0); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.rotate(360); }},
        }},
       {"Skew",
        {
            // cv.skew(0, 0) is ignored, otherwise expressed as
            // concat(skewmatrix)
-           {1, 16, 1, 32, [](DisplayListBuilder& b) { b.skew(0.1, 0.1); }},
-           {1, 16, 1, 32, [](DisplayListBuilder& b) { b.skew(0.1, 0.2); }},
-           {1, 16, 1, 32, [](DisplayListBuilder& b) { b.skew(0.2, 0.1); }},
-           {0, 0, 0, 0, [](DisplayListBuilder& b) { b.skew(0, 0); }},
+           {1, 16, 1, 32, [](DlOpReceiver& r) { r.skew(0.1, 0.1); }},
+           {1, 16, 1, 32, [](DlOpReceiver& r) { r.skew(0.1, 0.2); }},
+           {1, 16, 1, 32, [](DlOpReceiver& r) { r.skew(0.2, 0.1); }},
+           {0, 0, 0, 0, [](DlOpReceiver& r) { r.skew(0, 0); }},
        }},
       {"Transform2DAffine",
        {
            {1, 32, 1, 32,
-            [](DisplayListBuilder& b) {
-              b.transform2DAffine(0, 1, 12, 1, 0, 33);
-            }},
-           // b.transform(identity) is ignored
+            [](DlOpReceiver& r) { r.transform2DAffine(0, 1, 12, 1, 0, 33); }},
+           // r.transform(identity) is ignored
            {0, 0, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.transform2DAffine(1, 0, 0, 0, 1, 0);
-            }},
+            [](DlOpReceiver& r) { r.transform2DAffine(1, 0, 0, 0, 1, 0); }},
        }},
       {"TransformFullPerspective",
        {
            {1, 72, 1, 72,
-            [](DisplayListBuilder& b) {
-              b.transformFullPerspective(0, 1, 0, 12, 1, 0, 0, 33, 3, 2, 5, 29,
+            [](DlOpReceiver& r) {
+              r.transformFullPerspective(0, 1, 0, 12, 1, 0, 0, 33, 3, 2, 5, 29,
                                          0, 0, 0, 12);
             }},
-           // b.transform(2D affine) is reduced to 2x3
+           // r.transform(2D affine) is reduced to 2x3
            {1, 32, 1, 32,
-            [](DisplayListBuilder& b) {
-              b.transformFullPerspective(2, 1, 0, 4, 1, 3, 0, 5, 0, 0, 1, 0, 0,
+            [](DlOpReceiver& r) {
+              r.transformFullPerspective(2, 1, 0, 4, 1, 3, 0, 5, 0, 0, 1, 0, 0,
                                          0, 0, 1);
             }},
-           // b.transform(identity) is ignored
+           // r.transform(identity) is ignored
            {0, 0, 0, 0,
-            [](DisplayListBuilder& b) {
-              b.transformFullPerspective(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
+            [](DlOpReceiver& r) {
+              r.transformFullPerspective(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
                                          0, 0, 1);
             }},
        }},
@@ -475,86 +415,86 @@
       {"ClipRect",
        {
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipRect(kTestBounds, DlCanvas::ClipOp::kIntersect, true);
+            [](DlOpReceiver& r) {
+              r.clipRect(kTestBounds, DlCanvas::ClipOp::kIntersect, true);
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipRect(kTestBounds.makeOffset(1, 1),
+            [](DlOpReceiver& r) {
+              r.clipRect(kTestBounds.makeOffset(1, 1),
                          DlCanvas::ClipOp::kIntersect, true);
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipRect(kTestBounds, DlCanvas::ClipOp::kIntersect, false);
+            [](DlOpReceiver& r) {
+              r.clipRect(kTestBounds, DlCanvas::ClipOp::kIntersect, false);
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipRect(kTestBounds, DlCanvas::ClipOp::kDifference, true);
+            [](DlOpReceiver& r) {
+              r.clipRect(kTestBounds, DlCanvas::ClipOp::kDifference, true);
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipRect(kTestBounds, DlCanvas::ClipOp::kDifference, false);
+            [](DlOpReceiver& r) {
+              r.clipRect(kTestBounds, DlCanvas::ClipOp::kDifference, false);
             }},
        }},
       {"ClipRRect",
        {
            {1, 64, 1, 64,
-            [](DisplayListBuilder& b) {
-              b.clipRRect(kTestRRect, DlCanvas::ClipOp::kIntersect, true);
+            [](DlOpReceiver& r) {
+              r.clipRRect(kTestRRect, DlCanvas::ClipOp::kIntersect, true);
             }},
            {1, 64, 1, 64,
-            [](DisplayListBuilder& b) {
-              b.clipRRect(kTestRRect.makeOffset(1, 1),
+            [](DlOpReceiver& r) {
+              r.clipRRect(kTestRRect.makeOffset(1, 1),
                           DlCanvas::ClipOp::kIntersect, true);
             }},
            {1, 64, 1, 64,
-            [](DisplayListBuilder& b) {
-              b.clipRRect(kTestRRect, DlCanvas::ClipOp::kIntersect, false);
+            [](DlOpReceiver& r) {
+              r.clipRRect(kTestRRect, DlCanvas::ClipOp::kIntersect, false);
             }},
            {1, 64, 1, 64,
-            [](DisplayListBuilder& b) {
-              b.clipRRect(kTestRRect, DlCanvas::ClipOp::kDifference, true);
+            [](DlOpReceiver& r) {
+              r.clipRRect(kTestRRect, DlCanvas::ClipOp::kDifference, true);
             }},
            {1, 64, 1, 64,
-            [](DisplayListBuilder& b) {
-              b.clipRRect(kTestRRect, DlCanvas::ClipOp::kDifference, false);
+            [](DlOpReceiver& r) {
+              r.clipRRect(kTestRRect, DlCanvas::ClipOp::kDifference, false);
             }},
        }},
       {"ClipPath",
        {
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipPath(kTestPath1, DlCanvas::ClipOp::kIntersect, true);
+            [](DlOpReceiver& r) {
+              r.clipPath(kTestPath1, DlCanvas::ClipOp::kIntersect, true);
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipPath(kTestPath2, DlCanvas::ClipOp::kIntersect, true);
+            [](DlOpReceiver& r) {
+              r.clipPath(kTestPath2, DlCanvas::ClipOp::kIntersect, true);
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipPath(kTestPath3, DlCanvas::ClipOp::kIntersect, true);
+            [](DlOpReceiver& r) {
+              r.clipPath(kTestPath3, DlCanvas::ClipOp::kIntersect, true);
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipPath(kTestPath1, DlCanvas::ClipOp::kIntersect, false);
+            [](DlOpReceiver& r) {
+              r.clipPath(kTestPath1, DlCanvas::ClipOp::kIntersect, false);
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipPath(kTestPath1, DlCanvas::ClipOp::kDifference, true);
+            [](DlOpReceiver& r) {
+              r.clipPath(kTestPath1, DlCanvas::ClipOp::kDifference, true);
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipPath(kTestPath1, DlCanvas::ClipOp::kDifference, false);
+            [](DlOpReceiver& r) {
+              r.clipPath(kTestPath1, DlCanvas::ClipOp::kDifference, false);
             }},
            // clipPath(rect) becomes clipRect
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.clipPath(kTestPathRect, DlCanvas::ClipOp::kIntersect, true);
+            [](DlOpReceiver& r) {
+              r.clipPath(kTestPathRect, DlCanvas::ClipOp::kIntersect, true);
             }},
            // clipPath(oval) becomes clipRRect
            {1, 64, 1, 64,
-            [](DisplayListBuilder& b) {
-              b.clipPath(kTestPathOval, DlCanvas::ClipOp::kIntersect, true);
+            [](DlOpReceiver& r) {
+              r.clipPath(kTestPathOval, DlCanvas::ClipOp::kIntersect, true);
             }},
        }},
   };
@@ -564,268 +504,256 @@
   return {
       {"DrawPaint",
        {
-           {1, 8, 1, 8, [](DisplayListBuilder& b) { b.drawPaint(); }},
+           {1, 8, 1, 8, [](DlOpReceiver& r) { r.drawPaint(); }},
        }},
       {"DrawColor",
        {
            // cv.drawColor becomes cv.drawPaint(paint)
            {1, 16, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawColor(SK_ColorBLUE, DlBlendMode::kSrcIn);
+            [](DlOpReceiver& r) {
+              r.drawColor(SK_ColorBLUE, DlBlendMode::kSrcIn);
             }},
            {1, 16, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawColor(SK_ColorBLUE, DlBlendMode::kDstIn);
+            [](DlOpReceiver& r) {
+              r.drawColor(SK_ColorBLUE, DlBlendMode::kDstIn);
             }},
            {1, 16, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawColor(SK_ColorCYAN, DlBlendMode::kSrcIn);
+            [](DlOpReceiver& r) {
+              r.drawColor(SK_ColorCYAN, DlBlendMode::kSrcIn);
             }},
        }},
       {"DrawLine",
        {
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawLine({0, 0}, {10, 10});
+            [](DlOpReceiver& r) {
+              r.drawLine({0, 0}, {10, 10});
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawLine({0, 1}, {10, 10});
+            [](DlOpReceiver& r) {
+              r.drawLine({0, 1}, {10, 10});
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawLine({0, 0}, {20, 10});
+            [](DlOpReceiver& r) {
+              r.drawLine({0, 0}, {20, 10});
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawLine({0, 0}, {10, 20});
+            [](DlOpReceiver& r) {
+              r.drawLine({0, 0}, {10, 20});
             }},
        }},
       {"DrawRect",
        {
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawRect({0, 0, 10, 10});
+            [](DlOpReceiver& r) {
+              r.drawRect({0, 0, 10, 10});
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawRect({0, 1, 10, 10});
+            [](DlOpReceiver& r) {
+              r.drawRect({0, 1, 10, 10});
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawRect({0, 0, 20, 10});
+            [](DlOpReceiver& r) {
+              r.drawRect({0, 0, 20, 10});
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawRect({0, 0, 10, 20});
+            [](DlOpReceiver& r) {
+              r.drawRect({0, 0, 10, 20});
             }},
        }},
       {"DrawOval",
        {
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawOval({0, 0, 10, 10});
+            [](DlOpReceiver& r) {
+              r.drawOval({0, 0, 10, 10});
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawOval({0, 1, 10, 10});
+            [](DlOpReceiver& r) {
+              r.drawOval({0, 1, 10, 10});
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawOval({0, 0, 20, 10});
+            [](DlOpReceiver& r) {
+              r.drawOval({0, 0, 20, 10});
             }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawOval({0, 0, 10, 20});
+            [](DlOpReceiver& r) {
+              r.drawOval({0, 0, 10, 20});
             }},
        }},
       {"DrawCircle",
        {
            // cv.drawCircle becomes cv.drawOval
            {1, 16, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawCircle({0, 0}, 10);
+            [](DlOpReceiver& r) {
+              r.drawCircle({0, 0}, 10);
             }},
            {1, 16, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawCircle({0, 5}, 10);
+            [](DlOpReceiver& r) {
+              r.drawCircle({0, 5}, 10);
             }},
            {1, 16, 1, 24,
-            [](DisplayListBuilder& b) {
-              b.drawCircle({0, 0}, 20);
+            [](DlOpReceiver& r) {
+              r.drawCircle({0, 0}, 20);
             }},
        }},
       {"DrawRRect",
        {
+           {1, 56, 1, 56, [](DlOpReceiver& r) { r.drawRRect(kTestRRect); }},
            {1, 56, 1, 56,
-            [](DisplayListBuilder& b) { b.drawRRect(kTestRRect); }},
-           {1, 56, 1, 56,
-            [](DisplayListBuilder& b) {
-              b.drawRRect(kTestRRect.makeOffset(5, 5));
-            }},
+            [](DlOpReceiver& r) { r.drawRRect(kTestRRect.makeOffset(5, 5)); }},
        }},
       {"DrawDRRect",
        {
            {1, 112, 1, 112,
-            [](DisplayListBuilder& b) {
-              b.drawDRRect(kTestRRect, kTestInnerRRect);
-            }},
+            [](DlOpReceiver& r) { r.drawDRRect(kTestRRect, kTestInnerRRect); }},
            {1, 112, 1, 112,
-            [](DisplayListBuilder& b) {
-              b.drawDRRect(kTestRRect.makeOffset(5, 5),
+            [](DlOpReceiver& r) {
+              r.drawDRRect(kTestRRect.makeOffset(5, 5),
                            kTestInnerRRect.makeOffset(4, 4));
             }},
        }},
       {"DrawPath",
        {
-           {1, 24, 1, 24,
-            [](DisplayListBuilder& b) { b.drawPath(kTestPath1); }},
-           {1, 24, 1, 24,
-            [](DisplayListBuilder& b) { b.drawPath(kTestPath2); }},
-           {1, 24, 1, 24,
-            [](DisplayListBuilder& b) { b.drawPath(kTestPath3); }},
-           {1, 24, 1, 24,
-            [](DisplayListBuilder& b) { b.drawPath(kTestPathRect); }},
-           {1, 24, 1, 24,
-            [](DisplayListBuilder& b) { b.drawPath(kTestPathOval); }},
+           {1, 24, 1, 24, [](DlOpReceiver& r) { r.drawPath(kTestPath1); }},
+           {1, 24, 1, 24, [](DlOpReceiver& r) { r.drawPath(kTestPath2); }},
+           {1, 24, 1, 24, [](DlOpReceiver& r) { r.drawPath(kTestPath3); }},
+           {1, 24, 1, 24, [](DlOpReceiver& r) { r.drawPath(kTestPathRect); }},
+           {1, 24, 1, 24, [](DlOpReceiver& r) { r.drawPath(kTestPathOval); }},
        }},
       {"DrawArc",
        {
            {1, 32, 1, 32,
-            [](DisplayListBuilder& b) {
-              b.drawArc(kTestBounds, 45, 270, false);
+            [](DlOpReceiver& r) { r.drawArc(kTestBounds, 45, 270, false); }},
+           {1, 32, 1, 32,
+            [](DlOpReceiver& r) {
+              r.drawArc(kTestBounds.makeOffset(1, 1), 45, 270, false);
             }},
            {1, 32, 1, 32,
-            [](DisplayListBuilder& b) {
-              b.drawArc(kTestBounds.makeOffset(1, 1), 45, 270, false);
-            }},
+            [](DlOpReceiver& r) { r.drawArc(kTestBounds, 30, 270, false); }},
            {1, 32, 1, 32,
-            [](DisplayListBuilder& b) {
-              b.drawArc(kTestBounds, 30, 270, false);
-            }},
+            [](DlOpReceiver& r) { r.drawArc(kTestBounds, 45, 260, false); }},
            {1, 32, 1, 32,
-            [](DisplayListBuilder& b) {
-              b.drawArc(kTestBounds, 45, 260, false);
-            }},
-           {1, 32, 1, 32,
-            [](DisplayListBuilder& b) {
-              b.drawArc(kTestBounds, 45, 270, true);
-            }},
+            [](DlOpReceiver& r) { r.drawArc(kTestBounds, 45, 270, true); }},
        }},
       {"DrawPoints",
        {
            {1, 8 + TestPointCount * 8, 1, 8 + TestPointCount * 8,
-            [](DisplayListBuilder& b) {
-              b.drawPoints(DlCanvas::PointMode::kPoints, TestPointCount,
+            [](DlOpReceiver& r) {
+              r.drawPoints(DlCanvas::PointMode::kPoints, TestPointCount,
                            TestPoints);
             }},
            {1, 8 + (TestPointCount - 1) * 8, 1, 8 + (TestPointCount - 1) * 8,
-            [](DisplayListBuilder& b) {
-              b.drawPoints(DlCanvas::PointMode::kPoints, TestPointCount - 1,
+            [](DlOpReceiver& r) {
+              r.drawPoints(DlCanvas::PointMode::kPoints, TestPointCount - 1,
                            TestPoints);
             }},
            {1, 8 + TestPointCount * 8, 1, 8 + TestPointCount * 8,
-            [](DisplayListBuilder& b) {
-              b.drawPoints(DlCanvas::PointMode::kLines, TestPointCount,
+            [](DlOpReceiver& r) {
+              r.drawPoints(DlCanvas::PointMode::kLines, TestPointCount,
                            TestPoints);
             }},
            {1, 8 + TestPointCount * 8, 1, 8 + TestPointCount * 8,
-            [](DisplayListBuilder& b) {
-              b.drawPoints(DlCanvas::PointMode::kPolygon, TestPointCount,
+            [](DlOpReceiver& r) {
+              r.drawPoints(DlCanvas::PointMode::kPolygon, TestPointCount,
                            TestPoints);
             }},
        }},
       {"DrawVertices",
        {
            {1, 112, 1, 16,
-            [](DisplayListBuilder& b) {
-              b.drawVertices(TestVertices1, DlBlendMode::kSrcIn);
+            [](DlOpReceiver& r) {
+              r.drawVertices(TestVertices1.get(), DlBlendMode::kSrcIn);
             }},
            {1, 112, 1, 16,
-            [](DisplayListBuilder& b) {
-              b.drawVertices(TestVertices1, DlBlendMode::kDstIn);
+            [](DlOpReceiver& r) {
+              r.drawVertices(TestVertices1.get(), DlBlendMode::kDstIn);
             }},
            {1, 112, 1, 16,
-            [](DisplayListBuilder& b) {
-              b.drawVertices(TestVertices2, DlBlendMode::kSrcIn);
+            [](DlOpReceiver& r) {
+              r.drawVertices(TestVertices2.get(), DlBlendMode::kSrcIn);
             }},
        }},
       {"DrawImage",
        {
            {1, 24, -1, 48,
-            [](DisplayListBuilder& b) {
-              b.drawImage(TestImage1, {10, 10}, kNearestSampling, false);
+            [](DlOpReceiver& r) {
+              r.drawImage(TestImage1, {10, 10}, kNearestSampling, false);
             }},
            {1, 24, -1, 48,
-            [](DisplayListBuilder& b) {
-              b.drawImage(TestImage1, {10, 10}, kNearestSampling, true);
+            [](DlOpReceiver& r) {
+              r.drawImage(TestImage1, {10, 10}, kNearestSampling, true);
             }},
            {1, 24, -1, 48,
-            [](DisplayListBuilder& b) {
-              b.drawImage(TestImage1, {20, 10}, kNearestSampling, false);
+            [](DlOpReceiver& r) {
+              r.drawImage(TestImage1, {20, 10}, kNearestSampling, false);
             }},
            {1, 24, -1, 48,
-            [](DisplayListBuilder& b) {
-              b.drawImage(TestImage1, {10, 20}, kNearestSampling, false);
+            [](DlOpReceiver& r) {
+              r.drawImage(TestImage1, {10, 20}, kNearestSampling, false);
             }},
            {1, 24, -1, 48,
-            [](DisplayListBuilder& b) {
-              b.drawImage(TestImage1, {10, 10}, kLinearSampling, false);
+            [](DlOpReceiver& r) {
+              r.drawImage(TestImage1, {10, 10}, kLinearSampling, false);
             }},
            {1, 24, -1, 48,
-            [](DisplayListBuilder& b) {
-              b.drawImage(TestImage2, {10, 10}, kNearestSampling, false);
+            [](DlOpReceiver& r) {
+              r.drawImage(TestImage2, {10, 10}, kNearestSampling, false);
             }},
            {1, 24, -1, 48,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               auto dl_image = DlImage::Make(TestSkImage);
-              b.drawImage(dl_image, {10, 10}, kNearestSampling, false);
+              r.drawImage(dl_image, {10, 10}, kNearestSampling, false);
             }},
        }},
       {"DrawImageRect",
        {
            {1, 56, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
-                              kNearestSampling, false);
+            [](DlOpReceiver& r) {
+              r.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
+                              kNearestSampling, false,
+                              DlCanvas::SrcRectConstraint::kFast);
             }},
            {1, 56, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
-                              kNearestSampling, true);
+            [](DlOpReceiver& r) {
+              r.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
+                              kNearestSampling, true,
+                              DlCanvas::SrcRectConstraint::kFast);
             }},
            {1, 56, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageRect(
-                  TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
-                  kNearestSampling, false,
-                  SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint);
+            [](DlOpReceiver& r) {
+              r.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
+                              kNearestSampling, false,
+                              DlCanvas::SrcRectConstraint::kStrict);
             }},
            {1, 56, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageRect(TestImage1, {10, 10, 25, 20}, {10, 10, 80, 80},
-                              kNearestSampling, false);
+            [](DlOpReceiver& r) {
+              r.drawImageRect(TestImage1, {10, 10, 25, 20}, {10, 10, 80, 80},
+                              kNearestSampling, false,
+                              DlCanvas::SrcRectConstraint::kFast);
             }},
            {1, 56, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 85, 80},
-                              kNearestSampling, false);
+            [](DlOpReceiver& r) {
+              r.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 85, 80},
+                              kNearestSampling, false,
+                              DlCanvas::SrcRectConstraint::kFast);
             }},
            {1, 56, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
-                              kLinearSampling, false);
+            [](DlOpReceiver& r) {
+              r.drawImageRect(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
+                              kLinearSampling, false,
+                              DlCanvas::SrcRectConstraint::kFast);
             }},
            {1, 56, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageRect(TestImage2, {10, 10, 15, 15}, {10, 10, 80, 80},
-                              kNearestSampling, false);
+            [](DlOpReceiver& r) {
+              r.drawImageRect(TestImage2, {10, 10, 15, 15}, {10, 10, 80, 80},
+                              kNearestSampling, false,
+                              DlCanvas::SrcRectConstraint::kFast);
             }},
            {1, 56, -1, 80,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               auto dl_image = DlImage::Make(TestSkImage);
-              b.drawImageRect(dl_image, {10, 10, 15, 15}, {10, 10, 80, 80},
-                              kNearestSampling, false);
+              r.drawImageRect(dl_image, {10, 10, 15, 15}, {10, 10, 80, 80},
+                              kNearestSampling, false,
+                              DlCanvas::SrcRectConstraint::kFast);
             }},
        }},
       {"DrawImageNine",
@@ -833,124 +761,124 @@
            // SkCanvas::drawImageNine is immediately converted to
            // drawImageLattice
            {1, 48, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageNine(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
+            [](DlOpReceiver& r) {
+              r.drawImageNine(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
                               DlFilterMode::kNearest, false);
             }},
            {1, 48, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageNine(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
+            [](DlOpReceiver& r) {
+              r.drawImageNine(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
                               DlFilterMode::kNearest, true);
             }},
            {1, 48, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageNine(TestImage1, {10, 10, 25, 20}, {10, 10, 80, 80},
+            [](DlOpReceiver& r) {
+              r.drawImageNine(TestImage1, {10, 10, 25, 20}, {10, 10, 80, 80},
                               DlFilterMode::kNearest, false);
             }},
            {1, 48, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageNine(TestImage1, {10, 10, 20, 20}, {10, 10, 85, 80},
+            [](DlOpReceiver& r) {
+              r.drawImageNine(TestImage1, {10, 10, 20, 20}, {10, 10, 85, 80},
                               DlFilterMode::kNearest, false);
             }},
            {1, 48, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageNine(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
+            [](DlOpReceiver& r) {
+              r.drawImageNine(TestImage1, {10, 10, 20, 20}, {10, 10, 80, 80},
                               DlFilterMode::kLinear, false);
             }},
            {1, 48, -1, 80,
-            [](DisplayListBuilder& b) {
-              b.drawImageNine(TestImage2, {10, 10, 15, 15}, {10, 10, 80, 80},
+            [](DlOpReceiver& r) {
+              r.drawImageNine(TestImage2, {10, 10, 15, 15}, {10, 10, 80, 80},
                               DlFilterMode::kNearest, false);
             }},
            {1, 48, -1, 80,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               auto dl_image = DlImage::Make(TestSkImage);
-              b.drawImageNine(dl_image, {10, 10, 15, 15}, {10, 10, 80, 80},
+              r.drawImageNine(dl_image, {10, 10, 15, 15}, {10, 10, 80, 80},
                               DlFilterMode::kNearest, false);
             }},
        }},
       {"DrawAtlas",
        {
            {1, 48 + 32 + 8, -1, 48 + 32 + 32,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
               static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
-              b.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
+              r.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
                           DlBlendMode::kSrcIn, kNearestSampling, nullptr,
                           false);
             }},
            {1, 48 + 32 + 8, -1, 48 + 32 + 32,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
               static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
-              b.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
+              r.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
                           DlBlendMode::kSrcIn, kNearestSampling, nullptr, true);
             }},
            {1, 48 + 32 + 8, -1, 48 + 32 + 32,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               static SkRSXform xforms[] = {{0, 1, 0, 0}, {0, 1, 0, 0}};
               static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
-              b.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
+              r.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
                           DlBlendMode::kSrcIn, kNearestSampling, nullptr,
                           false);
             }},
            {1, 48 + 32 + 8, -1, 48 + 32 + 32,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
               static SkRect texs[] = {{10, 10, 20, 20}, {20, 25, 30, 30}};
-              b.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
+              r.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
                           DlBlendMode::kSrcIn, kNearestSampling, nullptr,
                           false);
             }},
            {1, 48 + 32 + 8, -1, 48 + 32 + 32,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
               static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
-              b.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
+              r.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
                           DlBlendMode::kSrcIn, kLinearSampling, nullptr, false);
             }},
            {1, 48 + 32 + 8, -1, 48 + 32 + 32,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
               static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
-              b.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
+              r.drawAtlas(TestImage1, xforms, texs, nullptr, 2,
                           DlBlendMode::kDstIn, kNearestSampling, nullptr,
                           false);
             }},
            {1, 64 + 32 + 8, -1, 64 + 32 + 32,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
               static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
               static SkRect cull_rect = {0, 0, 200, 200};
-              b.drawAtlas(TestImage2, xforms, texs, nullptr, 2,
+              r.drawAtlas(TestImage2, xforms, texs, nullptr, 2,
                           DlBlendMode::kSrcIn, kNearestSampling, &cull_rect,
                           false);
             }},
            {1, 48 + 32 + 8 + 8, -1, 48 + 32 + 32 + 8,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
               static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
               static DlColor colors[] = {DlColor::kBlue(), DlColor::kGreen()};
-              b.drawAtlas(TestImage1, xforms, texs, colors, 2,
+              r.drawAtlas(TestImage1, xforms, texs, colors, 2,
                           DlBlendMode::kSrcIn, kNearestSampling, nullptr,
                           false);
             }},
            {1, 64 + 32 + 8 + 8, -1, 64 + 32 + 32 + 8,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
               static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
               static DlColor colors[] = {DlColor::kBlue(), DlColor::kGreen()};
               static SkRect cull_rect = {0, 0, 200, 200};
-              b.drawAtlas(TestImage1, xforms, texs, colors, 2,
+              r.drawAtlas(TestImage1, xforms, texs, colors, 2,
                           DlBlendMode::kSrcIn, kNearestSampling, &cull_rect,
                           false);
             }},
            {1, 48 + 32 + 8, -1, 48 + 32 + 32,
-            [](DisplayListBuilder& b) {
+            [](DlOpReceiver& r) {
               auto dl_image = DlImage::Make(TestSkImage);
               static SkRSXform xforms[] = {{1, 0, 0, 0}, {0, 1, 0, 0}};
               static SkRect texs[] = {{10, 10, 20, 20}, {20, 20, 30, 30}};
-              b.drawAtlas(dl_image, xforms, texs, nullptr, 2,
+              r.drawAtlas(dl_image, xforms, texs, nullptr, 2,
                           DlBlendMode::kSrcIn, kNearestSampling, nullptr,
                           false);
             }},
@@ -959,24 +887,26 @@
        {
            // cv.drawDL does not exist
            {1, 16, -1, 16,
-            [](DisplayListBuilder& b) { b.drawDisplayList(TestDisplayList1); }},
+            [](DlOpReceiver& r) { r.drawDisplayList(TestDisplayList1, 1.0); }},
            {1, 16, -1, 16,
-            [](DisplayListBuilder& b) { b.drawDisplayList(TestDisplayList2); }},
+            [](DlOpReceiver& r) { r.drawDisplayList(TestDisplayList1, 0.5); }},
            {1, 16, -1, 16,
-            [](DisplayListBuilder& b) {
-              b.drawDisplayList(MakeTestDisplayList(10, 10, SK_ColorRED));
+            [](DlOpReceiver& r) { r.drawDisplayList(TestDisplayList2, 1.0); }},
+           {1, 16, -1, 16,
+            [](DlOpReceiver& r) {
+              r.drawDisplayList(MakeTestDisplayList(10, 10, SK_ColorRED), 1.0);
             }},
        }},
       {"DrawTextBlob",
        {
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) { b.drawTextBlob(TestBlob1, 10, 10); }},
+            [](DlOpReceiver& r) { r.drawTextBlob(TestBlob1, 10, 10); }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) { b.drawTextBlob(TestBlob1, 20, 10); }},
+            [](DlOpReceiver& r) { r.drawTextBlob(TestBlob1, 20, 10); }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) { b.drawTextBlob(TestBlob1, 10, 20); }},
+            [](DlOpReceiver& r) { r.drawTextBlob(TestBlob1, 10, 20); }},
            {1, 24, 1, 24,
-            [](DisplayListBuilder& b) { b.drawTextBlob(TestBlob2, 10, 10); }},
+            [](DlOpReceiver& r) { r.drawTextBlob(TestBlob2, 10, 10); }},
        }},
       // The -1 op counts below are to indicate to the framework not to test
       // SkCanvas conversion of these ops as it converts the operation into a
@@ -988,28 +918,28 @@
            // cv shadows are turned into an opaque ShadowRec which is not
            // exposed
            {1, 32, -1, 32,
-            [](DisplayListBuilder& b) {
-              b.drawShadow(kTestPath1, SK_ColorGREEN, 1.0, false, 1.0);
+            [](DlOpReceiver& r) {
+              r.drawShadow(kTestPath1, SK_ColorGREEN, 1.0, false, 1.0);
             }},
            {1, 32, -1, 32,
-            [](DisplayListBuilder& b) {
-              b.drawShadow(kTestPath2, SK_ColorGREEN, 1.0, false, 1.0);
+            [](DlOpReceiver& r) {
+              r.drawShadow(kTestPath2, SK_ColorGREEN, 1.0, false, 1.0);
             }},
            {1, 32, -1, 32,
-            [](DisplayListBuilder& b) {
-              b.drawShadow(kTestPath1, SK_ColorBLUE, 1.0, false, 1.0);
+            [](DlOpReceiver& r) {
+              r.drawShadow(kTestPath1, SK_ColorBLUE, 1.0, false, 1.0);
             }},
            {1, 32, -1, 32,
-            [](DisplayListBuilder& b) {
-              b.drawShadow(kTestPath1, SK_ColorGREEN, 2.0, false, 1.0);
+            [](DlOpReceiver& r) {
+              r.drawShadow(kTestPath1, SK_ColorGREEN, 2.0, false, 1.0);
             }},
            {1, 32, -1, 32,
-            [](DisplayListBuilder& b) {
-              b.drawShadow(kTestPath1, SK_ColorGREEN, 1.0, true, 1.0);
+            [](DlOpReceiver& r) {
+              r.drawShadow(kTestPath1, SK_ColorGREEN, 1.0, true, 1.0);
             }},
            {1, 32, -1, 32,
-            [](DisplayListBuilder& b) {
-              b.drawShadow(kTestPath1, SK_ColorGREEN, 1.0, false, 2.5);
+            [](DlOpReceiver& r) {
+              r.drawShadow(kTestPath1, SK_ColorGREEN, 1.0, false, 2.5);
             }},
        }},
   };
diff --git a/display_list/testing/dl_test_snippets.h b/display_list/testing/dl_test_snippets.h
index 35278db..69bab82 100644
--- a/display_list/testing/dl_test_snippets.h
+++ b/display_list/testing/dl_test_snippets.h
@@ -20,7 +20,7 @@
 sk_sp<DisplayList> GetSampleDisplayList(int ops);
 sk_sp<DisplayList> GetSampleNestedDisplayList();
 
-typedef const std::function<void(DisplayListBuilder&)> DlInvoker;
+typedef const std::function<void(DlOpReceiver&)> DlInvoker;
 
 constexpr SkPoint kEndPoints[] = {
     {0, 0},
@@ -214,8 +214,7 @@
 
 static sk_sp<DisplayList> MakeTestDisplayList(int w, int h, SkColor color) {
   DisplayListBuilder builder;
-  builder.setColor(color);
-  builder.drawRect(SkRect::MakeWH(w, h));
+  builder.DrawRect(SkRect::MakeWH(w, h), DlPaint(color));
   return builder.Build();
 }
 static sk_sp<DisplayList> TestDisplayList1 =
@@ -271,13 +270,13 @@
   // through an SkCanvas interface, comparable to |DisplayList.byte_count().
   size_t sk_byte_count() { return sizeof(DisplayList) + sk_byte_count_; }
 
-  void Invoke(DisplayListBuilder& builder) { invoker(builder); }
+  void Invoke(DlOpReceiver& builder) { invoker(builder); }
 
-  sk_sp<DisplayList> Build() {
-    DisplayListBuilder builder;
-    invoker(builder);
-    return builder.Build();
-  }
+  // sk_sp<DisplayList> Build() {
+  //   DisplayListBuilder builder;
+  //   invoker(builder.asReceiver());
+  //   return builder.Build();
+  // }
 };
 
 struct DisplayListInvocationGroup {
diff --git a/display_list/types.h b/display_list/types.h
index dbe04aa..b8737f5 100644
--- a/display_list/types.h
+++ b/display_list/types.h
@@ -28,5 +28,4 @@
 #include "third_party/skia/include/effects/SkDashPathEffect.h"
 #include "third_party/skia/include/effects/SkDiscretePathEffect.h"
 #include "third_party/skia/include/gpu/GrTypes.h"
-#include "third_party/skia/include/utils/SkShadowUtils.h"
 #endif  // FLUTTER_DISPLAY_LIST_TYPES_H_
diff --git a/flow/layers/checkerboard_layertree_unittests.cc b/flow/layers/checkerboard_layertree_unittests.cc
index d54abf9..7917b5e 100644
--- a/flow/layers/checkerboard_layertree_unittests.cc
+++ b/flow/layers/checkerboard_layertree_unittests.cc
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "flutter/display_list/display_list_canvas_dispatcher.h"
 #include "flutter/flow/layers/clip_path_layer.h"
 #include "flutter/flow/layers/clip_rect_layer.h"
 #include "flutter/flow/layers/clip_rrect_layer.h"
@@ -235,8 +234,8 @@
   // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and
   // their shadows , so we do not do any painting there.
   EXPECT_EQ(layer->paint_bounds(),
-            DisplayListCanvasDispatcher::ComputeShadowBounds(
-                layer_path, initial_elevation, 1.0f, SkMatrix()));
+            DlCanvas::ComputeShadowBounds(layer_path, initial_elevation, 1.0f,
+                                          SkMatrix()));
   EXPECT_TRUE(layer->needs_painting(paint_context()));
   EXPECT_EQ(layer->elevation(), initial_elevation);
 
diff --git a/flow/layers/clip_path_layer_unittests.cc b/flow/layers/clip_path_layer_unittests.cc
index 4f07f8c..1012a4c 100644
--- a/flow/layers/clip_path_layer_unittests.cc
+++ b/flow/layers/clip_path_layer_unittests.cc
@@ -424,7 +424,7 @@
     {
       expected_builder.Translate(offset.fX, offset.fY);
       /* ClipRectLayer::Paint() */ {
-        expected_builder.save();
+        expected_builder.Save();
         expected_builder.ClipPath(layer_clip, ClipOp::kIntersect, true);
         /* child layer1 paint */ {
           expected_builder.DrawPath(path1, DlPaint().setAlpha(opacity_alpha));
@@ -472,25 +472,24 @@
 
   DisplayListBuilder expected_builder;
   /* OpacityLayer::Paint() */ {
-    expected_builder.save();
+    expected_builder.Save();
     {
-      expected_builder.translate(offset.fX, offset.fY);
+      expected_builder.Translate(offset.fX, offset.fY);
       /* ClipRectLayer::Paint() */ {
-        expected_builder.save();
-        expected_builder.clipPath(layer_clip, ClipOp::kIntersect, true);
-        expected_builder.setColor(opacity_alpha << 24);
-        expected_builder.saveLayer(&children_bounds, true);
+        expected_builder.Save();
+        expected_builder.ClipPath(layer_clip, ClipOp::kIntersect, true);
+        expected_builder.SaveLayer(&children_bounds,
+                                   &DlPaint().setAlpha(opacity_alpha));
         /* child layer1 paint */ {
-          expected_builder.setColor(0xFF000000);
-          expected_builder.drawPath(path1);
+          expected_builder.DrawPath(path1, DlPaint());
         }
         /* child layer2 paint */ {  //
-          expected_builder.drawPath(path2);
+          expected_builder.DrawPath(path2, DlPaint());
         }
-        expected_builder.restore();
+        expected_builder.Restore();
       }
     }
-    expected_builder.restore();
+    expected_builder.Restore();
   }
 
   opacity_layer->Paint(display_list_paint_context());
diff --git a/flow/layers/clip_rect_layer_unittests.cc b/flow/layers/clip_rect_layer_unittests.cc
index 728e487..6ddb907 100644
--- a/flow/layers/clip_rect_layer_unittests.cc
+++ b/flow/layers/clip_rect_layer_unittests.cc
@@ -454,25 +454,24 @@
 
   DisplayListBuilder expected_builder;
   /* OpacityLayer::Paint() */ {
-    expected_builder.save();
+    expected_builder.Save();
     {
-      expected_builder.translate(offset.fX, offset.fY);
+      expected_builder.Translate(offset.fX, offset.fY);
       /* ClipRectLayer::Paint() */ {
-        expected_builder.save();
-        expected_builder.clipRect(clip_rect, ClipOp::kIntersect, true);
-        expected_builder.setColor(opacity_alpha << 24);
-        expected_builder.saveLayer(&children_bounds, true);
+        expected_builder.Save();
+        expected_builder.ClipRect(clip_rect, ClipOp::kIntersect, true);
+        expected_builder.SaveLayer(&children_bounds,
+                                   &DlPaint().setAlpha(opacity_alpha));
         /* child layer1 paint */ {
-          expected_builder.setColor(0xFF000000);
-          expected_builder.drawPath(path1);
+          expected_builder.DrawPath(path1, DlPaint());
         }
         /* child layer2 paint */ {  //
-          expected_builder.drawPath(path2);
+          expected_builder.DrawPath(path2, DlPaint());
         }
-        expected_builder.restore();
+        expected_builder.Restore();
       }
     }
-    expected_builder.restore();
+    expected_builder.Restore();
   }
 
   opacity_layer->Paint(display_list_paint_context());
diff --git a/flow/layers/clip_rrect_layer_unittests.cc b/flow/layers/clip_rrect_layer_unittests.cc
index 9e789ce..75ca41c 100644
--- a/flow/layers/clip_rrect_layer_unittests.cc
+++ b/flow/layers/clip_rrect_layer_unittests.cc
@@ -467,25 +467,24 @@
 
   DisplayListBuilder expected_builder;
   /* OpacityLayer::Paint() */ {
-    expected_builder.save();
+    expected_builder.Save();
     {
-      expected_builder.translate(offset.fX, offset.fY);
+      expected_builder.Translate(offset.fX, offset.fY);
       /* ClipRectLayer::Paint() */ {
-        expected_builder.save();
-        expected_builder.clipRRect(clip_rrect, ClipOp::kIntersect, true);
-        expected_builder.setColor(opacity_alpha << 24);
-        expected_builder.saveLayer(&children_bounds, true);
+        expected_builder.Save();
+        expected_builder.ClipRRect(clip_rrect, ClipOp::kIntersect, true);
+        expected_builder.SaveLayer(&children_bounds,
+                                   &DlPaint().setAlpha(opacity_alpha));
         /* child layer1 paint */ {
-          expected_builder.setColor(0xFF000000);
-          expected_builder.drawPath(path1);
+          expected_builder.DrawPath(path1, DlPaint());
         }
         /* child layer2 paint */ {  //
-          expected_builder.drawPath(path2);
+          expected_builder.DrawPath(path2, DlPaint());
         }
-        expected_builder.restore();
+        expected_builder.Restore();
       }
     }
-    expected_builder.restore();
+    expected_builder.Restore();
   }
 
   opacity_layer->Paint(display_list_paint_context());
diff --git a/flow/layers/display_list_layer_unittests.cc b/flow/layers/display_list_layer_unittests.cc
index 5d4b633..1b2ad31 100644
--- a/flow/layers/display_list_layer_unittests.cc
+++ b/flow/layers/display_list_layer_unittests.cc
@@ -32,7 +32,7 @@
   const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f);
   const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
   DisplayListBuilder builder;
-  builder.drawRect(picture_bounds);
+  builder.DrawRect(picture_bounds, DlPaint());
   auto display_list = builder.Build();
   auto layer = std::make_shared<DisplayListLayer>(
       layer_offset, SkiaGPUObject<DisplayList>(display_list, unref_queue()),
@@ -47,7 +47,7 @@
   const SkPoint layer_offset = SkPoint::Make(0.0f, 0.0f);
   const SkRect picture_bounds = SkRect::MakeEmpty();
   DisplayListBuilder builder;
-  builder.drawRect(picture_bounds);
+  builder.DrawRect(picture_bounds, DlPaint());
   auto display_list = builder.Build();
   auto layer = std::make_shared<DisplayListLayer>(
       layer_offset, SkiaGPUObject<DisplayList>(display_list, unref_queue()),
@@ -77,7 +77,7 @@
       SkMatrix::Translate(layer_offset.fX, layer_offset.fY);
   const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
   DisplayListBuilder builder;
-  builder.drawRect(picture_bounds);
+  builder.DrawRect(picture_bounds, DlPaint());
   auto display_list = builder.Build();
   auto layer = std::make_shared<DisplayListLayer>(
       layer_offset, SkiaGPUObject(display_list, unref_queue()), false, false);
@@ -102,7 +102,7 @@
 TEST_F(DisplayListLayerTest, CachingDoesNotChangeCullRect) {
   const SkPoint layer_offset = SkPoint::Make(10, 10);
   DisplayListBuilder builder;
-  builder.drawRect({10, 10, 20, 20});
+  builder.DrawRect({10, 10, 20, 20}, DlPaint());
   auto display_list = builder.Build();
   auto layer = std::make_shared<DisplayListLayer>(
       layer_offset, SkiaGPUObject(display_list, unref_queue()), true, false);
@@ -118,7 +118,7 @@
   const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f);
   const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
   DisplayListBuilder builder;
-  builder.drawRect(picture_bounds);
+  builder.DrawRect(picture_bounds, DlPaint());
   auto display_list = builder.Build();
   auto display_list_layer = std::make_shared<DisplayListLayer>(
       layer_offset, SkiaGPUObject(display_list, unref_queue()), false, false);
@@ -130,6 +130,7 @@
             LayerStateStack::kCallerCanApplyOpacity);
 
   int opacity_alpha = 0x7F;
+  SkScalar opacity = opacity_alpha / 255.0;
   SkPoint opacity_offset = SkPoint::Make(10, 10);
   auto opacity_layer =
       std::make_shared<OpacityLayer>(opacity_alpha, opacity_offset);
@@ -138,30 +139,24 @@
   EXPECT_TRUE(opacity_layer->children_can_accept_opacity());
 
   DisplayListBuilder child_builder;
-  child_builder.drawRect(picture_bounds);
+  child_builder.DrawRect(picture_bounds, DlPaint());
   auto child_display_list = child_builder.Build();
 
   DisplayListBuilder expected_builder;
   /* opacity_layer::Paint() */ {
-    expected_builder.save();
+    expected_builder.Save();
     {
-      expected_builder.translate(opacity_offset.fX, opacity_offset.fY);
+      expected_builder.Translate(opacity_offset.fX, opacity_offset.fY);
       /* display_list_layer::Paint() */ {
-        expected_builder.save();
+        expected_builder.Save();
         {
-          expected_builder.translate(layer_offset.fX, layer_offset.fY);
-          expected_builder.setColor(opacity_alpha << 24);
-          expected_builder.saveLayer(&picture_bounds, true);
-          /* display_list contents */ {  //
-            expected_builder.drawDisplayList(child_display_list);
-          }
-          expected_builder.restore();
-          expected_builder.setColor(DlColor::kBlack());
+          expected_builder.Translate(layer_offset.fX, layer_offset.fY);
+          expected_builder.DrawDisplayList(child_display_list, opacity);
         }
-        expected_builder.restore();
+        expected_builder.Restore();
       }
     }
-    expected_builder.restore();
+    expected_builder.Restore();
   }
 
   opacity_layer->Paint(display_list_paint_context());
@@ -174,8 +169,8 @@
   const SkRect picture1_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
   const SkRect picture2_bounds = SkRect::MakeLTRB(10.0f, 15.0f, 30.0f, 35.0f);
   DisplayListBuilder builder;
-  builder.drawRect(picture1_bounds);
-  builder.drawRect(picture2_bounds);
+  builder.DrawRect(picture1_bounds, DlPaint());
+  builder.DrawRect(picture2_bounds, DlPaint());
   auto display_list = builder.Build();
   auto display_list_layer = std::make_shared<DisplayListLayer>(
       layer_offset, SkiaGPUObject(display_list, unref_queue()), false, false);
@@ -194,8 +189,8 @@
   EXPECT_FALSE(opacity_layer->children_can_accept_opacity());
 
   DisplayListBuilder child_builder;
-  child_builder.drawRect(picture1_bounds);
-  child_builder.drawRect(picture2_bounds);
+  child_builder.DrawRect(picture1_bounds, DlPaint());
+  child_builder.DrawRect(picture2_bounds, DlPaint());
   auto child_display_list = child_builder.Build();
 
   auto display_list_bounds = picture1_bounds;
@@ -204,24 +199,24 @@
       display_list_bounds.makeOffset(layer_offset.fX, layer_offset.fY);
   DisplayListBuilder expected_builder;
   /* opacity_layer::Paint() */ {
-    expected_builder.save();
+    expected_builder.Save();
     {
-      expected_builder.translate(opacity_offset.fX, opacity_offset.fY);
-      expected_builder.setColor(opacity_alpha << 24);
-      expected_builder.saveLayer(&save_layer_bounds, true);
+      expected_builder.Translate(opacity_offset.fX, opacity_offset.fY);
+      expected_builder.SaveLayer(&save_layer_bounds,
+                                 &DlPaint().setAlpha(opacity_alpha));
       {
         /* display_list_layer::Paint() */ {
-          expected_builder.save();
+          expected_builder.Save();
           {
-            expected_builder.translate(layer_offset.fX, layer_offset.fY);
-            expected_builder.drawDisplayList(child_display_list);
+            expected_builder.Translate(layer_offset.fX, layer_offset.fY);
+            expected_builder.DrawDisplayList(child_display_list);
           }
-          expected_builder.restore();
+          expected_builder.Restore();
         }
       }
-      expected_builder.restore();
+      expected_builder.Restore();
     }
-    expected_builder.restore();
+    expected_builder.Restore();
   }
 
   opacity_layer->Paint(display_list_paint_context());
@@ -234,8 +229,8 @@
   const SkRect picture1_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
   const SkRect picture2_bounds = SkRect::MakeLTRB(10.0f, 15.0f, 30.0f, 35.0f);
   DisplayListBuilder builder;
-  builder.drawRect(picture1_bounds);
-  builder.drawRect(picture2_bounds);
+  builder.DrawRect(picture1_bounds, DlPaint());
+  builder.DrawRect(picture2_bounds, DlPaint());
   auto display_list = builder.Build();
   auto display_list_layer = std::make_shared<DisplayListLayer>(
       layer_offset, SkiaGPUObject(display_list, unref_queue()), true, false);
@@ -386,7 +381,7 @@
   const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f);
   const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
   DisplayListBuilder builder;
-  builder.drawRect(picture_bounds);
+  builder.DrawRect(picture_bounds, DlPaint());
   auto display_list = builder.Build();
   auto layer = std::make_shared<DisplayListLayer>(
       layer_offset, SkiaGPUObject(display_list, unref_queue()), false, false);
@@ -405,7 +400,7 @@
   const SkPoint layer_offset = SkPoint::Make(1.5f, -0.5f);
   const SkRect picture_bounds = SkRect::MakeLTRB(5.0f, 6.0f, 20.5f, 21.5f);
   DisplayListBuilder builder;
-  builder.drawRect(picture_bounds);
+  builder.DrawRect(picture_bounds, DlPaint());
   auto display_list = builder.Build();
   auto layer = std::make_shared<DisplayListLayer>(
       layer_offset, SkiaGPUObject(display_list, unref_queue()), false, false);
@@ -423,7 +418,7 @@
   const SkRect missed_cull_rect = SkRect::MakeLTRB(100, 100, 200, 200);
   const SkRect hit_cull_rect = SkRect::MakeLTRB(0, 0, 200, 200);
   DisplayListBuilder builder;
-  builder.drawRect(picture_bounds);
+  builder.DrawRect(picture_bounds, DlPaint());
   auto display_list = builder.Build();
   auto layer = std::make_shared<DisplayListLayer>(
       layer_offset, SkiaGPUObject(display_list, unref_queue()), true, false);
@@ -524,8 +519,8 @@
   std::shared_ptr<DisplayListLayer> layers[layer_count];
   for (int i = 0; i < layer_count; i++) {
     DisplayListBuilder builder(false);
-    builder.drawRect({0, 0, 100, 100});
-    builder.drawRect({50, 50, 100, 100});
+    builder.DrawRect({0, 0, 100, 100}, DlPaint());
+    builder.DrawRect({50, 50, 100, 100}, DlPaint());
     auto display_list = builder.Build();
     ASSERT_FALSE(display_list->can_apply_group_opacity());
     SkPoint offset = {i * 200.0f, 0};
diff --git a/flow/layers/image_filter_layer_unittests.cc b/flow/layers/image_filter_layer_unittests.cc
index 77789a9..bb5ced3 100644
--- a/flow/layers/image_filter_layer_unittests.cc
+++ b/flow/layers/image_filter_layer_unittests.cc
@@ -248,7 +248,7 @@
       }
     }
   }
-  expected_builder.restore();
+  expected_builder.Restore();
   auto expected_display_list = expected_builder.Build();
 
   layer->Paint(display_list_paint_context());
diff --git a/flow/layers/opacity_layer_unittests.cc b/flow/layers/opacity_layer_unittests.cc
index a5e72e0..1be70be 100644
--- a/flow/layers/opacity_layer_unittests.cc
+++ b/flow/layers/opacity_layer_unittests.cc
@@ -606,24 +606,23 @@
 
   DisplayListBuilder expected_builder;
   /* opacity_layer_1::Paint */ {
-    expected_builder.save();
+    expected_builder.Save();
     {
-      expected_builder.translate(offset1.fX, offset1.fY);
+      expected_builder.Translate(offset1.fX, offset1.fY);
       /* opacity_layer_2::Paint */ {
-        expected_builder.save();
+        expected_builder.Save();
         {
-          expected_builder.translate(offset2.fX, offset2.fY);
-          expected_builder.setColor(savelayer_paint.getAlpha() << 24);
-          expected_builder.saveLayer(&mock_layer->paint_bounds(), true);
+          expected_builder.Translate(offset2.fX, offset2.fY);
+          expected_builder.SaveLayer(&mock_layer->paint_bounds(),
+                                     &savelayer_paint);
           /* mock_layer::Paint */ {
-            expected_builder.setColor(0xFF000000);
-            expected_builder.drawPath(mock_path);
+            expected_builder.DrawPath(mock_path, DlPaint());
           }
         }
-        expected_builder.restore();
+        expected_builder.Restore();
       }
     }
-    expected_builder.restore();
+    expected_builder.Restore();
   }
 
   opacity_layer_1->Paint(display_list_paint_context());
diff --git a/flow/layers/physical_shape_layer.cc b/flow/layers/physical_shape_layer.cc
index 61b3199..c237114 100644
--- a/flow/layers/physical_shape_layer.cc
+++ b/flow/layers/physical_shape_layer.cc
@@ -4,7 +4,6 @@
 
 #include "flutter/flow/layers/physical_shape_layer.h"
 
-#include "flutter/display_list/display_list_canvas_dispatcher.h"
 #include "flutter/flow/paint_utils.h"
 
 namespace flutter {
@@ -36,9 +35,9 @@
   if (elevation_ == 0) {
     bounds = path_.getBounds();
   } else {
-    bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
-        path_, elevation_, context->frame_device_pixel_ratio(),
-        context->GetTransform3x3());
+    bounds = DlCanvas::ComputeShadowBounds(path_, elevation_,
+                                           context->frame_device_pixel_ratio(),
+                                           context->GetTransform3x3());
   }
 
   context->AddLayerBounds(bounds);
@@ -65,7 +64,7 @@
   } else {
     // We will draw the shadow in Paint(), so add some margin to the paint
     // bounds to leave space for the shadow.
-    paint_bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
+    paint_bounds = DlCanvas::ComputeShadowBounds(
         path_, elevation_, context->frame_device_pixel_ratio,
         context->state_stack.transform_3x3());
   }
diff --git a/flow/layers/physical_shape_layer_unittests.cc b/flow/layers/physical_shape_layer_unittests.cc
index daae090..6c97b45 100644
--- a/flow/layers/physical_shape_layer_unittests.cc
+++ b/flow/layers/physical_shape_layer_unittests.cc
@@ -4,7 +4,6 @@
 
 #include "flutter/flow/layers/physical_shape_layer.h"
 
-#include "flutter/display_list/display_list_canvas_dispatcher.h"
 #include "flutter/flow/testing/diff_context_test.h"
 #include "flutter/flow/testing/layer_test.h"
 #include "flutter/flow/testing/mock_layer.h"
@@ -214,8 +213,8 @@
   // The Fuchsia system compositor handles all elevated PhysicalShapeLayers and
   // their shadows , so we do not do any painting there.
   EXPECT_EQ(layer->paint_bounds(),
-            DisplayListCanvasDispatcher::ComputeShadowBounds(
-                layer_path, initial_elevation, 1.0f, SkMatrix()));
+            DlCanvas::ComputeShadowBounds(layer_path, initial_elevation, 1.0f,
+                                          SkMatrix()));
   EXPECT_TRUE(layer->needs_painting(paint_context()));
   EXPECT_EQ(layer->elevation(), initial_elevation);
 
@@ -264,7 +263,7 @@
     // On Fuchsia, the system compositor handles all elevated
     // PhysicalShapeLayers and their shadows , so we do not do any painting
     // there.
-    SkRect paint_bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
+    SkRect paint_bounds = DlCanvas::ComputeShadowBounds(
         layer_path, initial_elevations[i], 1.0f /* pixel_ratio */, SkMatrix());
 
     // Without clipping the children will be painted as well
@@ -313,15 +312,15 @@
   path.addRect(0, 0, 8, 8).close();
 
   for (SkScalar elevation : elevations) {
-    SkRect baseline_bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
-        path, elevation, 1.0f, SkMatrix());
+    SkRect baseline_bounds =
+        DlCanvas::ComputeShadowBounds(path, elevation, 1.0f, SkMatrix());
     for (SkScalar scale : scales) {
       for (SkScalar translate_x : translates) {
         for (SkScalar translate_y : translates) {
           SkMatrix ctm;
           ctm.setScaleTranslate(scale, scale, translate_x, translate_y);
-          SkRect bounds = DisplayListCanvasDispatcher::ComputeShadowBounds(
-              path, elevation, scale, ctm);
+          SkRect bounds =
+              DlCanvas::ComputeShadowBounds(path, elevation, scale, ctm);
           EXPECT_FLOAT_EQ(bounds.fLeft, baseline_bounds.fLeft);
           EXPECT_FLOAT_EQ(bounds.fTop, baseline_bounds.fTop);
           EXPECT_FLOAT_EQ(bounds.fRight, baseline_bounds.fRight);
@@ -377,14 +376,14 @@
                   [=](SkCanvas* canvas) {
                     SkPath path;
                     path.addRect(test_case[0]).close();
-                    DisplayListCanvasDispatcher::DrawShadow(
-                        canvas, path, DlColor::kBlack(), 1.0f, false, 1.0f);
+                    DlSkCanvasAdapter(canvas).DrawShadow(
+                        path, DlColor::kBlack(), 1.0f, false, 1.0f);
                   },
                   [=](SkCanvas* canvas) {
                     SkPath path;
                     path.addRect(test_case[1]).close();
-                    DisplayListCanvasDispatcher::DrawShadow(
-                        canvas, path, DlColor::kBlack(), 1.0f, false, 1.0f);
+                    DlSkCanvasAdapter(canvas).DrawShadow(
+                        path, DlColor::kBlack(), 1.0f, false, 1.0f);
                   },
                   SkSize::Make(100, 100)),
               0);
diff --git a/flow/layers/shader_mask_layer_unittests.cc b/flow/layers/shader_mask_layer_unittests.cc
index 886f0d0..b08d73c 100644
--- a/flow/layers/shader_mask_layer_unittests.cc
+++ b/flow/layers/shader_mask_layer_unittests.cc
@@ -383,7 +383,7 @@
           /* child layer paint */ {
             expected_builder.DrawPath(child_path, DlPaint());
           }
-          expected_builder.translate(mask_rect.fLeft, mask_rect.fTop);
+          expected_builder.Translate(mask_rect.fLeft, mask_rect.fTop);
           expected_builder.DrawRect(
               SkRect::MakeWH(mask_rect.width(), mask_rect.height()),
               DlPaint().setBlendMode(DlBlendMode::kSrc));
diff --git a/flow/raster_cache_unittests.cc b/flow/raster_cache_unittests.cc
index 7fc8a5c..7f1c518 100644
--- a/flow/raster_cache_unittests.cc
+++ b/flow/raster_cache_unittests.cc
@@ -449,8 +449,7 @@
 
   SkRect logical_rect = SkRect::MakeLTRB(28, 0, 354.56731, 310.288);
   DisplayListBuilder builder(logical_rect);
-  builder.setColor(SK_ColorRED);
-  builder.drawRect(logical_rect);
+  builder.DrawRect(logical_rect, DlPaint(DlColor::kRed()));
   sk_sp<DisplayList> display_list = builder.Build();
 
   SkMatrix ctm = SkMatrix::MakeAll(1.3312, 0, 233, 0, 1.3312, 206, 0, 0, 1);
diff --git a/flow/testing/diff_context_test.cc b/flow/testing/diff_context_test.cc
index 888923a..61629eb 100644
--- a/flow/testing/diff_context_test.cc
+++ b/flow/testing/diff_context_test.cc
@@ -35,8 +35,7 @@
 sk_sp<DisplayList> DiffContextTest::CreateDisplayList(const SkRect& bounds,
                                                       SkColor color) {
   DisplayListBuilder builder;
-  builder.setColor(color);
-  builder.drawRect(bounds);
+  builder.DrawRect(bounds, DlPaint().setColor(color));
   return builder.Build();
 }
 
diff --git a/impeller/display_list/display_list_dispatcher.cc b/impeller/display_list/display_list_dispatcher.cc
index b1f8fb1..06ee1ee 100644
--- a/impeller/display_list/display_list_dispatcher.cc
+++ b/impeller/display_list/display_list_dispatcher.cc
@@ -181,12 +181,12 @@
   };
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setAntiAlias(bool aa) {
   // Nothing to do because AA is implicit.
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setDither(bool dither) {}
 
 static Paint::Style ToStyle(flutter::DlDrawStyle style) {
@@ -202,12 +202,12 @@
   return Paint::Style::kFill;
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setStyle(flutter::DlDrawStyle style) {
   paint_.style = ToStyle(style);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setColor(flutter::DlColor color) {
   paint_.color = {
       color.getRedF(),
@@ -217,17 +217,17 @@
   };
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setStrokeWidth(SkScalar width) {
   paint_.stroke_width = width;
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setStrokeMiter(SkScalar limit) {
   paint_.stroke_miter = limit;
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setStrokeCap(flutter::DlStrokeCap cap) {
   switch (cap) {
     case flutter::DlStrokeCap::kButt:
@@ -242,7 +242,7 @@
   }
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setStrokeJoin(flutter::DlStrokeJoin join) {
   switch (join) {
     case flutter::DlStrokeJoin::kMiter:
@@ -346,7 +346,7 @@
   }
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setColorSource(
     const flutter::DlColorSource* source) {
   if (!source) {
@@ -618,24 +618,24 @@
   return std::nullopt;
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setColorFilter(
     const flutter::DlColorFilter* filter) {
   // Needs https://github.com/flutter/flutter/issues/95434
   paint_.color_filter = ToColorFilterProc(filter);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setInvertColors(bool invert) {
   paint_.invert_colors = invert;
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setBlendMode(flutter::DlBlendMode dl_mode) {
   paint_.blend_mode = ToBlendMode(dl_mode);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setPathEffect(const flutter::DlPathEffect* effect) {
   // Needs https://github.com/flutter/flutter/issues/95434
   UNIMPLEMENTED;
@@ -654,7 +654,7 @@
   }
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setMaskFilter(const flutter::DlMaskFilter* filter) {
   // Needs https://github.com/flutter/flutter/issues/95434
   if (filter == nullptr) {
@@ -743,7 +743,7 @@
       };
       break;
     }
-    case flutter::DlImageFilterType::kComposeFilter: {
+    case flutter::DlImageFilterType::kCompose: {
       auto compose = filter->asCompose();
       FML_DCHECK(compose);
       auto outer = compose->outer();
@@ -782,7 +782,7 @@
                  bool is_subpass) { return color_filter(std::move(input)); };
       break;
     }
-    case flutter::DlImageFilterType::kLocalMatrixFilter: {
+    case flutter::DlImageFilterType::kLocalMatrix: {
       auto local_matrix_filter = filter->asLocalMatrix();
       FML_DCHECK(local_matrix_filter);
       auto internal_filter = local_matrix_filter->image_filter();
@@ -808,13 +808,13 @@
   }
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::setImageFilter(
     const flutter::DlImageFilter* filter) {
   paint_.image_filter = ToImageFilterProc(filter);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::save() {
   canvas_.Save();
 }
@@ -834,7 +834,7 @@
   return result;
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::saveLayer(const SkRect* bounds,
                                       const flutter::SaveLayerOptions options,
                                       const flutter::DlImageFilter* backdrop) {
@@ -842,32 +842,32 @@
   canvas_.SaveLayer(paint, ToRect(bounds), ToImageFilterProc(backdrop));
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::restore() {
   canvas_.Restore();
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::translate(SkScalar tx, SkScalar ty) {
   canvas_.Translate({tx, ty, 0.0});
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::scale(SkScalar sx, SkScalar sy) {
   canvas_.Scale({sx, sy, 1.0});
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::rotate(SkScalar degrees) {
   canvas_.Rotate(Degrees{degrees});
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::skew(SkScalar sx, SkScalar sy) {
   canvas_.Skew(sx, sy);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::transform2DAffine(SkScalar mxx,
                                               SkScalar mxy,
                                               SkScalar mxt,
@@ -884,7 +884,7 @@
   // clang-format on
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::transformFullPerspective(SkScalar mxx,
                                                      SkScalar mxy,
                                                      SkScalar mxz,
@@ -914,7 +914,7 @@
   canvas_.Transform(xformation);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::transformReset() {
   canvas_.ResetTransform();
   canvas_.Transform(initial_matrix_);
@@ -934,7 +934,7 @@
   }
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::clipRect(const SkRect& rect,
                                      ClipOp clip_op,
                                      bool is_aa) {
@@ -1020,7 +1020,7 @@
     case SkPathFillType::kInverseWinding:
     case SkPathFillType::kInverseEvenOdd:
       // Flutter doesn't expose these path fill types. These are only visible
-      // via the dispatcher interface. We should never get here.
+      // via the receiver interface. We should never get here.
       fill_type = FillType::kNonZero;
       break;
   }
@@ -1033,7 +1033,7 @@
       .TakePath();
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::clipRRect(const SkRRect& rrect,
                                       ClipOp clip_op,
                                       bool is_aa) {
@@ -1045,14 +1045,14 @@
   }
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::clipPath(const SkPath& path,
                                      ClipOp clip_op,
                                      bool is_aa) {
   canvas_.ClipPath(ToPath(path), ToClipOperation(clip_op));
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawColor(flutter::DlColor color,
                                       flutter::DlBlendMode dl_mode) {
   Paint paint;
@@ -1061,12 +1061,12 @@
   canvas_.DrawPaint(paint);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawPaint() {
   canvas_.DrawPaint(paint_);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawLine(const SkPoint& p0, const SkPoint& p1) {
   auto path = PathBuilder{}.AddLine(ToPoint(p0), ToPoint(p1)).TakePath();
   Paint paint = paint_;
@@ -1074,12 +1074,12 @@
   canvas_.DrawPath(path, paint);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawRect(const SkRect& rect) {
   canvas_.DrawRect(ToRect(rect), paint_);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawOval(const SkRect& bounds) {
   if (bounds.width() == bounds.height()) {
     canvas_.DrawCircle(ToPoint(bounds.center()), bounds.width() * 0.5, paint_);
@@ -1089,12 +1089,12 @@
   }
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawCircle(const SkPoint& center, SkScalar radius) {
   canvas_.DrawCircle(ToPoint(center), radius, paint_);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawRRect(const SkRRect& rrect) {
   if (rrect.isSimple()) {
     canvas_.DrawRRect(ToRect(rrect.rect()), rrect.getSimpleRadii().fX, paint_);
@@ -1103,7 +1103,7 @@
   }
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawDRRect(const SkRRect& outer,
                                        const SkRRect& inner) {
   PathBuilder builder;
@@ -1112,12 +1112,12 @@
   canvas_.DrawPath(builder.TakePath(FillType::kOdd), paint_);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawPath(const SkPath& path) {
   canvas_.DrawPath(ToPath(path), paint_);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawArc(const SkRect& oval_bounds,
                                     SkScalar start_degrees,
                                     SkScalar sweep_degrees,
@@ -1128,7 +1128,7 @@
   canvas_.DrawPath(builder.TakePath(), paint_);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawPoints(PointMode mode,
                                        uint32_t count,
                                        const SkPoint points[]) {
@@ -1167,14 +1167,14 @@
   }
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawVertices(const flutter::DlVertices* vertices,
                                          flutter::DlBlendMode dl_mode) {
   canvas_.DrawVertices(DLVerticesGeometry::MakeVertices(vertices),
                        ToBlendMode(dl_mode), paint_);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawImage(const sk_sp<flutter::DlImage> image,
                                       const SkPoint point,
                                       flutter::DlImageSampling sampling,
@@ -1193,24 +1193,23 @@
   const auto dest =
       SkRect::MakeXYWH(point.fX, point.fY, size.width, size.height);
 
-  drawImageRect(
-      image,                   // image
-      src,                     // source rect
-      dest,                    // destination rect
-      sampling,                // sampling options
-      render_with_attributes,  // render with attributes
-      SkCanvas::SrcRectConstraint::kStrict_SrcRectConstraint  // constraint
+  drawImageRect(image,                      // image
+                src,                        // source rect
+                dest,                       // destination rect
+                sampling,                   // sampling options
+                render_with_attributes,     // render with attributes
+                SrcRectConstraint::kStrict  // constraint
   );
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawImageRect(
     const sk_sp<flutter::DlImage> image,
     const SkRect& src,
     const SkRect& dst,
     flutter::DlImageSampling sampling,
     bool render_with_attributes,
-    SkCanvas::SrcRectConstraint constraint) {
+    SrcRectConstraint constraint = SrcRectConstraint::kFast) {
   canvas_.DrawImageRect(
       std::make_shared<Image>(image->impeller_texture()),  // image
       ToRect(src),                                         // source rect
@@ -1220,7 +1219,7 @@
   );
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawImageNine(const sk_sp<flutter::DlImage> image,
                                           const SkIRect& center,
                                           const SkRect& dst,
@@ -1233,7 +1232,7 @@
       ToRect(dst), ToSamplerDescriptor(filter), &canvas_, &paint_);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawAtlas(const sk_sp<flutter::DlImage> atlas,
                                       const SkRSXform xform[],
                                       const SkRect tex[],
@@ -1249,21 +1248,43 @@
                     ToSamplerDescriptor(sampling), ToRect(cull_rect), paint_);
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawDisplayList(
-    const sk_sp<flutter::DisplayList> display_list) {
-  int saveCount = canvas_.GetSaveCount();
-  Paint savePaint = paint_;
-  Matrix saveMatrix = initial_matrix_;
-  paint_ = Paint();
+    const sk_sp<flutter::DisplayList> display_list,
+    SkScalar opacity) {
+  // Save all values that must remain untouched after the operation.
+  Paint saved_paint = paint_;
+  Matrix saved_initial_matrix = initial_matrix_;
+  int restore_count = canvas_.GetSaveCount();
+
+  // Establish a new baseline for interpreting the new DL.
+  // Matrix and clip are left untouched, the current
+  // transform is saved as the new base matrix, and paint
+  // values are reset to defaults.
   initial_matrix_ = canvas_.GetCurrentTransformation();
+  paint_ = Paint();
+
+  // Handle passed opacity in the most brute-force way by using
+  // a SaveLayer. If the display_list is able to inherit the
+  // opacity, this could also be handled by modulating all of its
+  // attribute settings (for example, color), by the indicated
+  // opacity.
+  if (opacity < SK_Scalar1) {
+    Paint save_paint;
+    save_paint.color = Color(0, 0, 0, opacity);
+    canvas_.SaveLayer(save_paint);
+  }
+
   display_list->Dispatch(*this);
-  paint_ = savePaint;
-  initial_matrix_ = saveMatrix;
-  canvas_.RestoreToCount(saveCount);
+
+  // Restore all saved state back to what it was before we interpreted
+  // the display_list
+  canvas_.RestoreToCount(restore_count);
+  initial_matrix_ = saved_initial_matrix;
+  paint_ = saved_paint;
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawTextBlob(const sk_sp<SkTextBlob> blob,
                                          SkScalar x,
                                          SkScalar y) {
@@ -1274,7 +1295,7 @@
   );
 }
 
-// |flutter::Dispatcher|
+// |flutter::DlOpReceiver|
 void DisplayListDispatcher::drawShadow(const SkPath& path,
                                        const flutter::DlColor color,
                                        const SkScalar elevation,
diff --git a/impeller/display_list/display_list_dispatcher.h b/impeller/display_list/display_list_dispatcher.h
index 2f462f9..76cf251 100644
--- a/impeller/display_list/display_list_dispatcher.h
+++ b/impeller/display_list/display_list_dispatcher.h
@@ -7,14 +7,14 @@
 #include "display_list/display_list_path_effect.h"
 #include "flutter/display_list/display_list.h"
 #include "flutter/display_list/display_list_blend_mode.h"
-#include "flutter/display_list/display_list_dispatcher.h"
+#include "flutter/display_list/dl_op_receiver.h"
 #include "flutter/fml/macros.h"
 #include "impeller/aiks/canvas.h"
 #include "impeller/aiks/paint.h"
 
 namespace impeller {
 
-class DisplayListDispatcher final : public flutter::Dispatcher {
+class DisplayListDispatcher final : public flutter::DlOpReceiver {
  public:
   DisplayListDispatcher();
 
@@ -22,75 +22,75 @@
 
   Picture EndRecordingAsPicture();
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setAntiAlias(bool aa) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setDither(bool dither) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setStyle(flutter::DlDrawStyle style) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setColor(flutter::DlColor color) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setStrokeWidth(SkScalar width) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setStrokeMiter(SkScalar limit) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setStrokeCap(flutter::DlStrokeCap cap) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setStrokeJoin(flutter::DlStrokeJoin join) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setColorSource(const flutter::DlColorSource* source) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setColorFilter(const flutter::DlColorFilter* filter) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setInvertColors(bool invert) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setBlendMode(flutter::DlBlendMode mode) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setPathEffect(const flutter::DlPathEffect* effect) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setMaskFilter(const flutter::DlMaskFilter* filter) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void setImageFilter(const flutter::DlImageFilter* filter) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void save() override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void saveLayer(const SkRect* bounds,
                  const flutter::SaveLayerOptions options,
                  const flutter::DlImageFilter* backdrop) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void restore() override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void translate(SkScalar tx, SkScalar ty) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void scale(SkScalar sx, SkScalar sy) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void rotate(SkScalar degrees) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void skew(SkScalar sx, SkScalar sy) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void transform2DAffine(SkScalar mxx,
                          SkScalar mxy,
                          SkScalar mxt,
@@ -98,7 +98,7 @@
                          SkScalar myy,
                          SkScalar myt) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void transformFullPerspective(SkScalar mxx,
                                 SkScalar mxy,
                                 SkScalar mxz,
@@ -116,82 +116,82 @@
                                 SkScalar mwz,
                                 SkScalar mwt) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void transformReset() override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void clipRect(const SkRect& rect, ClipOp clip_op, bool is_aa) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void clipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void clipPath(const SkPath& path, ClipOp clip_op, bool is_aa) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawColor(flutter::DlColor color, flutter::DlBlendMode mode) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawPaint() override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawLine(const SkPoint& p0, const SkPoint& p1) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawRect(const SkRect& rect) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawOval(const SkRect& bounds) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawCircle(const SkPoint& center, SkScalar radius) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawRRect(const SkRRect& rrect) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawDRRect(const SkRRect& outer, const SkRRect& inner) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawPath(const SkPath& path) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawArc(const SkRect& oval_bounds,
                SkScalar start_degrees,
                SkScalar sweep_degrees,
                bool use_center) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawPoints(PointMode mode,
                   uint32_t count,
                   const SkPoint points[]) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawVertices(const flutter::DlVertices* vertices,
                     flutter::DlBlendMode dl_mode) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawImage(const sk_sp<flutter::DlImage> image,
                  const SkPoint point,
                  flutter::DlImageSampling sampling,
                  bool render_with_attributes) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawImageRect(const sk_sp<flutter::DlImage> image,
                      const SkRect& src,
                      const SkRect& dst,
                      flutter::DlImageSampling sampling,
                      bool render_with_attributes,
-                     SkCanvas::SrcRectConstraint constraint) override;
+                     SrcRectConstraint constraint) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawImageNine(const sk_sp<flutter::DlImage> image,
                      const SkIRect& center,
                      const SkRect& dst,
                      flutter::DlFilterMode filter,
                      bool render_with_attributes) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawAtlas(const sk_sp<flutter::DlImage> atlas,
                  const SkRSXform xform[],
                  const SkRect tex[],
@@ -202,15 +202,16 @@
                  const SkRect* cull_rect,
                  bool render_with_attributes) override;
 
-  // |flutter::Dispatcher|
-  void drawDisplayList(const sk_sp<flutter::DisplayList> display_list) override;
+  // |flutter::DlOpReceiver|
+  void drawDisplayList(const sk_sp<flutter::DisplayList> display_list,
+                       SkScalar opacity) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawTextBlob(const sk_sp<SkTextBlob> blob,
                     SkScalar x,
                     SkScalar y) override;
 
-  // |flutter::Dispatcher|
+  // |flutter::DlOpReceiver|
   void drawShadow(const SkPath& path,
                   const flutter::DlColor color,
                   const SkScalar elevation,
diff --git a/impeller/display_list/display_list_unittests.cc b/impeller/display_list/display_list_unittests.cc
index 848f84f..7694196 100644
--- a/impeller/display_list/display_list_unittests.cc
+++ b/impeller/display_list/display_list_unittests.cc
@@ -44,16 +44,15 @@
 
 TEST_P(DisplayListTest, CanDrawRect) {
   flutter::DisplayListBuilder builder;
-  builder.setColor(SK_ColorBLUE);
-  builder.drawRect(SkRect::MakeXYWH(10, 10, 100, 100));
+  builder.DrawRect(SkRect::MakeXYWH(10, 10, 100, 100),
+                   flutter::DlPaint(flutter::DlColor::kBlue()));
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
 TEST_P(DisplayListTest, CanDrawTextBlob) {
   flutter::DisplayListBuilder builder;
-  builder.setColor(SK_ColorBLUE);
-  builder.drawTextBlob(SkTextBlob::MakeFromString("Hello", CreateTestFont()),
-                       100, 100);
+  builder.DrawTextBlob(SkTextBlob::MakeFromString("Hello", CreateTestFont()),
+                       100, 100, flutter::DlPaint(flutter::DlColor::kBlue()));
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
@@ -78,70 +77,69 @@
 
 TEST_P(DisplayListTest, CanDrawTextWithSaveLayer) {
   flutter::DisplayListBuilder builder;
-  builder.setColor(SK_ColorRED);
-  builder.drawTextBlob(SkTextBlob::MakeFromString("Hello", CreateTestFont()),
-                       100, 100);
+  builder.DrawTextBlob(SkTextBlob::MakeFromString("Hello", CreateTestFont()),
+                       100, 100, flutter::DlPaint(flutter::DlColor::kRed()));
 
   flutter::DlPaint save_paint;
   float alpha = 0.5;
   save_paint.setAlpha(static_cast<uint8_t>(255 * alpha));
   builder.SaveLayer(nullptr, &save_paint);
-  builder.setColor(SK_ColorRED);
-  builder.drawTextBlob(SkTextBlob::MakeFromString("Hello with half alpha",
+  builder.DrawTextBlob(SkTextBlob::MakeFromString("Hello with half alpha",
                                                   CreateTestFontOfSize(100)),
-                       100, 300);
-  builder.restore();
+                       100, 300, flutter::DlPaint(flutter::DlColor::kRed()));
+  builder.Restore();
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
 TEST_P(DisplayListTest, CanDrawImage) {
   auto texture = CreateTextureForFixture("embarcadero.jpg");
   flutter::DisplayListBuilder builder;
-  builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
-                    flutter::DlImageSampling::kNearestNeighbor, true);
+  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
+                    flutter::DlImageSampling::kNearestNeighbor, nullptr);
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
 TEST_P(DisplayListTest, CanDrawCapsAndJoins) {
   flutter::DisplayListBuilder builder;
+  flutter::DlPaint paint;
 
-  builder.setStyle(flutter::DlDrawStyle::kStroke);
-  builder.setStrokeWidth(30);
-  builder.setColor(SK_ColorRED);
+  paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
+  paint.setStrokeWidth(30);
+  paint.setColor(SK_ColorRED);
 
   auto path =
       SkPathBuilder{}.moveTo(-50, 0).lineTo(0, -50).lineTo(50, 0).snapshot();
 
-  builder.translate(100, 100);
+  builder.Translate(100, 100);
   {
-    builder.setStrokeCap(flutter::DlStrokeCap::kButt);
-    builder.setStrokeJoin(flutter::DlStrokeJoin::kMiter);
-    builder.setStrokeMiter(4);
-    builder.drawPath(path);
+    paint.setStrokeCap(flutter::DlStrokeCap::kButt);
+    paint.setStrokeJoin(flutter::DlStrokeJoin::kMiter);
+    paint.setStrokeMiter(4);
+    builder.DrawPath(path, paint);
   }
 
   {
-    builder.save();
-    builder.translate(0, 100);
+    builder.Save();
+    builder.Translate(0, 100);
     // The joint in the path is 45 degrees. A miter length of 1 convert to a
     // bevel in this case.
-    builder.setStrokeMiter(1);
-    builder.drawPath(path);
-    builder.restore();
+    paint.setStrokeMiter(1);
+    builder.DrawPath(path, paint);
+    builder.Restore();
   }
 
-  builder.translate(150, 0);
+  builder.Translate(150, 0);
   {
-    builder.setStrokeCap(flutter::DlStrokeCap::kSquare);
-    builder.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
-    builder.drawPath(path);
+    paint.setStrokeCap(flutter::DlStrokeCap::kSquare);
+    paint.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
+    builder.DrawPath(path, paint);
   }
 
-  builder.translate(150, 0);
+  builder.Translate(150, 0);
   {
-    builder.setStrokeCap(flutter::DlStrokeCap::kRound);
-    builder.setStrokeJoin(flutter::DlStrokeJoin::kRound);
-    builder.drawPath(path);
+    paint.setStrokeCap(flutter::DlStrokeCap::kRound);
+    paint.setStrokeJoin(flutter::DlStrokeJoin::kRound);
+    builder.DrawPath(path, paint);
   }
 
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
@@ -186,20 +184,21 @@
         Point(200, 200), Point(400, 400), 20, Color::White(), Color::White());
 
     flutter::DisplayListBuilder builder;
+    flutter::DlPaint paint;
 
     Vector2 scale = GetContentScale();
-    builder.scale(scale.x, scale.y);
-    builder.setStyle(flutter::DlDrawStyle::kStroke);
-    builder.setStrokeCap(cap);
-    builder.setStrokeJoin(flutter::DlStrokeJoin::kMiter);
-    builder.setStrokeMiter(10);
+    builder.Scale(scale.x, scale.y);
+    paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
+    paint.setStrokeCap(cap);
+    paint.setStrokeJoin(flutter::DlStrokeJoin::kMiter);
+    paint.setStrokeMiter(10);
     auto rect = SkRect::MakeLTRB(p1.x, p1.y, p2.x, p2.y);
-    builder.setColor(SK_ColorGREEN);
-    builder.setStrokeWidth(2);
-    builder.drawRect(rect);
-    builder.setColor(SK_ColorRED);
-    builder.setStrokeWidth(stroke_width);
-    builder.drawArc(rect, start_angle, sweep_angle, use_center);
+    paint.setColor(SK_ColorGREEN);
+    paint.setStrokeWidth(2);
+    builder.DrawRect(rect, paint);
+    paint.setColor(SK_ColorRED);
+    paint.setStrokeWidth(stroke_width);
+    builder.DrawArc(rect, start_angle, sweep_angle, use_center, paint);
 
     return builder.Build();
   };
@@ -209,8 +208,10 @@
 TEST_P(DisplayListTest, StrokedPathsDrawCorrectly) {
   auto callback = [&]() {
     flutter::DisplayListBuilder builder;
-    builder.setColor(SK_ColorRED);
-    builder.setStyle(flutter::DlDrawStyle::kStroke);
+    flutter::DlPaint paint;
+
+    paint.setColor(SK_ColorRED);
+    paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
 
     static float stroke_width = 10.0f;
     static int selected_stroke_type = 0;
@@ -256,45 +257,46 @@
         join = flutter::DlStrokeJoin::kMiter;
         break;
     }
-    builder.setStrokeCap(cap);
-    builder.setStrokeJoin(join);
-    builder.setStrokeWidth(stroke_width);
+    paint.setStrokeCap(cap);
+    paint.setStrokeJoin(join);
+    paint.setStrokeWidth(stroke_width);
 
     // Make rendering better to watch.
-    builder.scale(1.5f, 1.5f);
+    builder.Scale(1.5f, 1.5f);
 
     // Rectangle
-    builder.translate(100, 100);
-    builder.drawRect(SkRect::MakeSize({100, 100}));
+    builder.Translate(100, 100);
+    builder.DrawRect(SkRect::MakeSize({100, 100}), paint);
 
     // Rounded rectangle
-    builder.translate(150, 0);
-    builder.drawRRect(SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10));
+    builder.Translate(150, 0);
+    builder.DrawRRect(SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10),
+                      paint);
 
     // Double rounded rectangle
-    builder.translate(150, 0);
-    builder.drawDRRect(
+    builder.Translate(150, 0);
+    builder.DrawDRRect(
         SkRRect::MakeRectXY(SkRect::MakeSize({100, 50}), 10, 10),
-        SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 30), 10, 10));
+        SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 10, 80, 30), 10, 10), paint);
 
     // Contour with duplicate join points
     {
-      builder.translate(150, 0);
+      builder.Translate(150, 0);
       SkPath path;
       path.moveTo(0, 0);
       path.lineTo(0, 0);
       path.lineTo({100, 0});
       path.lineTo({100, 0});
       path.lineTo({100, 100});
-      builder.drawPath(path);
+      builder.DrawPath(path, paint);
     }
 
     // Contour with duplicate start and end points
 
     // Line.
-    builder.translate(200, 0);
+    builder.Translate(200, 0);
     {
-      builder.save();
+      builder.Save();
 
       SkPath line_path;
       line_path.moveTo(0, 0);
@@ -305,71 +307,71 @@
       line_path.lineTo({50, 50});
       line_path.lineTo({100, 0});
       line_path.lineTo({100, 0});
-      builder.drawPath(line_path);
+      builder.DrawPath(line_path, paint);
 
-      builder.translate(0, 100);
-      builder.drawPath(line_path);
+      builder.Translate(0, 100);
+      builder.DrawPath(line_path, paint);
 
-      builder.translate(0, 100);
+      builder.Translate(0, 100);
       SkPath line_path2;
       line_path2.moveTo(0, 0);
       line_path2.lineTo(0, 0);
       line_path2.lineTo(0, 0);
-      builder.drawPath(line_path2);
+      builder.DrawPath(line_path2, paint);
 
-      builder.restore();
+      builder.Restore();
     }
 
     // Cubic.
-    builder.translate(150, 0);
+    builder.Translate(150, 0);
     {
-      builder.save();
+      builder.Save();
 
       SkPath cubic_path;
       cubic_path.moveTo({0, 0});
       cubic_path.cubicTo(0, 0, 140.0, 100.0, 140, 20);
-      builder.drawPath(cubic_path);
+      builder.DrawPath(cubic_path, paint);
 
-      builder.translate(0, 100);
+      builder.Translate(0, 100);
       SkPath cubic_path2;
       cubic_path2.moveTo({0, 0});
       cubic_path2.cubicTo(0, 0, 0, 0, 150, 150);
-      builder.drawPath(cubic_path2);
+      builder.DrawPath(cubic_path2, paint);
 
-      builder.translate(0, 100);
+      builder.Translate(0, 100);
       SkPath cubic_path3;
       cubic_path3.moveTo({0, 0});
       cubic_path3.cubicTo(0, 0, 0, 0, 0, 0);
-      builder.drawPath(cubic_path3);
+      builder.DrawPath(cubic_path3, paint);
 
-      builder.restore();
+      builder.Restore();
     }
 
     // Quad.
-    builder.translate(200, 0);
+    builder.Translate(200, 0);
     {
-      builder.save();
+      builder.Save();
 
       SkPath quad_path;
       quad_path.moveTo(0, 0);
       quad_path.moveTo(0, 0);
       quad_path.quadTo({100, 40}, {50, 80});
-      builder.drawPath(quad_path);
+      builder.DrawPath(quad_path, paint);
 
-      builder.translate(0, 150);
+      builder.Translate(0, 150);
       SkPath quad_path2;
       quad_path2.moveTo(0, 0);
       quad_path2.moveTo(0, 0);
       quad_path2.quadTo({0, 0}, {100, 100});
-      builder.drawPath(quad_path2);
+      builder.DrawPath(quad_path2, paint);
 
-      builder.translate(0, 100);
+      builder.Translate(0, 100);
       SkPath quad_path3;
       quad_path3.moveTo(0, 0);
       quad_path3.quadTo({0, 0}, {0, 0});
-      builder.drawPath(quad_path3);
+      builder.DrawPath(quad_path3, paint);
 
-      builder.restore();
+      builder.Restore();
     }
     return builder.Build();
   };
@@ -378,15 +380,17 @@
 
 TEST_P(DisplayListTest, CanDrawWithOddPathWinding) {
   flutter::DisplayListBuilder builder;
-  builder.setColor(SK_ColorRED);
-  builder.setStyle(flutter::DlDrawStyle::kFill);
+  flutter::DlPaint paint;
 
-  builder.translate(300, 300);
+  paint.setColor(SK_ColorRED);
+  paint.setDrawStyle(flutter::DlDrawStyle::kFill);
+
+  builder.Translate(300, 300);
   SkPath path;
   path.setFillType(SkPathFillType::kEvenOdd);
   path.addCircle(0, 0, 100);
   path.addCircle(0, 0, 50);
-  builder.drawPath(path);
+  builder.DrawPath(path, paint);
 
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
@@ -394,29 +398,31 @@
 TEST_P(DisplayListTest, CanDrawWithMaskBlur) {
   auto texture = CreateTextureForFixture("embarcadero.jpg");
   flutter::DisplayListBuilder builder;
+  flutter::DlPaint paint;
 
   // Mask blurred image.
   {
     auto filter = flutter::DlBlurMaskFilter(kNormal_SkBlurStyle, 10.0f);
-    builder.setMaskFilter(&filter);
-    builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
-                      flutter::DlImageSampling::kNearestNeighbor, true);
+    paint.setMaskFilter(&filter);
+    builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
+                      flutter::DlImageSampling::kNearestNeighbor, &paint);
   }
 
   // Mask blurred filled path.
   {
-    builder.setColor(SK_ColorYELLOW);
+    paint.setColor(SK_ColorYELLOW);
     auto filter = flutter::DlBlurMaskFilter(kOuter_SkBlurStyle, 10.0f);
-    builder.setMaskFilter(&filter);
-    builder.drawArc(SkRect::MakeXYWH(410, 110, 100, 100), 45, 270, true);
+    paint.setMaskFilter(&filter);
+    builder.DrawArc(SkRect::MakeXYWH(410, 110, 100, 100), 45, 270, true, paint);
   }
 
   // Mask blurred text.
   {
     auto filter = flutter::DlBlurMaskFilter(kSolid_SkBlurStyle, 10.0f);
-    builder.setMaskFilter(&filter);
-    builder.drawTextBlob(
-        SkTextBlob::MakeFromString("Testing", CreateTestFont()), 220, 170);
+    paint.setMaskFilter(&filter);
+    builder.DrawTextBlob(
+        SkTextBlob::MakeFromString("Testing", CreateTestFont()), 220, 170,
+        paint);
   }
 
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
@@ -438,23 +444,24 @@
 TEST_P(DisplayListTest, CanDrawWithBlendColorFilter) {
   auto texture = CreateTextureForFixture("embarcadero.jpg");
   flutter::DisplayListBuilder builder;
+  flutter::DlPaint paint;
 
   // Pipeline blended image.
   {
     auto filter = flutter::DlBlendColorFilter(SK_ColorYELLOW,
                                               flutter::DlBlendMode::kModulate);
-    builder.setColorFilter(&filter);
-    builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
-                      flutter::DlImageSampling::kNearestNeighbor, true);
+    paint.setColorFilter(&filter);
+    builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
+                      flutter::DlImageSampling::kNearestNeighbor, &paint);
   }
 
   // Advanced blended image.
   {
     auto filter =
         flutter::DlBlendColorFilter(SK_ColorRED, flutter::DlBlendMode::kScreen);
-    builder.setColorFilter(&filter);
-    builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(250, 250),
-                      flutter::DlImageSampling::kNearestNeighbor, true);
+    paint.setColorFilter(&filter);
+    builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(250, 250),
+                      flutter::DlImageSampling::kNearestNeighbor, &paint);
   }
 
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
@@ -469,18 +476,21 @@
   };
   auto texture = CreateTextureForFixture("boston.jpg");
   flutter::DisplayListBuilder builder;
+  flutter::DlPaint paint;
+
   auto color_filter =
       std::make_shared<flutter::DlMatrixColorFilter>(invert_color_matrix);
   auto image_filter =
       std::make_shared<flutter::DlColorFilterImageFilter>(color_filter);
-  builder.setImageFilter(image_filter.get());
-  builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
-                    flutter::DlImageSampling::kNearestNeighbor, true);
 
-  builder.translate(0, 700);
-  builder.setColorFilter(color_filter.get());
-  builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
-                    flutter::DlImageSampling::kNearestNeighbor, true);
+  paint.setImageFilter(image_filter.get());
+  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
+                    flutter::DlImageSampling::kNearestNeighbor, &paint);
+
+  builder.Translate(0, 700);
+  paint.setColorFilter(color_filter.get());
+  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
+                    flutter::DlImageSampling::kNearestNeighbor, &paint);
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
@@ -495,12 +505,13 @@
     ImGui::End();
 
     flutter::DisplayListBuilder builder;
+    flutter::DlPaint paint;
 
     auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1],
                                              flutter::DlTileMode::kClamp);
-    builder.setImageFilter(&filter);
-    builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200),
-                      flutter::DlImageSampling::kNearestNeighbor, true);
+    paint.setImageFilter(&filter);
+    builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200),
+                      flutter::DlImageSampling::kNearestNeighbor, &paint);
 
     return builder.Build();
   };
@@ -511,17 +522,20 @@
 TEST_P(DisplayListTest, CanDrawWithComposeImageFilter) {
   auto texture = CreateTextureForFixture("boston.jpg");
   flutter::DisplayListBuilder builder;
+  flutter::DlPaint paint;
+
   auto dilate = std::make_shared<flutter::DlDilateImageFilter>(10.0, 10.0);
   auto erode = std::make_shared<flutter::DlErodeImageFilter>(10.0, 10.0);
   auto open = std::make_shared<flutter::DlComposeImageFilter>(dilate, erode);
   auto close = std::make_shared<flutter::DlComposeImageFilter>(erode, dilate);
-  builder.setImageFilter(open.get());
-  builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
-                    flutter::DlImageSampling::kNearestNeighbor, true);
-  builder.translate(0, 700);
-  builder.setImageFilter(close.get());
-  builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
-                    flutter::DlImageSampling::kNearestNeighbor, true);
+
+  paint.setImageFilter(open.get());
+  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
+                    flutter::DlImageSampling::kNearestNeighbor, &paint);
+  builder.Translate(0, 700);
+  paint.setImageFilter(close.get());
+  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
+                    flutter::DlImageSampling::kNearestNeighbor, &paint);
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
@@ -550,9 +564,10 @@
   auto compose = std::make_shared<flutter::DlComposeImageFilter>(outer, inner);
 
   flutter::DisplayListBuilder builder;
-  builder.setImageFilter(compose.get());
-  builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
-                    flutter::DlImageSampling::kNearestNeighbor, true);
+  flutter::DlPaint paint;
+  paint.setImageFilter(compose.get());
+  builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(100, 100),
+                    flutter::DlImageSampling::kNearestNeighbor, &paint);
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
@@ -686,7 +701,7 @@
     flutter::DisplayListBuilder builder;
 
     Vector2 scale = ctm_scale * GetContentScale();
-    builder.scale(scale.x, scale.y);
+    builder.Scale(scale.x, scale.y);
 
     auto filter = flutter::DlBlurImageFilter(sigma[0], sigma[1],
                                              flutter::DlTileMode::kClamp);
@@ -701,12 +716,12 @@
     // Insert a clip to test that the backdrop filter handles stencil depths > 0
     // correctly.
     if (add_clip) {
-      builder.clipRect(SkRect::MakeLTRB(0, 0, 99999, 99999),
+      builder.ClipRect(SkRect::MakeLTRB(0, 0, 99999, 99999),
                        flutter::DlCanvas::ClipOp::kIntersect, true);
     }
 
-    builder.drawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200),
-                      flutter::DlImageSampling::kNearestNeighbor, true);
+    builder.DrawImage(DlImageImpeller::Make(texture), SkPoint::Make(200, 200),
+                      flutter::DlImageSampling::kNearestNeighbor, nullptr);
     builder.SaveLayer(bounds.has_value() ? &bounds.value() : nullptr, nullptr,
                       &filter);
 
@@ -714,12 +729,13 @@
       auto circle_center =
           IMPELLER_PLAYGROUND_POINT(Point(500, 400), 20, Color::Red());
 
-      builder.setStyle(flutter::DlDrawStyle::kStroke);
-      builder.setStrokeCap(flutter::DlStrokeCap::kButt);
-      builder.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
-      builder.setStrokeWidth(10);
-      builder.setColor(flutter::DlColor::kRed().withAlpha(100));
-      builder.drawCircle({circle_center.x, circle_center.y}, 100);
+      flutter::DlPaint paint;
+      paint.setDrawStyle(flutter::DlDrawStyle::kStroke);
+      paint.setStrokeCap(flutter::DlStrokeCap::kButt);
+      paint.setStrokeJoin(flutter::DlStrokeJoin::kBevel);
+      paint.setStrokeWidth(10);
+      paint.setColor(flutter::DlColor::kRed().withAlpha(100));
+      builder.DrawCircle({circle_center.x, circle_center.y}, 100, paint);
     }
 
     return builder.Build();
@@ -733,12 +749,12 @@
   auto texture = CreateTextureForFixture("embarcadero.jpg");
   flutter::DisplayListBuilder builder;
   auto size = texture->GetSize();
-  builder.drawImageNine(
+  builder.DrawImageNine(
       DlImageImpeller::Make(texture),
       SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
                         size.height * 3 / 4),
       SkRect::MakeLTRB(0, 0, size.width * 2, size.height * 2),
-      flutter::DlFilterMode::kNearest, true);
+      flutter::DlFilterMode::kNearest, nullptr);
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
@@ -749,12 +765,12 @@
   auto texture = CreateTextureForFixture("embarcadero.jpg");
   flutter::DisplayListBuilder builder;
   auto size = texture->GetSize();
-  builder.drawImageNine(
+  builder.DrawImageNine(
       DlImageImpeller::Make(texture),
       SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
                         size.height * 3 / 4),
       SkRect::MakeLTRB(0, 0, size.width / 2, size.height),
-      flutter::DlFilterMode::kNearest, true);
+      flutter::DlFilterMode::kNearest, nullptr);
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
@@ -765,12 +781,12 @@
   auto texture = CreateTextureForFixture("embarcadero.jpg");
   flutter::DisplayListBuilder builder;
   auto size = texture->GetSize();
-  builder.drawImageNine(
+  builder.DrawImageNine(
       DlImageImpeller::Make(texture),
       SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
                         size.height * 3 / 4),
       SkRect::MakeLTRB(0, 0, size.width, size.height / 2),
-      flutter::DlFilterMode::kNearest, true);
+      flutter::DlFilterMode::kNearest, nullptr);
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
@@ -780,12 +796,12 @@
   auto texture = CreateTextureForFixture("embarcadero.jpg");
   flutter::DisplayListBuilder builder;
   auto size = texture->GetSize();
-  builder.drawImageNine(
+  builder.DrawImageNine(
       DlImageImpeller::Make(texture),
       SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
                         size.height * 3 / 4),
       SkRect::MakeLTRB(0, 0, size.width / 2, size.height / 2),
-      flutter::DlFilterMode::kNearest, true);
+      flutter::DlFilterMode::kNearest, nullptr);
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
@@ -795,12 +811,12 @@
   auto texture = CreateTextureForFixture("embarcadero.jpg");
   flutter::DisplayListBuilder builder;
   auto size = texture->GetSize();
-  builder.drawImageNine(
+  builder.DrawImageNine(
       DlImageImpeller::Make(texture),
       SkIRect::MakeLTRB(size.width / 4, size.height / 4, size.width * 3 / 4,
                         size.height * 3 / 4),
       SkRect::MakeLTRB(0, 0, size.width / 4, size.height / 4),
-      flutter::DlFilterMode::kNearest, true);
+      flutter::DlFilterMode::kNearest, nullptr);
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
 }
 
@@ -865,9 +881,10 @@
 
 TEST_P(DisplayListTest, CanDrawShadow) {
   flutter::DisplayListBuilder builder;
+  flutter::DlPaint paint;
 
   auto content_scale = GetContentScale() * 0.8;
-  builder.scale(content_scale.x, content_scale.y);
+  builder.Scale(content_scale.x, content_scale.y);
 
   constexpr size_t star_spikes = 5;
   constexpr SkScalar half_spike_rotation = kPi / star_spikes;
@@ -892,20 +909,20 @@
       SkPath{}.addCircle(100, 50, 50),
       SkPath{}.addPoly(star.data(), star.size(), true),
   };
-  builder.setColor(flutter::DlColor::kWhite());
-  builder.drawPaint();
-  builder.setColor(flutter::DlColor::kCyan());
-  builder.translate(100, 50);
+  paint.setColor(flutter::DlColor::kWhite());
+  builder.DrawPaint(paint);
+  paint.setColor(flutter::DlColor::kCyan());
+  builder.Translate(100, 50);
   for (size_t x = 0; x < paths.size(); x++) {
-    builder.save();
+    builder.Save();
     for (size_t y = 0; y < 6; y++) {
-      builder.drawShadow(paths[x], flutter::DlColor::kBlack(), 3 + y * 8, false,
+      builder.DrawShadow(paths[x], flutter::DlColor::kBlack(), 3 + y * 8, false,
                          1);
-      builder.drawPath(paths[x]);
-      builder.translate(0, 150);
+      builder.DrawPath(paths[x], paint);
+      builder.Translate(0, 150);
     }
-    builder.restore();
-    builder.translate(250, 0);
+    builder.Restore();
+    builder.Translate(250, 0);
   }
 
   ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
@@ -1021,20 +1038,21 @@
     ImGui::End();
 
     flutter::DisplayListBuilder builder;
-    SkPaint paint;
+    flutter::DlPaint paint;
+
     if (enable_savelayer) {
       builder.SaveLayer(nullptr, nullptr);
     }
     {
       auto content_scale = GetContentScale();
-      builder.scale(content_scale.x, content_scale.y);
+      builder.Scale(content_scale.x, content_scale.y);
 
       // Set the current transform
       auto ctm_matrix =
           SkMatrix::MakeAll(ctm_scale[0], ctm_skew[0], ctm_translation[0],  //
                             ctm_skew[1], ctm_scale[1], ctm_translation[1],  //
                             0, 0, 1);
-      builder.transform(ctm_matrix);
+      builder.Transform(ctm_matrix);
 
       // Set the matrix filter
       auto filter_matrix =
@@ -1047,7 +1065,7 @@
           case 0: {
             auto filter = flutter::DlMatrixImageFilter(
                 filter_matrix, flutter::DlImageSampling::kLinear);
-            builder.setImageFilter(&filter);
+            paint.setImageFilter(&filter);
             break;
           }
           case 1: {
@@ -1056,17 +1074,17 @@
                     .shared();
             auto filter = flutter::DlLocalMatrixImageFilter(filter_matrix,
                                                             internal_filter);
-            builder.setImageFilter(&filter);
+            paint.setImageFilter(&filter);
             break;
           }
         }
       }
 
-      builder.drawImage(DlImageImpeller::Make(boston), {},
-                        flutter::DlImageSampling::kLinear, true);
+      builder.DrawImage(DlImageImpeller::Make(boston), {},
+                        flutter::DlImageSampling::kLinear, &paint);
     }
     if (enable_savelayer) {
-      builder.restore();
+      builder.Restore();
     }
 
     return builder.Build();
diff --git a/lib/ui/painting/canvas.cc b/lib/ui/painting/canvas.cc
index 92f7043..03d8385 100644
--- a/lib/ui/painting/canvas.cc
+++ b/lib/ui/painting/canvas.cc
@@ -47,7 +47,7 @@
 
 void Canvas::save() {
   if (display_list_builder_) {
-    builder()->save();
+    builder()->Save();
   }
 }
 
@@ -57,11 +57,11 @@
 
   FML_DCHECK(paint.isNotNull());
   if (display_list_builder_) {
-    bool restore_with_paint =
-        paint.sync_to(builder(), kSaveLayerWithPaintFlags);
-    FML_DCHECK(restore_with_paint);
+    DlPaint dl_paint;
+    const DlPaint* save_paint = paint.paint(dl_paint, kSaveLayerWithPaintFlags);
+    FML_DCHECK(save_paint);
     TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)");
-    builder()->saveLayer(nullptr, restore_with_paint);
+    builder()->SaveLayer(nullptr, save_paint);
   }
 }
 
@@ -76,17 +76,17 @@
   FML_DCHECK(paint.isNotNull());
   SkRect bounds = SkRect::MakeLTRB(left, top, right, bottom);
   if (display_list_builder_) {
-    bool restore_with_paint =
-        paint.sync_to(builder(), kSaveLayerWithPaintFlags);
-    FML_DCHECK(restore_with_paint);
+    DlPaint dl_paint;
+    const DlPaint* save_paint = paint.paint(dl_paint, kSaveLayerWithPaintFlags);
+    FML_DCHECK(save_paint);
     TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)");
-    builder()->saveLayer(&bounds, restore_with_paint);
+    builder()->SaveLayer(&bounds, save_paint);
   }
 }
 
 void Canvas::restore() {
   if (display_list_builder_) {
-    builder()->restore();
+    builder()->Restore();
   }
 }
 
@@ -100,31 +100,31 @@
 
 void Canvas::restoreToCount(int count) {
   if (display_list_builder_ && count < getSaveCount()) {
-    builder()->restoreToCount(count);
+    builder()->RestoreToCount(count);
   }
 }
 
 void Canvas::translate(double dx, double dy) {
   if (display_list_builder_) {
-    builder()->translate(dx, dy);
+    builder()->Translate(dx, dy);
   }
 }
 
 void Canvas::scale(double sx, double sy) {
   if (display_list_builder_) {
-    builder()->scale(sx, sy);
+    builder()->Scale(sx, sy);
   }
 }
 
 void Canvas::rotate(double radians) {
   if (display_list_builder_) {
-    builder()->rotate(radians * 180.0 / M_PI);
+    builder()->Rotate(radians * 180.0 / M_PI);
   }
 }
 
 void Canvas::skew(double sx, double sy) {
   if (display_list_builder_) {
-    builder()->skew(sx, sy);
+    builder()->Skew(sx, sy);
   }
 }
 
@@ -133,7 +133,7 @@
   // Both DisplayList and SkM44 constructor take row-major matrix order
   if (display_list_builder_) {
     // clang-format off
-    builder()->transformFullPerspective(
+    builder()->TransformFullPerspective(
         matrix4[ 0], matrix4[ 4], matrix4[ 8], matrix4[12],
         matrix4[ 1], matrix4[ 5], matrix4[ 9], matrix4[13],
         matrix4[ 2], matrix4[ 6], matrix4[10], matrix4[14],
@@ -162,14 +162,14 @@
                       DlCanvas::ClipOp clipOp,
                       bool doAntiAlias) {
   if (display_list_builder_) {
-    builder()->clipRect(SkRect::MakeLTRB(left, top, right, bottom), clipOp,
+    builder()->ClipRect(SkRect::MakeLTRB(left, top, right, bottom), clipOp,
                         doAntiAlias);
   }
 }
 
 void Canvas::clipRRect(const RRect& rrect, bool doAntiAlias) {
   if (display_list_builder_) {
-    builder()->clipRRect(rrect.sk_rrect, DlCanvas::ClipOp::kIntersect,
+    builder()->ClipRRect(rrect.sk_rrect, DlCanvas::ClipOp::kIntersect,
                          doAntiAlias);
   }
 }
@@ -181,7 +181,7 @@
     return;
   }
   if (display_list_builder_) {
-    builder()->clipPath(path->path(), DlCanvas::ClipOp::kIntersect,
+    builder()->ClipPath(path->path(), DlCanvas::ClipOp::kIntersect,
                         doAntiAlias);
   }
 }
@@ -210,7 +210,7 @@
 
 void Canvas::drawColor(SkColor color, DlBlendMode blend_mode) {
   if (display_list_builder_) {
-    builder()->drawColor(color, blend_mode);
+    builder()->DrawColor(color, blend_mode);
   }
 }
 
@@ -224,8 +224,9 @@
 
   FML_DCHECK(paint.isNotNull());
   if (display_list_builder_) {
-    paint.sync_to(builder(), kDrawLineFlags);
-    builder()->drawLine(SkPoint::Make(x1, y1), SkPoint::Make(x2, y2));
+    DlPaint dl_paint;
+    paint.paint(dl_paint, kDrawLineFlags);
+    builder()->DrawLine(SkPoint::Make(x1, y1), SkPoint::Make(x2, y2), dl_paint);
   }
 }
 
@@ -234,14 +235,15 @@
 
   FML_DCHECK(paint.isNotNull());
   if (display_list_builder_) {
-    paint.sync_to(builder(), kDrawPaintFlags);
-    std::shared_ptr<const DlImageFilter> filter = builder()->getImageFilter();
+    DlPaint dl_paint;
+    paint.paint(dl_paint, kDrawPaintFlags);
+    std::shared_ptr<const DlImageFilter> filter = dl_paint.getImageFilter();
     if (filter && !filter->asColorFilter()) {
       // drawPaint does an implicit saveLayer if an SkImageFilter is
       // present that cannot be replaced by an SkColorFilter.
       TRACE_EVENT0("flutter", "ui.Canvas::saveLayer (Recorded)");
     }
-    builder()->drawPaint();
+    builder()->DrawPaint(dl_paint);
   }
 }
 
@@ -255,8 +257,9 @@
 
   FML_DCHECK(paint.isNotNull());
   if (display_list_builder_) {
-    paint.sync_to(builder(), kDrawRectFlags);
-    builder()->drawRect(SkRect::MakeLTRB(left, top, right, bottom));
+    DlPaint dl_paint;
+    paint.paint(dl_paint, kDrawRectFlags);
+    builder()->DrawRect(SkRect::MakeLTRB(left, top, right, bottom), dl_paint);
   }
 }
 
@@ -267,8 +270,9 @@
 
   FML_DCHECK(paint.isNotNull());
   if (display_list_builder_) {
-    paint.sync_to(builder(), kDrawRRectFlags);
-    builder()->drawRRect(rrect.sk_rrect);
+    DlPaint dl_paint;
+    paint.paint(dl_paint, kDrawRRectFlags);
+    builder()->DrawRRect(rrect.sk_rrect, dl_paint);
   }
 }
 
@@ -280,8 +284,9 @@
 
   FML_DCHECK(paint.isNotNull());
   if (display_list_builder_) {
-    paint.sync_to(builder(), kDrawDRRectFlags);
-    builder()->drawDRRect(outer.sk_rrect, inner.sk_rrect);
+    DlPaint dl_paint;
+    paint.paint(dl_paint, kDrawDRRectFlags);
+    builder()->DrawDRRect(outer.sk_rrect, inner.sk_rrect, dl_paint);
   }
 }
 
@@ -295,8 +300,9 @@
 
   FML_DCHECK(paint.isNotNull());
   if (display_list_builder_) {
-    paint.sync_to(builder(), kDrawOvalFlags);
-    builder()->drawOval(SkRect::MakeLTRB(left, top, right, bottom));
+    DlPaint dl_paint;
+    paint.paint(dl_paint, kDrawOvalFlags);
+    builder()->DrawOval(SkRect::MakeLTRB(left, top, right, bottom), dl_paint);
   }
 }
 
@@ -309,8 +315,9 @@
 
   FML_DCHECK(paint.isNotNull());
   if (display_list_builder_) {
-    paint.sync_to(builder(), kDrawCircleFlags);
-    builder()->drawCircle(SkPoint::Make(x, y), radius);
+    DlPaint dl_paint;
+    paint.paint(dl_paint, kDrawCircleFlags);
+    builder()->DrawCircle(SkPoint::Make(x, y), radius, dl_paint);
   }
 }
 
@@ -327,13 +334,13 @@
 
   FML_DCHECK(paint.isNotNull());
   if (display_list_builder_) {
-    paint.sync_to(builder(),
-                  useCenter  //
-                      ? kDrawArcWithCenterFlags
-                      : kDrawArcNoCenterFlags);
-    builder()->drawArc(SkRect::MakeLTRB(left, top, right, bottom),
+    DlPaint dl_paint;
+    paint.paint(dl_paint, useCenter  //
+                              ? kDrawArcWithCenterFlags
+                              : kDrawArcNoCenterFlags);
+    builder()->DrawArc(SkRect::MakeLTRB(left, top, right, bottom),
                        startAngle * 180.0 / M_PI, sweepAngle * 180.0 / M_PI,
-                       useCenter);
+                       useCenter, dl_paint);
   }
 }
 
@@ -349,8 +356,9 @@
     return;
   }
   if (display_list_builder_) {
-    paint.sync_to(builder(), kDrawPathFlags);
-    builder()->drawPath(path->path());
+    DlPaint dl_paint;
+    paint.paint(dl_paint, kDrawPathFlags);
+    builder()->DrawPath(path->path(), dl_paint);
   }
 }
 
@@ -378,9 +386,9 @@
 
   auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);
   if (display_list_builder_) {
-    bool with_attributes = paint.sync_to(builder(), kDrawImageWithPaintFlags);
-    builder()->drawImage(dl_image, SkPoint::Make(x, y), sampling,
-                         with_attributes);
+    DlPaint dl_paint;
+    const DlPaint* opt_paint = paint.paint(dl_paint, kDrawImageWithPaintFlags);
+    builder()->DrawImage(dl_image, SkPoint::Make(x, y), sampling, opt_paint);
   }
   return Dart_Null();
 }
@@ -417,10 +425,11 @@
   SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom);
   auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);
   if (display_list_builder_) {
-    bool with_attributes =
-        paint.sync_to(builder(), kDrawImageRectWithPaintFlags);
-    builder()->drawImageRect(dl_image, src, dst, sampling, with_attributes,
-                             SkCanvas::kFast_SrcRectConstraint);
+    DlPaint dl_paint;
+    const DlPaint* opt_paint =
+        paint.paint(dl_paint, kDrawImageRectWithPaintFlags);
+    builder()->DrawImageRect(dl_image, src, dst, sampling, opt_paint,
+                             DlCanvas::SrcRectConstraint::kFast);
   }
   return Dart_Null();
 }
@@ -459,9 +468,10 @@
   SkRect dst = SkRect::MakeLTRB(dst_left, dst_top, dst_right, dst_bottom);
   auto filter = ImageFilter::FilterModeFromIndex(bitmapSamplingIndex);
   if (display_list_builder_) {
-    bool with_attributes =
-        paint.sync_to(builder(), kDrawImageNineWithPaintFlags);
-    builder()->drawImageNine(dl_image, icenter, dst, filter, with_attributes);
+    DlPaint dl_paint;
+    const DlPaint* opt_paint =
+        paint.paint(dl_paint, kDrawImageNineWithPaintFlags);
+    builder()->DrawImageNine(dl_image, icenter, dst, filter, opt_paint);
   }
   return Dart_Null();
 }
@@ -474,7 +484,7 @@
   }
   if (picture->display_list()) {
     if (display_list_builder_) {
-      builder()->drawDisplayList(picture->display_list());
+      builder()->DrawDisplayList(picture->display_list());
     }
   } else {
     FML_DCHECK(false);
@@ -492,20 +502,22 @@
 
   FML_DCHECK(paint.isNotNull());
   if (display_list_builder_) {
+    DlPaint dl_paint;
     switch (point_mode) {
       case DlCanvas::PointMode::kPoints:
-        paint.sync_to(builder(), kDrawPointsAsPointsFlags);
+        paint.paint(dl_paint, kDrawPointsAsPointsFlags);
         break;
       case DlCanvas::PointMode::kLines:
-        paint.sync_to(builder(), kDrawPointsAsLinesFlags);
+        paint.paint(dl_paint, kDrawPointsAsLinesFlags);
         break;
       case DlCanvas::PointMode::kPolygon:
-        paint.sync_to(builder(), kDrawPointsAsPolygonFlags);
+        paint.paint(dl_paint, kDrawPointsAsPolygonFlags);
         break;
     }
-    builder()->drawPoints(point_mode,
+    builder()->DrawPoints(point_mode,
                           points.num_elements() / 2,  // SkPoints have 2 floats
-                          reinterpret_cast<const SkPoint*>(points.data()));
+                          reinterpret_cast<const SkPoint*>(points.data()),
+                          dl_paint);
   }
 }
 
@@ -522,8 +534,9 @@
   }
   FML_DCHECK(paint.isNotNull());
   if (display_list_builder_) {
-    paint.sync_to(builder(), kDrawVerticesFlags);
-    builder()->drawVertices(vertices->vertices(), blend_mode);
+    DlPaint dl_paint;
+    paint.paint(dl_paint, kDrawVerticesFlags);
+    builder()->DrawVertices(vertices->vertices(), blend_mode, dl_paint);
   }
 }
 
@@ -564,14 +577,15 @@
     tonic::Int32List colors(colors_handle);
     tonic::Float32List cull_rect(cull_rect_handle);
 
-    bool with_attributes = paint.sync_to(builder(), kDrawAtlasWithPaintFlags);
-    builder()->drawAtlas(
+    DlPaint dl_paint;
+    const DlPaint* opt_paint = paint.paint(dl_paint, kDrawAtlasWithPaintFlags);
+    builder()->DrawAtlas(
         dl_image, reinterpret_cast<const SkRSXform*>(transforms.data()),
         reinterpret_cast<const SkRect*>(rects.data()),
         reinterpret_cast<const DlColor*>(colors.data()),
         rects.num_elements() / 4,  // SkRect have four floats.
         blend_mode, sampling, reinterpret_cast<const SkRect*>(cull_rect.data()),
-        with_attributes);
+        opt_paint);
   }
   return Dart_Null();
 }
@@ -598,7 +612,7 @@
     // that situation we bypass the canvas interface and inject the
     // shadow parameters directly into the underlying DisplayList.
     // See: https://bugs.chromium.org/p/skia/issues/detail?id=12125
-    builder()->drawShadow(path->path(), color, elevation, transparentOccluder,
+    builder()->DrawShadow(path->path(), color, elevation, transparentOccluder,
                           dpr);
   }
 }
diff --git a/lib/ui/painting/color_filter.cc b/lib/ui/painting/color_filter.cc
index 12b0db9..06d421b 100644
--- a/lib/ui/painting/color_filter.cc
+++ b/lib/ui/painting/color_filter.cc
@@ -23,8 +23,8 @@
 }
 
 void ColorFilter::initMode(int color, int blend_mode) {
-  filter_ = std::make_shared<DlBlendColorFilter>(
-      static_cast<DlColor>(color), static_cast<DlBlendMode>(blend_mode));
+  filter_ = DlBlendColorFilter::Make(static_cast<DlColor>(color),
+                                     static_cast<DlBlendMode>(blend_mode));
 }
 
 void ColorFilter::initMatrix(const tonic::Float32List& color_matrix) {
@@ -39,7 +39,7 @@
   matrix[9] *= 1.0f / 255;
   matrix[14] *= 1.0f / 255;
   matrix[19] *= 1.0f / 255;
-  filter_ = std::make_shared<DlMatrixColorFilter>(matrix);
+  filter_ = DlMatrixColorFilter::Make(matrix);
 }
 
 void ColorFilter::initLinearToSrgbGamma() {
diff --git a/lib/ui/painting/color_filter.h b/lib/ui/painting/color_filter.h
index f80c48d..b7aa2e2 100644
--- a/lib/ui/painting/color_filter.h
+++ b/lib/ui/painting/color_filter.h
@@ -31,9 +31,6 @@
   ~ColorFilter() override;
 
   const std::shared_ptr<const DlColorFilter> filter() const { return filter_; }
-  const DlColorFilter* dl_filter() const {
-    return (filter_ && filter_->skia_object()) ? filter_.get() : nullptr;
-  }
 
  private:
   std::shared_ptr<const DlColorFilter> filter_;
diff --git a/lib/ui/painting/gradient.cc b/lib/ui/painting/gradient.cc
index 9651982..5f586a3 100644
--- a/lib/ui/painting/gradient.cc
+++ b/lib/ui/painting/gradient.cc
@@ -25,7 +25,7 @@
 void CanvasGradient::initLinear(const tonic::Float32List& end_points,
                                 const tonic::Int32List& colors,
                                 const tonic::Float32List& color_stops,
-                                SkTileMode tile_mode,
+                                DlTileMode tile_mode,
                                 const tonic::Float64List& matrix4) {
   FML_DCHECK(end_points.num_elements() == 4);
   FML_DCHECK(colors.num_elements() == color_stops.num_elements() ||
@@ -48,7 +48,7 @@
 
   dl_shader_ = DlColorSource::MakeLinear(
       p0, p1, colors.num_elements(), colors_array, color_stops.data(),
-      ToDl(tile_mode), has_matrix ? &sk_matrix : nullptr);
+      tile_mode, has_matrix ? &sk_matrix : nullptr);
 }
 
 void CanvasGradient::initRadial(double center_x,
@@ -56,7 +56,7 @@
                                 double radius,
                                 const tonic::Int32List& colors,
                                 const tonic::Float32List& color_stops,
-                                SkTileMode tile_mode,
+                                DlTileMode tile_mode,
                                 const tonic::Float64List& matrix4) {
   FML_DCHECK(colors.num_elements() == color_stops.num_elements() ||
              color_stops.data() == nullptr);
@@ -74,7 +74,7 @@
 
   dl_shader_ = DlColorSource::MakeRadial(
       SkPoint::Make(center_x, center_y), radius, colors.num_elements(),
-      colors_array, color_stops.data(), ToDl(tile_mode),
+      colors_array, color_stops.data(), tile_mode,
       has_matrix ? &sk_matrix : nullptr);
 }
 
@@ -82,7 +82,7 @@
                                double center_y,
                                const tonic::Int32List& colors,
                                const tonic::Float32List& color_stops,
-                               SkTileMode tile_mode,
+                               DlTileMode tile_mode,
                                double start_angle,
                                double end_angle,
                                const tonic::Float64List& matrix4) {
@@ -103,7 +103,7 @@
   dl_shader_ = DlColorSource::MakeSweep(
       SkPoint::Make(center_x, center_y), start_angle * 180.0 / M_PI,
       end_angle * 180.0 / M_PI, colors.num_elements(), colors_array,
-      color_stops.data(), ToDl(tile_mode), has_matrix ? &sk_matrix : nullptr);
+      color_stops.data(), tile_mode, has_matrix ? &sk_matrix : nullptr);
 }
 
 void CanvasGradient::initTwoPointConical(double start_x,
@@ -114,7 +114,7 @@
                                          double end_radius,
                                          const tonic::Int32List& colors,
                                          const tonic::Float32List& color_stops,
-                                         SkTileMode tile_mode,
+                                         DlTileMode tile_mode,
                                          const tonic::Float64List& matrix4) {
   FML_DCHECK(colors.num_elements() == color_stops.num_elements() ||
              color_stops.data() == nullptr);
@@ -134,7 +134,7 @@
       SkPoint::Make(start_x, start_y), start_radius,            //
       SkPoint::Make(end_x, end_y), end_radius,                  //
       colors.num_elements(), colors_array, color_stops.data(),  //
-      ToDl(tile_mode), has_matrix ? &sk_matrix : nullptr);
+      tile_mode, has_matrix ? &sk_matrix : nullptr);
 }
 
 CanvasGradient::CanvasGradient() = default;
diff --git a/lib/ui/painting/gradient.h b/lib/ui/painting/gradient.h
index 5041ab2..d510cef 100644
--- a/lib/ui/painting/gradient.h
+++ b/lib/ui/painting/gradient.h
@@ -12,9 +12,6 @@
 
 namespace flutter {
 
-// TODO: update this if/when Skia adds Decal mode skbug.com/7638
-static_assert(kSkTileModeCount >= 3, "Need to update tile mode enum");
-
 class CanvasGradient : public Shader {
   DEFINE_WRAPPERTYPEINFO();
   FML_FRIEND_MAKE_REF_COUNTED(CanvasGradient);
@@ -26,7 +23,7 @@
   void initLinear(const tonic::Float32List& end_points,
                   const tonic::Int32List& colors,
                   const tonic::Float32List& color_stops,
-                  SkTileMode tile_mode,
+                  DlTileMode tile_mode,
                   const tonic::Float64List& matrix4);
 
   void initRadial(double center_x,
@@ -34,14 +31,14 @@
                   double radius,
                   const tonic::Int32List& colors,
                   const tonic::Float32List& color_stops,
-                  SkTileMode tile_mode,
+                  DlTileMode tile_mode,
                   const tonic::Float64List& matrix4);
 
   void initSweep(double center_x,
                  double center_y,
                  const tonic::Int32List& colors,
                  const tonic::Float32List& color_stops,
-                 SkTileMode tile_mode,
+                 DlTileMode tile_mode,
                  double start_angle,
                  double end_angle,
                  const tonic::Float64List& matrix4);
@@ -54,11 +51,12 @@
                            double end_radius,
                            const tonic::Int32List& colors,
                            const tonic::Float32List& color_stops,
-                           SkTileMode tile_mode,
+                           DlTileMode tile_mode,
                            const tonic::Float64List& matrix4);
 
   std::shared_ptr<DlColorSource> shader(DlImageSampling sampling) override {
-    return dl_shader_->with_sampling(sampling);
+    // Gradient color sources do not have image sampling variants...
+    return dl_shader_;
   }
 
  private:
diff --git a/lib/ui/painting/image_filter.cc b/lib/ui/painting/image_filter.cc
index 665d3b0..d92db00 100644
--- a/lib/ui/painting/image_filter.cc
+++ b/lib/ui/painting/image_filter.cc
@@ -52,45 +52,32 @@
 
 void ImageFilter::initBlur(double sigma_x,
                            double sigma_y,
-                           SkTileMode tile_mode) {
-  filter_ =
-      std::make_shared<DlBlurImageFilter>(sigma_x, sigma_y, ToDl(tile_mode));
+                           DlTileMode tile_mode) {
+  filter_ = DlBlurImageFilter::Make(sigma_x, sigma_y, tile_mode);
 }
 
 void ImageFilter::initDilate(double radius_x, double radius_y) {
-  filter_ = std::make_shared<DlDilateImageFilter>(radius_x, radius_y);
+  filter_ = DlDilateImageFilter::Make(radius_x, radius_y);
 }
 
 void ImageFilter::initErode(double radius_x, double radius_y) {
-  filter_ = std::make_shared<DlErodeImageFilter>(radius_x, radius_y);
+  filter_ = DlErodeImageFilter::Make(radius_x, radius_y);
 }
 
 void ImageFilter::initMatrix(const tonic::Float64List& matrix4,
                              int filterQualityIndex) {
   auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex);
-  filter_ =
-      std::make_shared<DlMatrixImageFilter>(ToSkMatrix(matrix4), sampling);
+  filter_ = DlMatrixImageFilter::Make(ToSkMatrix(matrix4), sampling);
 }
 
 void ImageFilter::initColorFilter(ColorFilter* colorFilter) {
   FML_DCHECK(colorFilter);
-  auto dl_filter = colorFilter->dl_filter();
-  // Skia may return nullptr if the colorfilter is a no-op.
-  if (dl_filter) {
-    filter_ = std::make_shared<DlColorFilterImageFilter>(dl_filter);
-  }
+  filter_ = DlColorFilterImageFilter::Make(colorFilter->filter());
 }
 
 void ImageFilter::initComposeFilter(ImageFilter* outer, ImageFilter* inner) {
   FML_DCHECK(outer && inner);
-  if (!outer->dl_filter()) {
-    filter_ = inner->filter();
-  } else if (!inner->dl_filter()) {
-    filter_ = outer->filter();
-  } else {
-    filter_ = std::make_shared<DlComposeImageFilter>(outer->dl_filter(),
-                                                     inner->dl_filter());
-  }
+  filter_ = DlComposeImageFilter::Make(outer->filter(), inner->filter());
 }
 
 }  // namespace flutter
diff --git a/lib/ui/painting/image_filter.h b/lib/ui/painting/image_filter.h
index 67d9738..66e3012 100644
--- a/lib/ui/painting/image_filter.h
+++ b/lib/ui/painting/image_filter.h
@@ -30,7 +30,7 @@
   static DlImageSampling SamplingFromIndex(int filterQualityIndex);
   static DlFilterMode FilterModeFromIndex(int index);
 
-  void initBlur(double sigma_x, double sigma_y, SkTileMode tile_mode);
+  void initBlur(double sigma_x, double sigma_y, DlTileMode tile_mode);
   void initDilate(double radius_x, double radius_y);
   void initErode(double radius_x, double radius_y);
   void initMatrix(const tonic::Float64List& matrix4, int filter_quality_index);
@@ -38,9 +38,6 @@
   void initComposeFilter(ImageFilter* outer, ImageFilter* inner);
 
   const std::shared_ptr<const DlImageFilter> filter() const { return filter_; }
-  const DlImageFilter* dl_filter() const {
-    return (filter_ && filter_->skia_object()) ? filter_.get() : nullptr;
-  }
 
   static void RegisterNatives(tonic::DartLibraryNatives* natives);
 
diff --git a/lib/ui/painting/image_shader.cc b/lib/ui/painting/image_shader.cc
index bd00fd1..e2cdae7 100644
--- a/lib/ui/painting/image_shader.cc
+++ b/lib/ui/painting/image_shader.cc
@@ -24,8 +24,8 @@
 }
 
 Dart_Handle ImageShader::initWithImage(CanvasImage* image,
-                                       SkTileMode tmx,
-                                       SkTileMode tmy,
+                                       DlTileMode tmx,
+                                       DlTileMode tmy,
                                        int filter_quality_index,
                                        Dart_Handle matrix_handle) {
   if (!image) {
@@ -41,7 +41,7 @@
       sampling_is_locked_ ? ImageFilter::SamplingFromIndex(filter_quality_index)
                           : DlImageSampling::kLinear;
   cached_shader_ = UIDartState::CreateGPUObject(sk_make_sp<DlImageColorSource>(
-      image_, ToDl(tmx), ToDl(tmy), sampling, &local_matrix));
+      image_, tmx, tmy, sampling, &local_matrix));
   return Dart_Null();
 }
 
diff --git a/lib/ui/painting/image_shader.h b/lib/ui/painting/image_shader.h
index d130c5d..7c69d13 100644
--- a/lib/ui/painting/image_shader.h
+++ b/lib/ui/painting/image_shader.h
@@ -25,8 +25,8 @@
   static void Create(Dart_Handle wrapper);
 
   Dart_Handle initWithImage(CanvasImage* image,
-                            SkTileMode tmx,
-                            SkTileMode tmy,
+                            DlTileMode tmx,
+                            DlTileMode tmy,
                             int filter_quality_index,
                             Dart_Handle matrix_handle);
 
diff --git a/lib/ui/painting/paint.cc b/lib/ui/painting/paint.cc
index 2849526..32539c5 100644
--- a/lib/ui/painting/paint.cc
+++ b/lib/ui/painting/paint.cc
@@ -53,148 +53,17 @@
 // default SkPaintDefaults_MiterLimit in Skia (which is not in a public header).
 constexpr double kStrokeMiterLimitDefault = 4.0;
 
-// A color matrix which inverts colors.
-// clang-format off
-constexpr float kInvertColors[20] = {
-  -1.0,    0,    0, 1.0, 0,
-     0, -1.0,    0, 1.0, 0,
-     0,    0, -1.0, 1.0, 0,
-   1.0,  1.0,  1.0, 1.0, 0
-};
-// clang-format on
-
 // Must be kept in sync with the MaskFilter private constants in painting.dart.
 enum MaskFilterType { kNull, kBlur };
 
 Paint::Paint(Dart_Handle paint_objects, Dart_Handle paint_data)
     : paint_objects_(paint_objects), paint_data_(paint_data) {}
 
-const SkPaint* Paint::paint(SkPaint& paint) const {
+const DlPaint* Paint::paint(DlPaint& paint,
+                            const DisplayListAttributeFlags& flags) const {
   if (isNull()) {
     return nullptr;
   }
-  FML_DCHECK(paint == SkPaint());
-
-  tonic::DartByteData byte_data(paint_data_);
-  FML_CHECK(byte_data.length_in_bytes() == kDataByteCount);
-
-  const uint32_t* uint_data = static_cast<const uint32_t*>(byte_data.data());
-  const float* float_data = static_cast<const float*>(byte_data.data());
-
-  Dart_Handle values[kObjectCount];
-  if (!Dart_IsNull(paint_objects_)) {
-    FML_DCHECK(Dart_IsList(paint_objects_));
-    intptr_t length = 0;
-    Dart_ListLength(paint_objects_, &length);
-
-    FML_CHECK(length == kObjectCount);
-    if (Dart_IsError(
-            Dart_ListGetRange(paint_objects_, 0, kObjectCount, values))) {
-      return nullptr;
-    }
-
-    Dart_Handle shader = values[kShaderIndex];
-    if (!Dart_IsNull(shader)) {
-      if (Shader* decoded = tonic::DartConverter<Shader*>::FromDart(shader)) {
-        auto sampling =
-            ImageFilter::SamplingFromIndex(uint_data[kFilterQualityIndex]);
-        auto color_source = decoded->shader(sampling);
-        // TODO(dnfield): Remove this restriction.
-        // This currently is only used by paragraph code. Once SkParagraph does
-        // not need to take an SkPaint, we won't be restricted in this way
-        // because we will not need to access the shader on the UI task runner.
-        if (color_source->owning_context() != DlImage::OwningContext::kRaster) {
-          paint.setShader(color_source->skia_object());
-        }
-      }
-    }
-
-    Dart_Handle color_filter = values[kColorFilterIndex];
-    if (!Dart_IsNull(color_filter)) {
-      ColorFilter* decoded =
-          tonic::DartConverter<ColorFilter*>::FromDart(color_filter);
-      paint.setColorFilter(decoded->filter()->skia_object());
-    }
-
-    Dart_Handle image_filter = values[kImageFilterIndex];
-    if (!Dart_IsNull(image_filter)) {
-      ImageFilter* decoded =
-          tonic::DartConverter<ImageFilter*>::FromDart(image_filter);
-      paint.setImageFilter(decoded->filter()->skia_object());
-    }
-  }
-
-  paint.setAntiAlias(uint_data[kIsAntiAliasIndex] == 0);
-
-  uint32_t encoded_color = uint_data[kColorIndex];
-  if (encoded_color) {
-    SkColor color = encoded_color ^ kColorDefault;
-    paint.setColor(color);
-  }
-
-  uint32_t encoded_blend_mode = uint_data[kBlendModeIndex];
-  if (encoded_blend_mode) {
-    uint32_t blend_mode = encoded_blend_mode ^ kBlendModeDefault;
-    paint.setBlendMode(static_cast<SkBlendMode>(blend_mode));
-  }
-
-  uint32_t style = uint_data[kStyleIndex];
-  if (style) {
-    paint.setStyle(static_cast<SkPaint::Style>(style));
-  }
-
-  float stroke_width = float_data[kStrokeWidthIndex];
-  if (stroke_width != 0.0) {
-    paint.setStrokeWidth(stroke_width);
-  }
-
-  uint32_t stroke_cap = uint_data[kStrokeCapIndex];
-  if (stroke_cap) {
-    paint.setStrokeCap(static_cast<SkPaint::Cap>(stroke_cap));
-  }
-
-  uint32_t stroke_join = uint_data[kStrokeJoinIndex];
-  if (stroke_join) {
-    paint.setStrokeJoin(static_cast<SkPaint::Join>(stroke_join));
-  }
-
-  float stroke_miter_limit = float_data[kStrokeMiterLimitIndex];
-  if (stroke_miter_limit != 0.0) {
-    paint.setStrokeMiter(stroke_miter_limit + kStrokeMiterLimitDefault);
-  }
-
-  if (uint_data[kInvertColorIndex]) {
-    sk_sp<SkColorFilter> invert_filter = SkColorFilters::Matrix(kInvertColors);
-    sk_sp<SkColorFilter> current_filter = paint.refColorFilter();
-    if (current_filter) {
-      invert_filter = invert_filter->makeComposed(current_filter);
-    }
-    paint.setColorFilter(invert_filter);
-  }
-
-  if (uint_data[kDitherIndex]) {
-    paint.setDither(true);
-  }
-
-  switch (uint_data[kMaskFilterIndex]) {
-    case kNull:
-      break;
-    case kBlur:
-      SkBlurStyle blur_style =
-          static_cast<SkBlurStyle>(uint_data[kMaskFilterBlurStyleIndex]);
-      double sigma = float_data[kMaskFilterSigmaIndex];
-      paint.setMaskFilter(SkMaskFilter::MakeBlur(blur_style, sigma));
-      break;
-  }
-
-  return &paint;
-}
-
-bool Paint::sync_to(DisplayListBuilder* builder,
-                    const DisplayListAttributeFlags& flags) const {
-  if (isNull()) {
-    return false;
-  }
   tonic::DartByteData byte_data(paint_data_);
   FML_CHECK(byte_data.length_in_bytes() == kDataByteCount);
 
@@ -204,13 +73,13 @@
   Dart_Handle values[kObjectCount];
   if (Dart_IsNull(paint_objects_)) {
     if (flags.applies_shader()) {
-      builder->setColorSource(nullptr);
+      paint.setColorSource(nullptr);
     }
     if (flags.applies_color_filter()) {
-      builder->setColorFilter(nullptr);
+      paint.setColorFilter(nullptr);
     }
     if (flags.applies_image_filter()) {
-      builder->setImageFilter(nullptr);
+      paint.setImageFilter(nullptr);
     }
   } else {
     FML_DCHECK(Dart_IsList(paint_objects_));
@@ -220,20 +89,20 @@
     FML_CHECK(length == kObjectCount);
     if (Dart_IsError(
             Dart_ListGetRange(paint_objects_, 0, kObjectCount, values))) {
-      return false;
+      return nullptr;
     }
 
     if (flags.applies_shader()) {
       Dart_Handle shader = values[kShaderIndex];
       if (Dart_IsNull(shader)) {
-        builder->setColorSource(nullptr);
+        paint.setColorSource(nullptr);
       } else {
         if (Shader* decoded = tonic::DartConverter<Shader*>::FromDart(shader)) {
           auto sampling =
               ImageFilter::SamplingFromIndex(uint_data[kFilterQualityIndex]);
-          builder->setColorSource(decoded->shader(sampling).get());
+          paint.setColorSource(decoded->shader(sampling));
         } else {
-          builder->setColorSource(nullptr);
+          paint.setColorSource(nullptr);
         }
       }
     }
@@ -241,94 +110,89 @@
     if (flags.applies_color_filter()) {
       Dart_Handle color_filter = values[kColorFilterIndex];
       if (Dart_IsNull(color_filter)) {
-        builder->setColorFilter(nullptr);
+        paint.setColorFilter(nullptr);
       } else {
         ColorFilter* decoded =
             tonic::DartConverter<ColorFilter*>::FromDart(color_filter);
-        builder->setColorFilter(decoded->dl_filter());
+        paint.setColorFilter(decoded->filter());
       }
     }
 
     if (flags.applies_image_filter()) {
       Dart_Handle image_filter = values[kImageFilterIndex];
       if (Dart_IsNull(image_filter)) {
-        builder->setImageFilter(nullptr);
+        paint.setImageFilter(nullptr);
       } else {
         ImageFilter* decoded =
             tonic::DartConverter<ImageFilter*>::FromDart(image_filter);
-        builder->setImageFilter(decoded->dl_filter());
+        paint.setImageFilter(decoded->filter());
       }
     }
   }
 
   if (flags.applies_anti_alias()) {
-    builder->setAntiAlias(uint_data[kIsAntiAliasIndex] == 0);
+    paint.setAntiAlias(uint_data[kIsAntiAliasIndex] == 0);
   }
 
   if (flags.applies_alpha_or_color()) {
     uint32_t encoded_color = uint_data[kColorIndex];
-    builder->setColor(encoded_color ^ kColorDefault);
+    paint.setColor(encoded_color ^ kColorDefault);
   }
 
   if (flags.applies_blend()) {
     uint32_t encoded_blend_mode = uint_data[kBlendModeIndex];
     uint32_t blend_mode = encoded_blend_mode ^ kBlendModeDefault;
-    builder->setBlendMode(static_cast<DlBlendMode>(blend_mode));
+    paint.setBlendMode(static_cast<DlBlendMode>(blend_mode));
   }
 
   if (flags.applies_style()) {
     uint32_t style = uint_data[kStyleIndex];
-    builder->setStyle(static_cast<DlDrawStyle>(style));
+    paint.setDrawStyle(static_cast<DlDrawStyle>(style));
   }
 
-  if (flags.is_stroked(builder->getStyle())) {
+  if (flags.is_stroked(paint.getDrawStyle())) {
     float stroke_width = float_data[kStrokeWidthIndex];
-    builder->setStrokeWidth(stroke_width);
+    paint.setStrokeWidth(stroke_width);
 
     float stroke_miter_limit = float_data[kStrokeMiterLimitIndex];
-    builder->setStrokeMiter(stroke_miter_limit + kStrokeMiterLimitDefault);
+    paint.setStrokeMiter(stroke_miter_limit + kStrokeMiterLimitDefault);
 
     uint32_t stroke_cap = uint_data[kStrokeCapIndex];
-    builder->setStrokeCap(static_cast<DlStrokeCap>(stroke_cap));
+    paint.setStrokeCap(static_cast<DlStrokeCap>(stroke_cap));
 
     uint32_t stroke_join = uint_data[kStrokeJoinIndex];
-    builder->setStrokeJoin(static_cast<DlStrokeJoin>(stroke_join));
+    paint.setStrokeJoin(static_cast<DlStrokeJoin>(stroke_join));
   }
 
   if (flags.applies_color_filter()) {
-    builder->setInvertColors(uint_data[kInvertColorIndex] != 0);
+    paint.setInvertColors(uint_data[kInvertColorIndex] != 0);
   }
 
   if (flags.applies_dither()) {
-    builder->setDither(uint_data[kDitherIndex] != 0);
+    paint.setDither(uint_data[kDitherIndex] != 0);
   }
 
   if (flags.applies_path_effect()) {
     // The paint API exposed to Dart does not support path effects.  But other
     // operations such as text may set a path effect, which must be cleared.
-    builder->setPathEffect(nullptr);
+    paint.setPathEffect(nullptr);
   }
 
   if (flags.applies_mask_filter()) {
     switch (uint_data[kMaskFilterIndex]) {
       case kNull:
-        builder->setMaskFilter(nullptr);
+        paint.setMaskFilter(nullptr);
         break;
       case kBlur:
         SkBlurStyle blur_style =
             static_cast<SkBlurStyle>(uint_data[kMaskFilterBlurStyleIndex]);
         double sigma = float_data[kMaskFilterSigmaIndex];
-        DlBlurMaskFilter dl_filter(blur_style, sigma);
-        if (dl_filter.skia_object()) {
-          builder->setMaskFilter(&dl_filter);
-        } else {
-          builder->setMaskFilter(nullptr);
-        }
+        paint.setMaskFilter(DlBlurMaskFilter::Make(blur_style, sigma));
         break;
     }
   }
 
-  return true;
+  return &paint;
 }
 
 void Paint::toDlPaint(DlPaint& paint) const {
@@ -414,11 +278,14 @@
       SkBlurStyle blur_style =
           static_cast<SkBlurStyle>(uint_data[kMaskFilterBlurStyleIndex]);
       double sigma = float_data[kMaskFilterSigmaIndex];
-      std::shared_ptr<DlBlurMaskFilter> dl_filter =
-          std::make_shared<DlBlurMaskFilter>(blur_style, sigma);
-      if (dl_filter->skia_object()) {
-        paint.setMaskFilter(dl_filter);
-      }
+      // Make could return a nullptr here if the values are NOP or
+      // do not make sense. We could interpret that as if there was
+      // no value passed from Dart at all (i.e. don't change the
+      // setting in the paint object as in the kNull branch right
+      // above here), but the maskfilter flag was actually set
+      // indicating that the developer "tried" to set a mask, so we
+      // should set the null value rather than do nothing.
+      paint.setMaskFilter(DlBlurMaskFilter::Make(blur_style, sigma));
       break;
   }
 }
diff --git a/lib/ui/painting/paint.h b/lib/ui/painting/paint.h
index 6e74535..466f26a 100644
--- a/lib/ui/painting/paint.h
+++ b/lib/ui/painting/paint.h
@@ -17,19 +17,11 @@
   Paint() = default;
   Paint(Dart_Handle paint_objects, Dart_Handle paint_data);
 
-  const SkPaint* paint(SkPaint& paint) const;
+  const DlPaint* paint(DlPaint& paint,
+                       const DisplayListAttributeFlags& flags) const;
 
   void toDlPaint(DlPaint& paint) const;
 
-  /// Synchronize the Dart properties to the display list according
-  /// to the attribute flags that indicate which properties are needed.
-  /// The return value indicates if the paint was non-null and can
-  /// either be DCHECKed or used to indicate to the DisplayList
-  /// draw operation whether or not to use the synchronized attributes
-  /// (mainly the drawImage and saveLayer methods).
-  bool sync_to(DisplayListBuilder* builder,
-               const DisplayListAttributeFlags& flags) const;
-
   bool isNull() const { return Dart_IsNull(paint_data_); }
   bool isNotNull() const { return !Dart_IsNull(paint_data_); }
 
diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc
index d4a43e6..4533587 100644
--- a/shell/common/rasterizer.cc
+++ b/shell/common/rasterizer.cc
@@ -272,9 +272,9 @@
   };
 
   sk_sp<SkSurface> surface = SkSurface::MakeRaster(image_info);
-  SkCanvas* canvas = surface->getCanvas();
-  canvas->clear(SK_ColorTRANSPARENT);
-  display_list->RenderTo(canvas);
+  auto canvas = DlSkCanvasAdapter(surface->getCanvas());
+  canvas.Clear(DlColor::kTransparent());
+  canvas.DrawDisplayList(display_list);
 
   sk_sp<SkImage> image = surface->makeImageSnapshot();
   return std::make_unique<SnapshotDelegate::GpuImageResult>(
@@ -342,9 +342,9 @@
               return;
             }
 
-            SkCanvas* canvas = sk_surface->getCanvas();
-            canvas->clear(SK_ColorTRANSPARENT);
-            display_list->RenderTo(canvas);
+            auto canvas = DlSkCanvasAdapter(sk_surface->getCanvas());
+            canvas.Clear(DlColor::kTransparent());
+            canvas.DrawDisplayList(display_list);
 
             result = std::make_unique<SnapshotDelegate::GpuImageResult>(
                 texture, sk_ref_sp(context), nullptr, "");
diff --git a/shell/common/snapshot_controller_skia.cc b/shell/common/snapshot_controller_skia.cc
index 27b6700..0e2fdea 100644
--- a/shell/common/snapshot_controller_skia.cc
+++ b/shell/common/snapshot_controller_skia.cc
@@ -126,7 +126,7 @@
     sk_sp<DisplayList> display_list,
     SkISize size) {
   return DoMakeRasterSnapshot(size, [display_list](SkCanvas* canvas) {
-    display_list->RenderTo(canvas);
+    DlSkCanvasAdapter(canvas).DrawDisplayList(display_list);
   });
 }
 
diff --git a/shell/platform/darwin/graphics/FlutterDarwinExternalTextureMetal.mm b/shell/platform/darwin/graphics/FlutterDarwinExternalTextureMetal.mm
index 6e1cea4..4730fff 100644
--- a/shell/platform/darwin/graphics/FlutterDarwinExternalTextureMetal.mm
+++ b/shell/platform/darwin/graphics/FlutterDarwinExternalTextureMetal.mm
@@ -61,12 +61,12 @@
   }
 
   if (_externalImage) {
-    context.canvas->DrawImageRect(_externalImage,                          // image
-                                  SkRect::Make(_externalImage->bounds()),  // source rect
-                                  bounds,                                  // destination rect
-                                  sampling,                                // sampling
-                                  context.paint,                           // paint
-                                  false                                    // enforce edges
+    context.canvas->DrawImageRect(_externalImage,                                // image
+                                  SkRect::Make(_externalImage->bounds()),        // source rect
+                                  bounds,                                        // destination rect
+                                  sampling,                                      // sampling
+                                  context.paint,                                 // paint
+                                  flutter::DlCanvas::SrcRectConstraint::kStrict  // enforce edges
     );
   }
 }
diff --git a/testing/display_list_testing.cc b/testing/display_list_testing.cc
index 2caadcb..96a29cf 100644
--- a/testing/display_list_testing.cc
+++ b/testing/display_list_testing.cc
@@ -197,8 +197,15 @@
   switch (op) {
     case DlCanvas::ClipOp::kDifference: return os << "ClipOp::kDifference";
     case DlCanvas::ClipOp::kIntersect:  return os << "ClipOp::kIntersect";
+  }
+}
 
-    default: return os << "ClipOp::????";
+std::ostream& operator<<(std::ostream& os, const DlCanvas::SrcRectConstraint& constraint) {
+  switch (constraint) {
+    case DlCanvas::SrcRectConstraint::kFast:
+      return os << "SrcRectConstraint::kFast";
+    case DlCanvas::SrcRectConstraint::kStrict:
+      return os << "SrcRectConstraint::kStrict";
   }
 }
 
@@ -207,8 +214,6 @@
     case DlStrokeCap::kButt:   return os << "Cap::kButt";
     case DlStrokeCap::kRound:  return os << "Cap::kRound";
     case DlStrokeCap::kSquare: return os << "Cap::kSquare";
-
-    default: return os << "Cap::????";
   }
 }
 
@@ -217,8 +222,6 @@
     case DlStrokeJoin::kMiter: return os << "Join::kMiter";
     case DlStrokeJoin::kRound: return os << "Join::kRound";
     case DlStrokeJoin::kBevel: return os << "Join::kBevel";
-
-    default: return os << "Join::????";
   }
 }
 
@@ -227,8 +230,6 @@
     case DlDrawStyle::kFill:          return os << "Style::kFill";
     case DlDrawStyle::kStroke:        return os << "Style::kStroke";
     case DlDrawStyle::kStrokeAndFill: return os << "Style::kStrokeAnFill";
-
-    default: return os << "Style::????";
   }
 }
 
@@ -238,19 +239,14 @@
     case kSolid_SkBlurStyle:  return os << "BlurStyle::kSolid";
     case kOuter_SkBlurStyle:  return os << "BlurStyle::kOuter";
     case kInner_SkBlurStyle:  return os << "BlurStyle::kInner";
-
-    default: return os << "Style::????";
   }
 }
 
-static std::ostream& operator<<(std::ostream& os,
-                                const DlCanvas::PointMode& mode) {
+std::ostream& operator<<(std::ostream& os, const DlCanvas::PointMode& mode) {
   switch (mode) {
     case DlCanvas::PointMode::kPoints:  return os << "PointMode::kPoints";
     case DlCanvas::PointMode::kLines:   return os << "PointMode::kLines";
     case DlCanvas::PointMode::kPolygon: return os << "PointMode::kPolygon";
-
-    default: return os << "PointMode::????";
   }
 }
 
@@ -448,7 +444,7 @@
       break;
     }
     default:
-      os_ << "DlUnknownColorSource(" << source->skia_object().get() << ")";
+      os_ << "?DlUnknownColorSource?()";
       break;
   }
   os_ << ");" << std::endl;
@@ -490,7 +486,7 @@
       break;
     }
     default:
-      os_ << "DlUnknownColorFilter(" << filter.skia_object().get() << ")";
+      os_ << "?DlUnknownColorFilter?()";
       break;
   }
 }
@@ -530,7 +526,7 @@
       break;
     }
     default:
-      os_ << "DlUnknownMaskFilter(" << filter->skia_object().get() << ")";
+      os_ << "?DlUnknownMaskFilter?()";
       break;
   }
   os_ << ");" << std::endl;
@@ -563,7 +559,7 @@
       os_ << "DlMatrixImageFilter(" << matrix->matrix() << ", " << matrix->sampling() << ")";
       break;
     }
-    case DlImageFilterType::kComposeFilter: {
+    case DlImageFilterType::kCompose: {
       const DlComposeImageFilter* compose = filter.asCompose();
       FML_DCHECK(compose);
       os_ << "DlComposeImageFilter(" << std::endl;
@@ -590,7 +586,7 @@
       os_ << ")";
       break;
     }
-    case DlImageFilterType::kLocalMatrixFilter: {
+    case DlImageFilterType::kLocalMatrix: {
       const DlLocalMatrixImageFilter* local_matrix = filter.asLocalMatrix();
       FML_DCHECK(local_matrix);
       os_ << "DlLocalMatrixImageFilter(" << local_matrix->matrix();
@@ -810,7 +806,7 @@
                                                 const SkRect& dst,
                                                 DlImageSampling sampling,
                                                 bool render_with_attributes,
-                                                SkCanvas::SrcRectConstraint constraint) {
+                                                SrcRectConstraint constraint) {
   startl() << "drawImageRect(" << image.get() << "," << std::endl;
   startl() << "              src: " << src << "," << std::endl;
   startl() << "              dst: " << dst << "," << std::endl;
@@ -849,8 +845,12 @@
            << ");" << std::endl;
 }
 void DisplayListStreamDispatcher::drawDisplayList(
-    const sk_sp<DisplayList> display_list) {
-  startl() << "drawDisplayList(ID: " << display_list->unique_id() << ", bounds: " << display_list->bounds() << ");" << std::endl;
+    const sk_sp<DisplayList> display_list, SkScalar opacity) {
+  startl() << "drawDisplayList("
+           << "ID: " << display_list->unique_id() << ", "
+           << "bounds: " << display_list->bounds() << ", "
+           << "opacity: " << opacity
+           << ");" << std::endl;
 }
 void DisplayListStreamDispatcher::drawTextBlob(const sk_sp<SkTextBlob> blob,
                                                SkScalar x,
diff --git a/testing/display_list_testing.h b/testing/display_list_testing.h
index 26d5af0..60afff2 100644
--- a/testing/display_list_testing.h
+++ b/testing/display_list_testing.h
@@ -8,8 +8,8 @@
 #include <ostream>
 
 #include "flutter/display_list/display_list.h"
-#include "flutter/display_list/display_list_dispatcher.h"
 #include "flutter/display_list/display_list_path_effect.h"
+#include "flutter/display_list/dl_op_receiver.h"
 
 namespace flutter {
 namespace testing {
@@ -36,6 +36,10 @@
 extern std::ostream& operator<<(std::ostream& os, const DlPaint& paint);
 extern std::ostream& operator<<(std::ostream& os, const DlBlendMode& mode);
 extern std::ostream& operator<<(std::ostream& os, const DlCanvas::ClipOp& op);
+extern std::ostream& operator<<(std::ostream& os,
+                                const DlCanvas::PointMode& op);
+extern std::ostream& operator<<(std::ostream& os,
+                                const DlCanvas::SrcRectConstraint& op);
 extern std::ostream& operator<<(std::ostream& os, const DlStrokeCap& cap);
 extern std::ostream& operator<<(std::ostream& os, const DlStrokeJoin& join);
 extern std::ostream& operator<<(std::ostream& os, const DlDrawStyle& style);
@@ -47,7 +51,7 @@
 extern std::ostream& operator<<(std::ostream& os, const DlTileMode& mode);
 extern std::ostream& operator<<(std::ostream& os, const DlImage* image);
 
-class DisplayListStreamDispatcher final : public Dispatcher {
+class DisplayListStreamDispatcher final : public DlOpReceiver {
  public:
   DisplayListStreamDispatcher(std::ostream& os,
                               int cur_indent = 2,
@@ -121,7 +125,7 @@
                      const SkRect& dst,
                      DlImageSampling sampling,
                      bool render_with_attributes,
-                     SkCanvas::SrcRectConstraint constraint) override;
+                     SrcRectConstraint constraint) override;
   void drawImageNine(const sk_sp<DlImage> image,
                      const SkIRect& center,
                      const SkRect& dst,
@@ -136,7 +140,8 @@
                  DlImageSampling sampling,
                  const SkRect* cull_rect,
                  bool render_with_attributes) override;
-  void drawDisplayList(const sk_sp<DisplayList> display_list) override;
+  void drawDisplayList(const sk_sp<DisplayList> display_list,
+                       SkScalar opacity) override;
   void drawTextBlob(const sk_sp<SkTextBlob> blob,
                     SkScalar x,
                     SkScalar y) override;
diff --git a/testing/mock_canvas.cc b/testing/mock_canvas.cc
index 837d336..e72a27c 100644
--- a/testing/mock_canvas.cc
+++ b/testing/mock_canvas.cc
@@ -285,7 +285,7 @@
                                const SkRect&,
                                const DlImageSampling,
                                const DlPaint*,
-                               bool enforce_src_edges) {
+                               SrcRectConstraint constraint) {
   FML_DCHECK(false);
 }
 
diff --git a/testing/mock_canvas.h b/testing/mock_canvas.h
index 5473432..022793d 100644
--- a/testing/mock_canvas.h
+++ b/testing/mock_canvas.h
@@ -247,12 +247,13 @@
                  const SkPoint point,
                  DlImageSampling sampling,
                  const DlPaint* paint = nullptr) override;
-  void DrawImageRect(const sk_sp<DlImage>& image,
-                     const SkRect& src,
-                     const SkRect& dst,
-                     DlImageSampling sampling,
-                     const DlPaint* paint = nullptr,
-                     bool enforce_src_edges = false) override;
+  void DrawImageRect(
+      const sk_sp<DlImage>& image,
+      const SkRect& src,
+      const SkRect& dst,
+      DlImageSampling sampling,
+      const DlPaint* paint = nullptr,
+      SrcRectConstraint constraint = SrcRectConstraint::kFast) override;
   void DrawImageNine(const sk_sp<DlImage>& image,
                      const SkIRect& center,
                      const SkRect& dst,