[sksg] Consolidate geometry effects

Dash, trim, round, transform and upcoming offset have a lot in common.

Introduce a GeometryEffect base class to consolidate.

TBR=
Change-Id: Ib5556e6ebe416685c624d53ba8591e118aa4f0d6
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/300496
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/src/layers/shapelayer/FillStroke.cpp b/modules/skottie/src/layers/shapelayer/FillStroke.cpp
index a635698..1193219 100644
--- a/modules/skottie/src/layers/shapelayer/FillStroke.cpp
+++ b/modules/skottie/src/layers/shapelayer/FillStroke.cpp
@@ -10,7 +10,7 @@
 #include "modules/skottie/src/SkottiePriv.h"
 #include "modules/skottie/src/SkottieValue.h"
 #include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
-#include "modules/sksg/include/SkSGDashEffect.h"
+#include "modules/sksg/include/SkSGGeometryEffect.h"
 #include "modules/sksg/include/SkSGPaint.h"
 
 namespace skottie {
diff --git a/modules/skottie/src/layers/shapelayer/RoundCorners.cpp b/modules/skottie/src/layers/shapelayer/RoundCorners.cpp
index 7baa6d6..cf5f1b6 100644
--- a/modules/skottie/src/layers/shapelayer/RoundCorners.cpp
+++ b/modules/skottie/src/layers/shapelayer/RoundCorners.cpp
@@ -10,7 +10,7 @@
 #include "modules/skottie/src/SkottiePriv.h"
 #include "modules/skottie/src/SkottieValue.h"
 #include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
-#include "modules/sksg/include/SkSGRoundEffect.h"
+#include "modules/sksg/include/SkSGGeometryEffect.h"
 
 namespace skottie {
 namespace internal {
diff --git a/modules/skottie/src/layers/shapelayer/ShapeLayer.cpp b/modules/skottie/src/layers/shapelayer/ShapeLayer.cpp
index 3ab9394..e9993a1 100644
--- a/modules/skottie/src/layers/shapelayer/ShapeLayer.cpp
+++ b/modules/skottie/src/layers/shapelayer/ShapeLayer.cpp
@@ -12,16 +12,14 @@
 #include "modules/skottie/src/SkottiePriv.h"
 #include "modules/skottie/src/SkottieValue.h"
 #include "modules/sksg/include/SkSGDraw.h"
-#include "modules/sksg/include/SkSGGeometryTransform.h"
+#include "modules/sksg/include/SkSGGeometryEffect.h"
 #include "modules/sksg/include/SkSGGroup.h"
 #include "modules/sksg/include/SkSGMerge.h"
 #include "modules/sksg/include/SkSGPaint.h"
 #include "modules/sksg/include/SkSGPath.h"
 #include "modules/sksg/include/SkSGRect.h"
 #include "modules/sksg/include/SkSGRenderEffect.h"
-#include "modules/sksg/include/SkSGRoundEffect.h"
 #include "modules/sksg/include/SkSGTransform.h"
-#include "modules/sksg/include/SkSGTrimEffect.h"
 #include "src/utils/SkJSON.h"
 
 #include <algorithm>
diff --git a/modules/skottie/src/layers/shapelayer/TrimPaths.cpp b/modules/skottie/src/layers/shapelayer/TrimPaths.cpp
index 7db26cc..bc2dc15 100644
--- a/modules/skottie/src/layers/shapelayer/TrimPaths.cpp
+++ b/modules/skottie/src/layers/shapelayer/TrimPaths.cpp
@@ -10,8 +10,8 @@
 #include "modules/skottie/src/SkottiePriv.h"
 #include "modules/skottie/src/SkottieValue.h"
 #include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
+#include "modules/sksg/include/SkSGGeometryEffect.h"
 #include "modules/sksg/include/SkSGMerge.h"
-#include "modules/sksg/include/SkSGTrimEffect.h"
 
 #include <vector>
 
diff --git a/modules/sksg/include/SkSGDashEffect.h b/modules/sksg/include/SkSGDashEffect.h
deleted file mode 100644
index 341e71e..0000000
--- a/modules/sksg/include/SkSGDashEffect.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2020 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkSGDashEffect_DEFINED
-#define SkSGDashEffect_DEFINED
-
-#include "include/core/SkPath.h"
-#include "modules/sksg/include/SkSGGeometryNode.h"
-
-#include <vector>
-
-namespace  sksg {
-
-/**
- * Apply a dash effect to the child geometry.
- *
- * Follows the same semantics as SkDashPathEffect, with one minor tweak: when the number of
- * intervals is odd, they are repeated once more to attain an even sequence (same as SVG
- * stroke-dasharray: https://www.w3.org/TR/SVG11/painting.html#StrokeDasharrayProperty).
- */
-class DashEffect final : public GeometryNode {
-public:
-    static sk_sp<DashEffect> Make(sk_sp<GeometryNode> child) {
-        return child ? sk_sp<DashEffect>(new DashEffect(std::move(child))) : nullptr;
-    }
-
-    ~DashEffect() override;
-
-    SG_ATTRIBUTE(Intervals, std::vector<float>, fIntervals)
-    SG_ATTRIBUTE(Phase,                 float , fPhase    )
-
-protected:
-    void onClip(SkCanvas*, bool antiAlias) const override;
-    void onDraw(SkCanvas*, const SkPaint&) const override;
-    bool onContains(const SkPoint&)        const override;
-
-    SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
-    SkPath onAsPath() const override;
-
-private:
-    explicit DashEffect(sk_sp<GeometryNode>);
-
-    const sk_sp<GeometryNode> fChild;
-
-    SkPath fDashedPath; // cache
-
-    std::vector<float> fIntervals;
-    float              fPhase;
-};
-
-} // namespace sksg
-
-#endif // SkSGDashEffect_DEFINED
diff --git a/modules/sksg/include/SkSGGeometryEffect.h b/modules/sksg/include/SkSGGeometryEffect.h
new file mode 100644
index 0000000..01b501c
--- /dev/null
+++ b/modules/sksg/include/SkSGGeometryEffect.h
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkSGGeometryEffect_DEFINED
+#define SkSGGeometryEffect_DEFINED
+
+#include "modules/sksg/include/SkSGGeometryNode.h"
+
+#include "include/core/SkPath.h"
+#include "include/effects/SkTrimPathEffect.h"
+#include "modules/sksg/include/SkSGTransform.h"
+
+namespace sksg {
+
+/**
+ * Base class for geometry effects.
+ */
+class GeometryEffect : public GeometryNode {
+protected:
+    explicit GeometryEffect(sk_sp<GeometryNode>);
+    ~GeometryEffect() override;
+
+    void onClip(SkCanvas*, bool antiAlias) const final;
+    void onDraw(SkCanvas*, const SkPaint&) const final;
+    bool onContains(const SkPoint&)        const final;
+
+    SkRect onRevalidate(InvalidationController*, const SkMatrix&) final;
+    SkPath onAsPath() const final;
+
+    virtual SkPath onRevalidateEffect(const sk_sp<GeometryNode>&) = 0;
+
+private:
+    const sk_sp<GeometryNode> fChild;
+    SkPath                    fPath; // transformed child cache.
+
+    using INHERITED = GeometryNode;
+};
+
+/**
+ * Apply a trim effect to the child geometry.
+ */
+class TrimEffect final : public GeometryEffect {
+public:
+    static sk_sp<TrimEffect> Make(sk_sp<GeometryNode> child) {
+        return child ? sk_sp<TrimEffect>(new TrimEffect(std::move(child))) : nullptr;
+    }
+
+    SG_ATTRIBUTE(Start , SkScalar              , fStart )
+    SG_ATTRIBUTE(Stop  , SkScalar              , fStop  )
+    SG_ATTRIBUTE(Mode  , SkTrimPathEffect::Mode, fMode  )
+
+private:
+    explicit TrimEffect(sk_sp<GeometryNode> child) : INHERITED(std::move(child)) {}
+
+    SkPath onRevalidateEffect(const sk_sp<GeometryNode>&) override;
+
+    SkScalar               fStart = 0,
+                           fStop  = 1;
+    SkTrimPathEffect::Mode fMode  = SkTrimPathEffect::Mode::kNormal;
+
+    using INHERITED = GeometryEffect;
+};
+
+/**
+ * Apply a transform to a GeometryNode.
+ */
+class GeometryTransform final : public GeometryEffect {
+public:
+    static sk_sp<GeometryTransform> Make(sk_sp<GeometryNode> child, sk_sp<Transform> transform) {
+        return child && transform
+            ? sk_sp<GeometryTransform>(new GeometryTransform(std::move(child),
+                                                             std::move(transform)))
+            : nullptr;
+    }
+
+    ~GeometryTransform() override;
+
+    const sk_sp<Transform>& getTransform() const { return fTransform; }
+
+private:
+    GeometryTransform(sk_sp<GeometryNode>, sk_sp<Transform>);
+
+    SkPath onRevalidateEffect(const sk_sp<GeometryNode>&) override;
+
+    const sk_sp<Transform> fTransform;
+
+    using INHERITED = GeometryEffect;
+};
+
+/**
+ * Apply a dash effect to the child geometry.
+ *
+ * Follows the same semantics as SkDashPathEffect, with one minor tweak: when the number of
+ * intervals is odd, they are repeated once more to attain an even sequence (same as SVG
+ * stroke-dasharray: https://www.w3.org/TR/SVG11/painting.html#StrokeDasharrayProperty).
+ */
+class DashEffect final : public GeometryEffect {
+public:
+    static sk_sp<DashEffect> Make(sk_sp<GeometryNode> child) {
+        return child ? sk_sp<DashEffect>(new DashEffect(std::move(child))) : nullptr;
+    }
+
+    SG_ATTRIBUTE(Intervals, std::vector<float>, fIntervals)
+    SG_ATTRIBUTE(Phase,                 float , fPhase    )
+
+private:
+    explicit DashEffect(sk_sp<GeometryNode> child) : INHERITED(std::move(child)) {}
+
+    SkPath onRevalidateEffect(const sk_sp<GeometryNode>&) override;
+
+    std::vector<float> fIntervals;
+    float              fPhase;
+
+    using INHERITED = GeometryEffect;
+};
+
+/**
+ * Apply a rounded-corner effect to the child geometry.
+ */
+class RoundEffect final : public GeometryEffect {
+public:
+    static sk_sp<RoundEffect> Make(sk_sp<GeometryNode> child) {
+        return child ? sk_sp<RoundEffect>(new RoundEffect(std::move(child))) : nullptr;
+    }
+
+    SG_ATTRIBUTE(Radius, SkScalar, fRadius)
+
+private:
+    explicit RoundEffect(sk_sp<GeometryNode> child) : INHERITED(std::move(child)) {}
+
+    SkPath onRevalidateEffect(const sk_sp<GeometryNode>&) override;
+
+    SkScalar fRadius = 0;
+
+    using INHERITED = GeometryEffect;
+};
+
+} // namespace sksg
+
+#endif // SkSGGeometryEffect_DEFINED
diff --git a/modules/sksg/include/SkSGGeometryTransform.h b/modules/sksg/include/SkSGGeometryTransform.h
deleted file mode 100644
index 400ab8a..0000000
--- a/modules/sksg/include/SkSGGeometryTransform.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkSGGeometryTransform_DEFINED
-#define SkSGGeometryTransform_DEFINED
-
-#include "modules/sksg/include/SkSGGeometryNode.h"
-
-#include "include/core/SkPath.h"
-
-class SkMatrix;
-
-namespace sksg {
-
-class Transform;
-
-/**
- * Concrete Effect node, binding a Matrix to a GeometryNode.
- */
-class GeometryTransform final : public GeometryNode {
-public:
-    static sk_sp<GeometryTransform> Make(sk_sp<GeometryNode> child, sk_sp<Transform> transform) {
-        return child && transform
-            ? sk_sp<GeometryTransform>(new GeometryTransform(std::move(child),
-                                                             std::move(transform)))
-            : nullptr;
-    }
-
-    ~GeometryTransform() override;
-
-    const sk_sp<Transform>& getTransform() const { return fTransform; }
-
-protected:
-    void onClip(SkCanvas*, bool antiAlias) const override;
-    void onDraw(SkCanvas*, const SkPaint&) const override;
-    bool onContains(const SkPoint&)        const override;
-
-    SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
-    SkPath onAsPath() const override;
-
-private:
-    GeometryTransform(sk_sp<GeometryNode>, sk_sp<Transform>);
-
-    const sk_sp<GeometryNode> fChild;
-    const sk_sp<Transform>    fTransform;
-    SkPath                    fTransformedPath;
-
-    using INHERITED = GeometryNode;
-};
-
-}
-
-#endif // SkSGGeometryTransform_DEFINED
diff --git a/modules/sksg/include/SkSGRoundEffect.h b/modules/sksg/include/SkSGRoundEffect.h
deleted file mode 100644
index 20a02d0..0000000
--- a/modules/sksg/include/SkSGRoundEffect.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkSGRoundEffect_DEFINED
-#define SkSGRoundEffect_DEFINED
-
-#include "modules/sksg/include/SkSGGeometryNode.h"
-
-#include "include/core/SkPath.h"
-
-namespace sksg {
-
-/**
- * Concrete Geometry node, applying a rounded-corner effect to its child.
- */
-class RoundEffect final : public GeometryNode {
-public:
-    static sk_sp<RoundEffect> Make(sk_sp<GeometryNode> child) {
-        return child ? sk_sp<RoundEffect>(new RoundEffect(std::move(child))) : nullptr;
-    }
-
-    ~RoundEffect() override;
-
-    SG_ATTRIBUTE(Radius, SkScalar, fRadius)
-
-protected:
-    void onClip(SkCanvas*, bool antiAlias) const override;
-    void onDraw(SkCanvas*, const SkPaint&) const override;
-    bool onContains(const SkPoint&)        const override;
-
-    SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
-    SkPath onAsPath() const override;
-
-private:
-    explicit RoundEffect(sk_sp<GeometryNode>);
-
-    const sk_sp<GeometryNode> fChild;
-
-    SkPath                    fRoundedPath;
-    SkScalar                  fRadius = 0;
-
-    using INHERITED = GeometryNode;
-};
-
-} // namespace sksg
-
-#endif // SkSGRoundEffect_DEFINED
diff --git a/modules/sksg/include/SkSGTrimEffect.h b/modules/sksg/include/SkSGTrimEffect.h
deleted file mode 100644
index 804f028..0000000
--- a/modules/sksg/include/SkSGTrimEffect.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#ifndef SkSGTrimEffect_DEFINED
-#define SkSGTrimEffect_DEFINED
-
-#include "modules/sksg/include/SkSGGeometryNode.h"
-
-#include "include/core/SkPath.h"
-#include "include/effects/SkTrimPathEffect.h"
-
-class SkCanvas;
-class SkPaint;
-
-namespace sksg {
-
-/**
- * Concrete Geometry node, applying a trim effect to its child.
- */
-class TrimEffect final : public GeometryNode {
-public:
-    static sk_sp<TrimEffect> Make(sk_sp<GeometryNode> child) {
-        return child ? sk_sp<TrimEffect>(new TrimEffect(std::move(child))) : nullptr;
-    }
-
-    ~TrimEffect() override;
-
-    SG_ATTRIBUTE(Start , SkScalar              , fStart )
-    SG_ATTRIBUTE(Stop  , SkScalar              , fStop  )
-    SG_ATTRIBUTE(Mode  , SkTrimPathEffect::Mode, fMode  )
-
-protected:
-    void onClip(SkCanvas*, bool antiAlias) const override;
-    void onDraw(SkCanvas*, const SkPaint&) const override;
-    bool onContains(const SkPoint&)        const override;
-
-    SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
-    SkPath onAsPath() const override;
-
-private:
-    explicit TrimEffect(sk_sp<GeometryNode>);
-
-    const sk_sp<GeometryNode> fChild;
-
-    SkPath                    fTrimmedPath;
-    SkScalar                  fStart = 0,
-                              fStop  = 1;
-    SkTrimPathEffect::Mode    fMode  = SkTrimPathEffect::Mode::kNormal;
-
-    using INHERITED = GeometryNode;
-};
-
-} // namespace sksg
-
-#endif // SkSGTrimEffect_DEFINED
diff --git a/modules/sksg/sksg.gni b/modules/sksg/sksg.gni
index 739941b1..5e55e9a 100644
--- a/modules/sksg/sksg.gni
+++ b/modules/sksg/sksg.gni
@@ -9,11 +9,10 @@
 skia_sksg_sources = [
   "$_src/SkSGClipEffect.cpp",
   "$_src/SkSGColorFilter.cpp",
-  "$_src/SkSGDashEffect.cpp",
   "$_src/SkSGDraw.cpp",
   "$_src/SkSGEffectNode.cpp",
+  "$_src/SkSGGeometryEffect.cpp",
   "$_src/SkSGGeometryNode.cpp",
-  "$_src/SkSGGeometryTransform.cpp",
   "$_src/SkSGGradient.cpp",
   "$_src/SkSGGroup.cpp",
   "$_src/SkSGImage.cpp",
@@ -29,10 +28,8 @@
   "$_src/SkSGRect.cpp",
   "$_src/SkSGRenderEffect.cpp",
   "$_src/SkSGRenderNode.cpp",
-  "$_src/SkSGRoundEffect.cpp",
   "$_src/SkSGScene.cpp",
   "$_src/SkSGText.cpp",
   "$_src/SkSGTransform.cpp",
   "$_src/SkSGTransformPriv.h",
-  "$_src/SkSGTrimEffect.cpp",
 ]
diff --git a/modules/sksg/src/SkSGDashEffect.cpp b/modules/sksg/src/SkSGDashEffect.cpp
deleted file mode 100644
index 2b4002c..0000000
--- a/modules/sksg/src/SkSGDashEffect.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2020 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "modules/sksg/include/SkSGDashEffect.h"
-
-#include "include/core/SkCanvas.h"
-#include "include/core/SkStrokeRec.h"
-#include "include/effects/SkDashPathEffect.h"
-
-#include <algorithm>
-
-namespace sksg {
-
-namespace  {
-
-sk_sp<SkPathEffect> make_dash(const std::vector<float> intervals, float phase) {
-    if (intervals.empty()) {
-        return nullptr;
-    }
-
-    const auto* intervals_ptr   = intervals.data();
-    auto        intervals_count = intervals.size();
-
-    SkSTArray<32, float, true> storage;
-    if (intervals_count & 1) {
-        intervals_count *= 2;
-        storage.resize(intervals_count);
-        intervals_ptr = storage.data();
-
-        std::copy(intervals.begin(), intervals.end(), storage.begin());
-        std::copy(intervals.begin(), intervals.end(), storage.begin() + intervals.size());
-    }
-
-    return SkDashPathEffect::Make(intervals_ptr, SkToInt(intervals_count), phase);
-}
-
-} // namespace
-
-DashEffect::DashEffect(sk_sp<GeometryNode> child)
-    : fChild(std::move(child)) {
-    this->observeInval(fChild);
-}
-
-DashEffect::~DashEffect() {
-    this->unobserveInval(fChild);
-}
-
-void DashEffect::onClip(SkCanvas* canvas, bool antiAlias) const {
-    canvas->clipPath(fDashedPath, SkClipOp::kIntersect, antiAlias);
-}
-
-void DashEffect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
-    canvas->drawPath(fDashedPath, paint);
-}
-
-bool DashEffect::onContains(const SkPoint& p) const {
-    return fDashedPath.contains(p.x(), p.y());
-}
-
-SkPath DashEffect::onAsPath() const {
-    return fDashedPath;
-}
-
-SkRect DashEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
-    SkASSERT(this->hasInval());
-
-    const auto child_bounds = fChild->revalidate(ic, ctm);
-    const auto child_path   = fChild->asPath();
-
-    fDashedPath.reset();
-
-    auto dash_patheffect = make_dash(fIntervals, fPhase);
-    SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle);
-
-    if (!dash_patheffect ||
-        !dash_patheffect->filterPath(&fDashedPath, child_path, &rec, &child_bounds)) {
-        fDashedPath = std::move(child_path);
-    }
-    fDashedPath.shrinkToFit();
-
-    return fDashedPath.computeTightBounds();
-}
-
-} // namespace sksg
diff --git a/modules/sksg/src/SkSGGeometryEffect.cpp b/modules/sksg/src/SkSGGeometryEffect.cpp
new file mode 100644
index 0000000..935c917
--- /dev/null
+++ b/modules/sksg/src/SkSGGeometryEffect.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "modules/sksg/include/SkSGGeometryEffect.h"
+
+#include "include/core/SkCanvas.h"
+#include "include/core/SkStrokeRec.h"
+#include "include/effects/SkCornerPathEffect.h"
+#include "include/effects/SkDashPathEffect.h"
+#include "include/effects/SkTrimPathEffect.h"
+#include "modules/sksg/src/SkSGTransformPriv.h"
+
+namespace sksg {
+
+GeometryEffect::GeometryEffect(sk_sp<GeometryNode> child)
+    : fChild(std::move(child)) {
+    SkASSERT(fChild);
+
+    this->observeInval(fChild);
+}
+
+GeometryEffect::~GeometryEffect() {
+    this->unobserveInval(fChild);
+}
+
+void GeometryEffect::onClip(SkCanvas* canvas, bool antiAlias) const {
+    canvas->clipPath(fPath, SkClipOp::kIntersect, antiAlias);
+}
+
+void GeometryEffect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
+    canvas->drawPath(fPath, paint);
+}
+
+bool GeometryEffect::onContains(const SkPoint& p) const {
+    return fPath.contains(p.x(), p.y());
+}
+
+SkPath GeometryEffect::onAsPath() const {
+    return fPath;
+}
+
+SkRect GeometryEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+    SkASSERT(this->hasInval());
+
+    fChild->revalidate(ic, ctm);
+
+    fPath = this->onRevalidateEffect(fChild);
+    fPath.shrinkToFit();
+
+    return fPath.computeTightBounds();
+}
+
+SkPath TrimEffect::onRevalidateEffect(const sk_sp<GeometryNode>& child) {
+    SkPath path = child->asPath();
+
+    if (const auto trim = SkTrimPathEffect::Make(fStart, fStop, fMode)) {
+        SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle);
+        SkAssertResult(trim->filterPath(&path, path, &rec, nullptr));
+    }
+
+    return path;
+}
+
+GeometryTransform::GeometryTransform(sk_sp<GeometryNode> child, sk_sp<Transform> transform)
+    : INHERITED(std::move(child))
+    , fTransform(std::move(transform)) {
+    SkASSERT(fTransform);
+    this->observeInval(fTransform);
+}
+
+GeometryTransform::~GeometryTransform() {
+    this->unobserveInval(fTransform);
+}
+
+SkPath GeometryTransform::onRevalidateEffect(const sk_sp<GeometryNode>& child) {
+    fTransform->revalidate(nullptr, SkMatrix::I());
+    const auto m = TransformPriv::As<SkMatrix>(fTransform);
+
+    SkPath path = child->asPath();
+    path.transform(m);
+
+    return path;
+}
+
+namespace  {
+
+sk_sp<SkPathEffect> make_dash(const std::vector<float> intervals, float phase) {
+    if (intervals.empty()) {
+        return nullptr;
+    }
+
+    const auto* intervals_ptr   = intervals.data();
+    auto        intervals_count = intervals.size();
+
+    SkSTArray<32, float, true> storage;
+    if (intervals_count & 1) {
+        intervals_count *= 2;
+        storage.resize(intervals_count);
+        intervals_ptr = storage.data();
+
+        std::copy(intervals.begin(), intervals.end(), storage.begin());
+        std::copy(intervals.begin(), intervals.end(), storage.begin() + intervals.size());
+    }
+
+    return SkDashPathEffect::Make(intervals_ptr, SkToInt(intervals_count), phase);
+}
+
+} // namespace
+
+SkPath DashEffect::onRevalidateEffect(const sk_sp<GeometryNode>& child) {
+    SkPath path = child->asPath();
+
+    if (const auto dash_patheffect = make_dash(fIntervals, fPhase)) {
+        SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle);
+        dash_patheffect->filterPath(&path, path, &rec, nullptr);
+    }
+
+    return path;
+}
+
+SkPath RoundEffect::onRevalidateEffect(const sk_sp<GeometryNode>& child) {
+    SkPath path = child->asPath();
+
+    if (const auto round = SkCornerPathEffect::Make(fRadius)) {
+        SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle);
+        SkAssertResult(round->filterPath(&path, path, &rec, nullptr));
+    }
+
+    return path;
+}
+
+} // namesapce sksg
diff --git a/modules/sksg/src/SkSGGeometryTransform.cpp b/modules/sksg/src/SkSGGeometryTransform.cpp
deleted file mode 100644
index d0ff83ea..0000000
--- a/modules/sksg/src/SkSGGeometryTransform.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "modules/sksg/include/SkSGGeometryTransform.h"
-
-#include "include/core/SkCanvas.h"
-#include "modules/sksg/include/SkSGTransform.h"
-#include "modules/sksg/src/SkSGTransformPriv.h"
-
-namespace sksg {
-
-GeometryTransform::GeometryTransform(sk_sp<GeometryNode> child, sk_sp<Transform> transform)
-    : fChild(std::move(child))
-    , fTransform(std::move(transform)) {
-    this->observeInval(fChild);
-    this->observeInval(fTransform);
-}
-
-GeometryTransform::~GeometryTransform() {
-    this->unobserveInval(fChild);
-    this->unobserveInval(fTransform);
-}
-
-void GeometryTransform::onClip(SkCanvas* canvas, bool antiAlias) const {
-    canvas->clipPath(fTransformedPath, SkClipOp::kIntersect, antiAlias);
-}
-
-void GeometryTransform::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
-    canvas->drawPath(fTransformedPath, paint);
-}
-
-bool GeometryTransform::onContains(const SkPoint& p) const {
-    return fTransformedPath.contains(p.x(), p.y());
-}
-
-SkRect GeometryTransform::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
-    SkASSERT(this->hasInval());
-
-    // We don't care about matrix reval results.
-    fTransform->revalidate(ic, ctm);
-    const auto m = TransformPriv::As<SkMatrix>(fTransform);
-
-    auto bounds = fChild->revalidate(ic, ctm);
-    fTransformedPath = fChild->asPath();
-    fTransformedPath.transform(m);
-    fTransformedPath.shrinkToFit();
-
-    m.mapRect(&bounds);
-    return  bounds;
-}
-
-SkPath GeometryTransform::onAsPath() const {
-    return fTransformedPath;
-}
-
-} // namespace sksg
diff --git a/modules/sksg/src/SkSGRoundEffect.cpp b/modules/sksg/src/SkSGRoundEffect.cpp
deleted file mode 100644
index ba5db05..0000000
--- a/modules/sksg/src/SkSGRoundEffect.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2018 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "modules/sksg/include/SkSGRoundEffect.h"
-
-#include "include/core/SkCanvas.h"
-#include "include/core/SkStrokeRec.h"
-#include "include/effects/SkCornerPathEffect.h"
-
-namespace sksg {
-
-RoundEffect::RoundEffect(sk_sp<GeometryNode> child)
-    : fChild(std::move(child)) {
-    this->observeInval(fChild);
-}
-
-RoundEffect::~RoundEffect() {
-    this->unobserveInval(fChild);
-}
-
-void RoundEffect::onClip(SkCanvas* canvas, bool antiAlias) const {
-    canvas->clipPath(fRoundedPath, SkClipOp::kIntersect, antiAlias);
-}
-
-void RoundEffect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
-    SkASSERT(!paint.getPathEffect());
-
-    canvas->drawPath(fRoundedPath, paint);
-}
-
-bool RoundEffect::onContains(const SkPoint& p) const {
-    return fRoundedPath.contains(p.x(), p.y());
-}
-
-SkPath RoundEffect::onAsPath() const {
-    return fRoundedPath;
-}
-
-SkRect RoundEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
-    SkASSERT(this->hasInval());
-
-    const auto childbounds = fChild->revalidate(ic, ctm);
-    const auto path        = fChild->asPath();
-
-    if (auto round = SkCornerPathEffect::Make(fRadius)) {
-        fRoundedPath.reset();
-        SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle);
-        SkAssertResult(round->filterPath(&fRoundedPath, path, &rec, &childbounds));
-    } else {
-        fRoundedPath = path;
-    }
-
-    fRoundedPath.shrinkToFit();
-
-    return fRoundedPath.computeTightBounds();
-}
-
-} // namespace sksg
diff --git a/modules/sksg/src/SkSGTrimEffect.cpp b/modules/sksg/src/SkSGTrimEffect.cpp
deleted file mode 100644
index 4c63b0c..0000000
--- a/modules/sksg/src/SkSGTrimEffect.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2017 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "modules/sksg/include/SkSGTrimEffect.h"
-
-#include "include/core/SkCanvas.h"
-#include "include/core/SkStrokeRec.h"
-#include "include/effects/SkTrimPathEffect.h"
-
-namespace sksg {
-
-TrimEffect::TrimEffect(sk_sp<GeometryNode> child)
-    : fChild(std::move(child)) {
-    this->observeInval(fChild);
-}
-
-TrimEffect::~TrimEffect() {
-    this->unobserveInval(fChild);
-}
-
-void TrimEffect::onClip(SkCanvas* canvas, bool antiAlias) const {
-    canvas->clipPath(fTrimmedPath, SkClipOp::kIntersect, antiAlias);
-}
-
-void TrimEffect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
-    SkASSERT(!paint.getPathEffect());
-
-    canvas->drawPath(fTrimmedPath, paint);
-}
-
-bool TrimEffect::onContains(const SkPoint& p) const {
-    return fTrimmedPath.contains(p.x(), p.y());
-}
-
-SkPath TrimEffect::onAsPath() const {
-    return fTrimmedPath;
-}
-
-SkRect TrimEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
-    SkASSERT(this->hasInval());
-
-    const auto childbounds = fChild->revalidate(ic, ctm);
-    const auto path        = fChild->asPath();
-
-    if (auto trim = SkTrimPathEffect::Make(fStart, fStop, fMode)) {
-        fTrimmedPath.reset();
-        SkStrokeRec rec(SkStrokeRec::kHairline_InitStyle);
-        SkAssertResult(trim->filterPath(&fTrimmedPath, path, &rec, &childbounds));
-    } else {
-        fTrimmedPath = path;
-    }
-
-    fTrimmedPath.shrinkToFit();
-
-    return fTrimmedPath.computeTightBounds();
-}
-
-} // namespace sksg