aboutsummaryrefslogtreecommitdiffhomepage
path: root/modules
diff options
context:
space:
mode:
authorGravatar Florin Malita <fmalita@chromium.org>2018-05-25 12:43:51 -0400
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2018-05-25 17:11:52 +0000
commit3b526b05d652ad6c310d9c636187b20b51c7648c (patch)
tree6c6fa99f2e80db81e9c3f593fe5883aabdaa442b /modules
parent59da548b0c4d4239e0ec1855d3f7f77a2bff4b93 (diff)
"Modularize" SkSG
* relocate all SkSG-related files under modules/sksg/ * fix various tidbits to make non-sksg builds possible * drop obsolete SampleSGInval.cpp Change-Id: I54e6c5bb1a09f45030fa8d607b3eb3f7cba78957 Reviewed-on: https://skia-review.googlesource.com/130025 Commit-Queue: Florin Malita <fmalita@chromium.org> Reviewed-by: Mike Klein <mtklein@google.com>
Diffstat (limited to 'modules')
-rw-r--r--modules/sksg/BUILD.gn90
-rw-r--r--modules/sksg/include/SkSGClipEffect.h50
-rw-r--r--modules/sksg/include/SkSGColor.h37
-rw-r--r--modules/sksg/include/SkSGDraw.h48
-rw-r--r--modules/sksg/include/SkSGEffectNode.h38
-rw-r--r--modules/sksg/include/SkSGGeometryNode.h49
-rw-r--r--modules/sksg/include/SkSGGeometryTransform.h58
-rw-r--r--modules/sksg/include/SkSGGradient.h105
-rw-r--r--modules/sksg/include/SkSGGroup.h47
-rw-r--r--modules/sksg/include/SkSGImage.h49
-rw-r--r--modules/sksg/include/SkSGInvalidationController.h43
-rw-r--r--modules/sksg/include/SkSGMaskEffect.h51
-rw-r--r--modules/sksg/include/SkSGMerge.h64
-rw-r--r--modules/sksg/include/SkSGNode.h107
-rw-r--r--modules/sksg/include/SkSGOpacityEffect.h42
-rw-r--r--modules/sksg/include/SkSGPaintNode.h60
-rw-r--r--modules/sksg/include/SkSGPath.h48
-rw-r--r--modules/sksg/include/SkSGPlane.h40
-rw-r--r--modules/sksg/include/SkSGRect.h76
-rw-r--r--modules/sksg/include/SkSGRenderNode.h36
-rw-r--r--modules/sksg/include/SkSGRoundEffect.h50
-rw-r--r--modules/sksg/include/SkSGScene.h85
-rw-r--r--modules/sksg/include/SkSGText.h69
-rw-r--r--modules/sksg/include/SkSGTransform.h81
-rw-r--r--modules/sksg/include/SkSGTrimEffect.h58
-rw-r--r--modules/sksg/samples/SampleSVGPong.cpp299
-rw-r--r--modules/sksg/src/SkSGClipEffect.cpp50
-rw-r--r--modules/sksg/src/SkSGColor.cpp18
-rw-r--r--modules/sksg/src/SkSGDraw.cpp50
-rw-r--r--modules/sksg/src/SkSGEffectNode.cpp31
-rw-r--r--modules/sksg/src/SkSGGeometryNode.cpp32
-rw-r--r--modules/sksg/src/SkSGGeometryTransform.cpp53
-rw-r--r--modules/sksg/src/SkSGGradient.cpp60
-rw-r--r--modules/sksg/src/SkSGGroup.cpp66
-rw-r--r--modules/sksg/src/SkSGImage.cpp29
-rw-r--r--modules/sksg/src/SkSGInvalidationController.cpp32
-rw-r--r--modules/sksg/src/SkSGMaskEffect.cpp54
-rw-r--r--modules/sksg/src/SkSGMerge.cpp84
-rw-r--r--modules/sksg/src/SkSGNode.cpp152
-rw-r--r--modules/sksg/src/SkSGOpacityEffect.cpp42
-rw-r--r--modules/sksg/src/SkSGPaintNode.cpp41
-rw-r--r--modules/sksg/src/SkSGPath.cpp41
-rw-r--r--modules/sksg/src/SkSGPlane.cpp36
-rw-r--r--modules/sksg/src/SkSGRect.cpp60
-rw-r--r--modules/sksg/src/SkSGRenderNode.cpp19
-rw-r--r--modules/sksg/src/SkSGRoundEffect.cpp56
-rw-r--r--modules/sksg/src/SkSGScene.cpp70
-rw-r--r--modules/sksg/src/SkSGText.cpp77
-rw-r--r--modules/sksg/src/SkSGTransform.cpp70
-rw-r--r--modules/sksg/src/SkSGTrimEffect.cpp56
-rw-r--r--modules/sksg/tests/SGTest.cpp197
51 files changed, 3256 insertions, 0 deletions
diff --git a/modules/sksg/BUILD.gn b/modules/sksg/BUILD.gn
new file mode 100644
index 0000000000..2809cf1876
--- /dev/null
+++ b/modules/sksg/BUILD.gn
@@ -0,0 +1,90 @@
+# Copyright 2018 Google Inc.
+#
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+declare_args() {
+ skia_enable_sksg = true
+}
+
+config("public_config") {
+ if (skia_enable_sksg) {
+ defines = [ "SK_HAS_SKSG" ]
+ include_dirs = [ "include" ]
+ }
+}
+
+source_set("sksg") {
+ if (skia_enable_sksg) {
+ public_configs = [ ":public_config" ]
+ sources = [
+ "src/SkSGClipEffect.cpp",
+ "src/SkSGColor.cpp",
+ "src/SkSGDraw.cpp",
+ "src/SkSGEffectNode.cpp",
+ "src/SkSGGeometryNode.cpp",
+ "src/SkSGGeometryTransform.cpp",
+ "src/SkSGGradient.cpp",
+ "src/SkSGGroup.cpp",
+ "src/SkSGImage.cpp",
+ "src/SkSGInvalidationController.cpp",
+ "src/SkSGMaskEffect.cpp",
+ "src/SkSGMerge.cpp",
+ "src/SkSGNode.cpp",
+ "src/SkSGOpacityEffect.cpp",
+ "src/SkSGPaintNode.cpp",
+ "src/SkSGPath.cpp",
+ "src/SkSGPlane.cpp",
+ "src/SkSGRect.cpp",
+ "src/SkSGRenderNode.cpp",
+ "src/SkSGRoundEffect.cpp",
+ "src/SkSGScene.cpp",
+ "src/SkSGText.cpp",
+ "src/SkSGTransform.cpp",
+ "src/SkSGTrimEffect.cpp",
+ ]
+ configs += [ "../../:skia_private" ]
+ deps = [
+ "../..:skia",
+ ]
+ }
+}
+
+source_set("tests") {
+ if (skia_enable_sksg) {
+ testonly = true
+
+ configs += [
+ "../..:skia_private",
+ "../..:tests_config", # TODO: refactor to make this nicer
+ ]
+ sources = [
+ "tests/SGTest.cpp",
+ ]
+ deps = [
+ ":sksg",
+ "../..:gpu_tool_utils", # TODO: refactor to make this nicer
+ "../..:skia",
+ ]
+ }
+}
+
+source_set("samples") {
+ if (skia_enable_sksg) {
+ testonly = true
+
+ configs += [
+ "../..:skia_private",
+ "../..:samples_config", # TODO: refactor to make this nicer
+ ]
+ sources = [
+ "samples/SampleSVGPong.cpp",
+ ]
+ deps = [
+ ":sksg",
+ "../..:gm", # TODO: refactor to make this nicer
+ "../..:skia",
+ "../..:views", # TODO: refactor to make this nicer
+ ]
+ }
+}
diff --git a/modules/sksg/include/SkSGClipEffect.h b/modules/sksg/include/SkSGClipEffect.h
new file mode 100644
index 0000000000..674edb2b5c
--- /dev/null
+++ b/modules/sksg/include/SkSGClipEffect.h
@@ -0,0 +1,50 @@
+/*
+ * 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 SkSGClipEffect_DEFINED
+#define SkSGClipEffect_DEFINED
+
+#include "SkSGEffectNode.h"
+
+namespace sksg {
+
+class GeometryNode;
+
+/**
+ * Concrete Effect node, applying a clip to its descendants.
+ *
+ */
+class ClipEffect final : public EffectNode {
+public:
+ static sk_sp<ClipEffect> Make(sk_sp<RenderNode> child, sk_sp<GeometryNode> clip,
+ bool aa = false) {
+ return (child && clip)
+ ? sk_sp<ClipEffect>(new ClipEffect(std::move(child), std::move(clip), aa))
+ : nullptr;
+ }
+
+ ~ClipEffect() override;
+
+protected:
+ ClipEffect(sk_sp<RenderNode>, sk_sp<GeometryNode>, bool aa);
+
+ void onRender(SkCanvas*) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ const sk_sp<GeometryNode> fClipNode;
+ const bool fAntiAlias;
+
+ bool fNoop = false;
+
+ typedef EffectNode INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGClipEffect_DEFINED
diff --git a/modules/sksg/include/SkSGColor.h b/modules/sksg/include/SkSGColor.h
new file mode 100644
index 0000000000..a19921cfd3
--- /dev/null
+++ b/modules/sksg/include/SkSGColor.h
@@ -0,0 +1,37 @@
+/*
+ * 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 SkSGColor_DEFINED
+#define SkSGColor_DEFINED
+
+#include "SkSGPaintNode.h"
+
+#include "SkColor.h"
+
+namespace sksg {
+
+/**
+ * Concrete Paint node, wrapping an SkColor.
+ */
+class Color : public PaintNode {
+public:
+ static sk_sp<Color> Make(SkColor c) { return sk_sp<Color>(new Color(c)); }
+
+ SG_ATTRIBUTE(Color, SkColor, fColor)
+
+protected:
+ void onApplyToPaint(SkPaint*) const override;
+
+private:
+ explicit Color(SkColor);
+
+ SkColor fColor;
+};
+
+} // namespace sksg
+
+#endif // SkSGColor_DEFINED
diff --git a/modules/sksg/include/SkSGDraw.h b/modules/sksg/include/SkSGDraw.h
new file mode 100644
index 0000000000..20ead3d5f6
--- /dev/null
+++ b/modules/sksg/include/SkSGDraw.h
@@ -0,0 +1,48 @@
+/*
+ * 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 SkSGDraw_DEFINED
+#define SkSGDraw_DEFINED
+
+#include "SkSGRenderNode.h"
+
+namespace sksg {
+
+class GeometryNode;
+class PaintNode;
+
+/**
+ * Concrete rendering node.
+ *
+ * Wraps and draws a [geometry, paint] tuple.
+ *
+ * Think Skia SkCanvas::drawFoo(foo, paint) calls.
+ */
+class Draw : public RenderNode {
+public:
+ static sk_sp<Draw> Make(sk_sp<GeometryNode> geo, sk_sp<PaintNode> paint) {
+ return (geo && paint) ? sk_sp<Draw>(new Draw(std::move(geo), std::move(paint))) : nullptr;
+ }
+
+protected:
+ Draw(sk_sp<GeometryNode>, sk_sp<PaintNode> paint);
+ ~Draw() override;
+
+ void onRender(SkCanvas*) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ sk_sp<GeometryNode> fGeometry;
+ sk_sp<PaintNode> fPaint;
+
+ typedef RenderNode INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGDraw_DEFINED
diff --git a/modules/sksg/include/SkSGEffectNode.h b/modules/sksg/include/SkSGEffectNode.h
new file mode 100644
index 0000000000..ab0968e96c
--- /dev/null
+++ b/modules/sksg/include/SkSGEffectNode.h
@@ -0,0 +1,38 @@
+/*
+ * 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 SkSGEffectNode_DEFINED
+#define SkSGEffectNode_DEFINED
+
+#include "SkSGRenderNode.h"
+
+namespace sksg {
+
+/**
+ * Base class for nodes which apply some transformation when rendering
+ * their descendants.
+ *
+ * This includes transforms, clipping, filters, etc.
+ */
+class EffectNode : public RenderNode {
+protected:
+ explicit EffectNode(sk_sp<RenderNode>);
+ ~EffectNode() override;
+
+ void onRender(SkCanvas*) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ sk_sp<RenderNode> fChild;
+
+ typedef RenderNode INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGEffectNode_DEFINED
diff --git a/modules/sksg/include/SkSGGeometryNode.h b/modules/sksg/include/SkSGGeometryNode.h
new file mode 100644
index 0000000000..7ce3aa9b79
--- /dev/null
+++ b/modules/sksg/include/SkSGGeometryNode.h
@@ -0,0 +1,49 @@
+/*
+ * 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 SkSGGeometryNode_DEFINED
+#define SkSGGeometryNode_DEFINED
+
+#include "SkSGNode.h"
+
+class SkCanvas;
+class SkPaint;
+class SkPath;
+
+namespace sksg {
+
+/**
+ * Base class for nodes which provide 'geometry' (as opposed to paint)
+ * for drawing.
+ *
+ * Think SkRect, SkPath, etc.
+ */
+class GeometryNode : public Node {
+public:
+ void clip(SkCanvas*, bool antiAlias) const;
+ void draw(SkCanvas*, const SkPaint&) const;
+
+ SkPath asPath() const;
+
+protected:
+ GeometryNode();
+
+ virtual void onClip(SkCanvas*, bool antiAlias) const = 0;
+
+ virtual void onDraw(SkCanvas*, const SkPaint&) const = 0;
+
+ virtual SkPath onAsPath() const = 0;
+
+private:
+ friend class Draw; // wants to know the cached bounds.
+
+ typedef Node INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGGeometryNode_DEFINED
diff --git a/modules/sksg/include/SkSGGeometryTransform.h b/modules/sksg/include/SkSGGeometryTransform.h
new file mode 100644
index 0000000000..fe7e026031
--- /dev/null
+++ b/modules/sksg/include/SkSGGeometryTransform.h
@@ -0,0 +1,58 @@
+/*
+ * 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 "SkSGGeometryNode.h"
+
+#include "SkPath.h"
+#include "SkSGTransform.h"
+
+class SkMatrix;
+
+namespace sksg {
+
+/**
+ * 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<Matrix> matrix) {
+ return child && matrix
+ ? sk_sp<GeometryTransform>(new GeometryTransform(std::move(child), std::move(matrix)))
+ : nullptr;
+ }
+
+ static sk_sp<GeometryTransform> Make(sk_sp<GeometryNode> child, const SkMatrix& m) {
+ return Make(std::move(child), Matrix::Make(m));
+ }
+
+ ~GeometryTransform() override;
+
+ const sk_sp<Matrix>& getMatrix() const { return fMatrix; }
+
+protected:
+ void onClip(SkCanvas*, bool antiAlias) const override;
+ void onDraw(SkCanvas*, const SkPaint&) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+ SkPath onAsPath() const override;
+
+private:
+ GeometryTransform(sk_sp<GeometryNode>, sk_sp<Matrix>);
+
+ const sk_sp<GeometryNode> fChild;
+ const sk_sp<Matrix> fMatrix;
+ SkPath fTransformed;
+
+ using INHERITED = GeometryNode;
+};
+
+}
+
+#endif // SkSGGeometryTransform_DEFINED
diff --git a/modules/sksg/include/SkSGGradient.h b/modules/sksg/include/SkSGGradient.h
new file mode 100644
index 0000000000..d69cb1495c
--- /dev/null
+++ b/modules/sksg/include/SkSGGradient.h
@@ -0,0 +1,105 @@
+/*
+ * 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 SkSGGradient_DEFINED
+#define SkSGGradient_DEFINED
+
+#include "SkSGPaintNode.h"
+
+#include "SkColor.h"
+#include "SkPoint.h"
+#include "SkScalar.h"
+#include "SkShader.h"
+
+#include <vector>
+
+namespace sksg {
+
+/**
+ * Gradient base class.
+ */
+class Gradient : public PaintNode {
+public:
+ struct ColorStop {
+ SkScalar fPosition;
+ SkColor fColor;
+
+ bool operator==(const ColorStop& other) const {
+ return fPosition == other.fPosition && fColor == other.fColor;
+ }
+ };
+
+ SG_ATTRIBUTE(ColorStops, std::vector<ColorStop>, fColorStops)
+ SG_ATTRIBUTE(TileMode , SkShader::TileMode , fTileMode )
+
+protected:
+ void onApplyToPaint(SkPaint*) const final;
+
+ virtual sk_sp<SkShader> onMakeShader(const std::vector<SkColor>& colors,
+ const std::vector<SkScalar>& positions) const = 0;
+
+protected:
+ Gradient() = default;
+
+private:
+ std::vector<ColorStop> fColorStops;
+ SkShader::TileMode fTileMode = SkShader::kClamp_TileMode;
+
+ using INHERITED = PaintNode;
+};
+
+class LinearGradient final : public Gradient {
+public:
+ static sk_sp<LinearGradient> Make() {
+ return sk_sp<LinearGradient>(new LinearGradient());
+ }
+
+ SG_ATTRIBUTE(StartPoint, SkPoint, fStartPoint)
+ SG_ATTRIBUTE(EndPoint , SkPoint, fEndPoint )
+
+protected:
+ sk_sp<SkShader> onMakeShader(const std::vector<SkColor>& colors,
+ const std::vector<SkScalar>& positions) const override;
+
+private:
+ LinearGradient() = default;
+
+ SkPoint fStartPoint = SkPoint::Make(0, 0),
+ fEndPoint = SkPoint::Make(0, 0);
+
+ using INHERITED = Gradient;
+};
+
+class RadialGradient final : public Gradient {
+public:
+ static sk_sp<RadialGradient> Make() {
+ return sk_sp<RadialGradient>(new RadialGradient());
+ }
+
+ SG_ATTRIBUTE(StartCenter, SkPoint , fStartCenter)
+ SG_ATTRIBUTE(EndCenter , SkPoint , fEndCenter )
+ SG_ATTRIBUTE(StartRadius, SkScalar, fStartRadius)
+ SG_ATTRIBUTE(EndRadius , SkScalar, fEndRadius )
+
+protected:
+ sk_sp<SkShader> onMakeShader(const std::vector<SkColor>& colors,
+ const std::vector<SkScalar>& positions) const override;
+
+private:
+ RadialGradient() = default;
+
+ SkPoint fStartCenter = SkPoint::Make(0, 0),
+ fEndCenter = SkPoint::Make(0, 0);
+ SkScalar fStartRadius = 0,
+ fEndRadius = 0;
+
+ using INHERITED = Gradient;
+};
+
+} // namespace sksg
+
+#endif // SkSGGradient_DEFINED
diff --git a/modules/sksg/include/SkSGGroup.h b/modules/sksg/include/SkSGGroup.h
new file mode 100644
index 0000000000..482f10db8c
--- /dev/null
+++ b/modules/sksg/include/SkSGGroup.h
@@ -0,0 +1,47 @@
+/*
+ * 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 SkSGGroup_DEFINED
+#define SkSGGroup_DEFINED
+
+#include "SkSGRenderNode.h"
+
+#include "SkTArray.h"
+
+namespace sksg {
+
+/**
+ * Concrete node, grouping together multiple descendants.
+ */
+class Group : public RenderNode {
+public:
+ static sk_sp<Group> Make() {
+ return sk_sp<Group>(new Group());
+ }
+
+ void addChild(sk_sp<RenderNode>);
+ void removeChild(const sk_sp<RenderNode>&);
+
+ size_t size() const { return SkTo<size_t>(fChildren.count()); }
+ bool empty() const { return fChildren.empty(); }
+
+protected:
+ Group();
+ ~Group() override;
+
+ void onRender(SkCanvas*) const override;
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ SkTArray<sk_sp<RenderNode>, true> fChildren;
+
+ typedef RenderNode INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGGroup_DEFINED
diff --git a/modules/sksg/include/SkSGImage.h b/modules/sksg/include/SkSGImage.h
new file mode 100644
index 0000000000..7d17a50aaa
--- /dev/null
+++ b/modules/sksg/include/SkSGImage.h
@@ -0,0 +1,49 @@
+/*
+ * 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 SkSGImage_DEFINED
+#define SkSGImage_DEFINED
+
+#include "SkSGRenderNode.h"
+
+#include "SkFilterQuality.h"
+
+class SkImage;
+
+namespace sksg {
+
+/**
+ * Concrete rendering node, wrapping an SkImage.
+ *
+ */
+class Image final : public RenderNode {
+public:
+ static sk_sp<Image> Make(sk_sp<SkImage> image) {
+ return image ? sk_sp<Image>(new Image(std::move(image))) : nullptr;
+ }
+
+ SG_ATTRIBUTE(Quality , SkFilterQuality, fQuality )
+ SG_ATTRIBUTE(AntiAlias, bool , fAntiAlias)
+
+protected:
+ explicit Image(sk_sp<SkImage>);
+
+ void onRender(SkCanvas*) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ const sk_sp<SkImage> fImage;
+ SkFilterQuality fQuality = kLow_SkFilterQuality;
+ bool fAntiAlias = true;
+
+ typedef RenderNode INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGImage_DEFINED
diff --git a/modules/sksg/include/SkSGInvalidationController.h b/modules/sksg/include/SkSGInvalidationController.h
new file mode 100644
index 0000000000..df3857c1fe
--- /dev/null
+++ b/modules/sksg/include/SkSGInvalidationController.h
@@ -0,0 +1,43 @@
+/*
+ * 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 SkSGInvalidationController_DEFINED
+#define SkSGInvalidationController_DEFINED
+
+#include "SkMatrix.h"
+#include "SkTDArray.h"
+#include "SkTypes.h"
+
+struct SkRect;
+
+namespace sksg {
+
+/**
+ * Receiver for invalidation events.
+ *
+ * Tracks dirty regions for repaint.
+ */
+class InvalidationController : public SkNoncopyable {
+public:
+ InvalidationController();
+
+ void inval(const SkRect&, const SkMatrix& ctm = SkMatrix::I());
+
+ const SkRect& bounds() const { return fBounds; }
+ const SkRect* begin() const { return fRects.begin(); }
+ const SkRect* end() const { return fRects.end(); }
+
+private:
+ SkTDArray<SkRect> fRects;
+ SkRect fBounds;
+
+ typedef SkNoncopyable INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGInvalidationController_DEFINED
diff --git a/modules/sksg/include/SkSGMaskEffect.h b/modules/sksg/include/SkSGMaskEffect.h
new file mode 100644
index 0000000000..c4fd0120e5
--- /dev/null
+++ b/modules/sksg/include/SkSGMaskEffect.h
@@ -0,0 +1,51 @@
+/*
+ * 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 SkSGMaskEffect_DEFINED
+#define SkSGMaskEffect_DEFINED
+
+#include "SkSGEffectNode.h"
+
+namespace sksg {
+
+/**
+ * Concrete Effect node, applying a mask to its descendants.
+ *
+ */
+class MaskEffect final : public EffectNode {
+public:
+ enum class Mode {
+ kNormal,
+ kInvert
+ };
+
+ static sk_sp<MaskEffect> Make(sk_sp<RenderNode> child, sk_sp<RenderNode> mask,
+ Mode mode = Mode::kNormal) {
+ return (child && mask)
+ ? sk_sp<MaskEffect>(new MaskEffect(std::move(child), std::move(mask), mode))
+ : nullptr;
+ }
+
+ ~MaskEffect() override;
+
+protected:
+ MaskEffect(sk_sp<RenderNode>, sk_sp<RenderNode> mask, Mode);
+
+ void onRender(SkCanvas*) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ const sk_sp<RenderNode> fMaskNode;
+ const Mode fMaskMode;
+
+ typedef EffectNode INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGMaskEffect_DEFINED
diff --git a/modules/sksg/include/SkSGMerge.h b/modules/sksg/include/SkSGMerge.h
new file mode 100644
index 0000000000..54924d6475
--- /dev/null
+++ b/modules/sksg/include/SkSGMerge.h
@@ -0,0 +1,64 @@
+/*
+ * 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 SkSGMerge_DEFINED
+#define SkSGMerge_DEFINED
+
+#include "SkSGGeometryNode.h"
+
+#include "SkPath.h"
+
+#include <vector>
+
+class SkCanvas;
+class SkPaint;
+
+namespace sksg {
+
+/**
+ * Concrete Geometry node, combining other geometries based on Mode.
+ */
+class Merge final : public GeometryNode {
+public:
+ enum class Mode {
+ // Append path mode.
+ kMerge,
+
+ // SkPathOp ops.
+ kUnion,
+ kIntersect,
+ kDifference,
+ kReverseDifference,
+ kXOR,
+ };
+
+ static sk_sp<Merge> Make(std::vector<sk_sp<GeometryNode>>&& geos, Mode mode) {
+ return sk_sp<Merge>(new Merge(std::move(geos), mode));
+ }
+
+ ~Merge() override;
+
+protected:
+ void onClip(SkCanvas*, bool antiAlias) const override;
+ void onDraw(SkCanvas*, const SkPaint&) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+ SkPath onAsPath() const override;
+
+private:
+ Merge(std::vector<sk_sp<GeometryNode>>&& geos, Mode);
+
+ std::vector<sk_sp<GeometryNode>> fGeos;
+ SkPath fMerged;
+ Mode fMode;
+
+ using INHERITED = GeometryNode;
+};
+
+} // namespace sksg
+
+#endif // SkSGMerge_DEFINED
diff --git a/modules/sksg/include/SkSGNode.h b/modules/sksg/include/SkSGNode.h
new file mode 100644
index 0000000000..17619de485
--- /dev/null
+++ b/modules/sksg/include/SkSGNode.h
@@ -0,0 +1,107 @@
+/*
+ * 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 SkSGNode_DEFINED
+#define SkSGNode_DEFINED
+
+#include "SkRect.h"
+#include "SkRefCnt.h"
+#include "SkTDArray.h"
+
+class SkCanvas;
+class SkMatrix;
+
+namespace sksg {
+
+class InvalidationController;
+
+/**
+ * Base class for all scene graph nodes.
+ *
+ * Handles ingress edge management for the DAG (i.e. node -> "parent" node mapping),
+ * and invalidation.
+ *
+ * Note: egress edges are only implemented/supported in container subclasses
+ * (e.g. Group, Effect, Draw).
+ */
+class Node : public SkRefCnt {
+public:
+ // Traverse the DAG and revalidate any dependant/invalidated nodes.
+ // Returns the bounding box for the DAG fragment.
+ const SkRect& revalidate(InvalidationController*, const SkMatrix&);
+
+protected:
+ enum InvalTraits {
+ // Nodes with this trait never generate direct damage -- instead,
+ // the damage bubbles up to ancestors.
+ kBubbleDamage_Trait = 1 << 0,
+ };
+
+ explicit Node(uint32_t invalTraits);
+ ~Node() override;
+
+ const SkRect& bounds() const {
+ SkASSERT(!this->hasInval());
+ return fBounds;
+ }
+
+ // Tag this node for invalidation and optional damage.
+ void invalidate(bool damage = true);
+ bool hasInval() const { return fFlags & kInvalidated_Flag; }
+
+ // Dispatched on revalidation. Subclasses are expected to recompute/cache their properties
+ // and return their bounding box in local coordinates.
+ virtual SkRect onRevalidate(InvalidationController*, const SkMatrix& ctm) = 0;
+
+ // Register/unregister |this| to receive invalidation events from a descendant.
+ void observeInval(const sk_sp<Node>&);
+ void unobserveInval(const sk_sp<Node>&);
+
+private:
+ enum Flags {
+ kInvalidated_Flag = 1 << 0, // the node or its descendants require revalidation
+ kDamage_Flag = 1 << 1, // the node contributes damage during revalidation
+ kObserverArray_Flag = 1 << 2, // the node has more than one inval observer
+ kInTraversal_Flag = 1 << 3, // the node is part of a traversal (cycle detection)
+ };
+
+ template <typename Func>
+ void forEachInvalObserver(Func&&) const;
+
+ class ScopedFlag;
+
+ union {
+ Node* fInvalObserver;
+ SkTDArray<Node*>* fInvalObserverArray;
+ };
+ SkRect fBounds;
+ const uint32_t fInvalTraits : 16;
+ uint32_t fFlags : 16;
+
+ typedef SkRefCnt INHERITED;
+};
+
+// Helper for defining attribute getters/setters in subclasses.
+#define SG_ATTRIBUTE(attr_name, attr_type, attr_container) \
+ const attr_type& get##attr_name() const { return attr_container; } \
+ void set##attr_name(const attr_type& v) { \
+ if (attr_container == v) return; \
+ attr_container = v; \
+ this->invalidate(); \
+ }
+
+#define SG_MAPPED_ATTRIBUTE(attr_name, attr_type, attr_container) \
+ attr_type get##attr_name() const { return attr_container.get##attr_name(); } \
+ void set##attr_name(const attr_type& v) { \
+ if (attr_container.get##attr_name() == v) return; \
+ attr_container.set##attr_name(v); \
+ this->invalidate(); \
+ }
+
+} // namespace sksg
+
+#endif // SkSGNode_DEFINED
diff --git a/modules/sksg/include/SkSGOpacityEffect.h b/modules/sksg/include/SkSGOpacityEffect.h
new file mode 100644
index 0000000000..d906775b44
--- /dev/null
+++ b/modules/sksg/include/SkSGOpacityEffect.h
@@ -0,0 +1,42 @@
+/*
+ * 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 SkSGOpacityEffect_DEFINED
+#define SkSGOpacityEffect_DEFINED
+
+#include "SkSGEffectNode.h"
+
+namespace sksg {
+
+/**
+ * Concrete Effect node, applying opacity to its descendants.
+ *
+ */
+class OpacityEffect final : public EffectNode {
+public:
+ static sk_sp<OpacityEffect> Make(sk_sp<RenderNode> child, float opacity = 1) {
+ return child ? sk_sp<OpacityEffect>(new OpacityEffect(std::move(child), opacity)) : nullptr;
+ }
+
+ SG_ATTRIBUTE(Opacity, float, fOpacity)
+
+protected:
+ OpacityEffect(sk_sp<RenderNode>, float);
+
+ void onRender(SkCanvas*) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ float fOpacity;
+
+ typedef EffectNode INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGOpacityEffect_DEFINED
diff --git a/modules/sksg/include/SkSGPaintNode.h b/modules/sksg/include/SkSGPaintNode.h
new file mode 100644
index 0000000000..5c9563b3a6
--- /dev/null
+++ b/modules/sksg/include/SkSGPaintNode.h
@@ -0,0 +1,60 @@
+/*
+ * 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 SkSGPaintNode_DEFINED
+#define SkSGPaintNode_DEFINED
+
+#include "SkSGNode.h"
+
+#include "SkPaint.h"
+
+namespace sksg {
+
+/**
+ * Base class for nodes which provide a 'paint' (as opposed to geometry) for
+ * drawing (e.g. colors, gradients, patterns).
+ *
+ * Roughly equivalent to Skia's SkPaint.
+ */
+class PaintNode : public Node {
+public:
+ const SkPaint& makePaint();
+
+ SG_ATTRIBUTE(AntiAlias , bool , fAntiAlias )
+ SG_ATTRIBUTE(Opacity , SkScalar , fOpacity )
+ SG_ATTRIBUTE(BlendMode , SkBlendMode , fBlendMode )
+ SG_ATTRIBUTE(StrokeWidth, SkScalar , fStrokeWidth)
+ SG_ATTRIBUTE(StrokeMiter, SkScalar , fStrokeMiter)
+ SG_ATTRIBUTE(Style , SkPaint::Style, fStyle )
+ SG_ATTRIBUTE(StrokeJoin , SkPaint::Join , fStrokeJoin )
+ SG_ATTRIBUTE(StrokeCap , SkPaint::Cap , fStrokeCap )
+
+protected:
+ PaintNode();
+
+ virtual void onApplyToPaint(SkPaint*) const = 0;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) final;
+
+private:
+ SkPaint fPaint;
+
+ SkScalar fOpacity = 1,
+ fStrokeWidth = 1,
+ fStrokeMiter = 4;
+ bool fAntiAlias = false;
+ SkBlendMode fBlendMode = SkBlendMode::kSrcOver;
+ SkPaint::Style fStyle = SkPaint::kFill_Style;
+ SkPaint::Join fStrokeJoin = SkPaint::kMiter_Join;
+ SkPaint::Cap fStrokeCap = SkPaint::kButt_Cap;
+
+ typedef Node INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGGeometryNode_DEFINED
diff --git a/modules/sksg/include/SkSGPath.h b/modules/sksg/include/SkSGPath.h
new file mode 100644
index 0000000000..1a8718868d
--- /dev/null
+++ b/modules/sksg/include/SkSGPath.h
@@ -0,0 +1,48 @@
+/*
+ * 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 SkSGPath_DEFINED
+#define SkSGPath_DEFINED
+
+#include "SkSGGeometryNode.h"
+
+#include "SkPath.h"
+
+class SkCanvas;
+class SkPaint;
+
+namespace sksg {
+
+/**
+ * Concrete Geometry node, wrapping an SkPath.
+ */
+class Path : public GeometryNode {
+public:
+ static sk_sp<Path> Make() { return sk_sp<Path>(new Path(SkPath())); }
+ static sk_sp<Path> Make(const SkPath& r) { return sk_sp<Path>(new Path(r)); }
+
+ SG_ATTRIBUTE(Path, SkPath, fPath)
+ SG_MAPPED_ATTRIBUTE(FillType, SkPath::FillType, fPath)
+
+protected:
+ void onClip(SkCanvas*, bool antiAlias) const override;
+ void onDraw(SkCanvas*, const SkPaint&) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+ SkPath onAsPath() const override;
+
+private:
+ explicit Path(const SkPath&);
+
+ SkPath fPath;
+
+ using INHERITED = GeometryNode;
+};
+
+} // namespace sksg
+
+#endif // SkSGPath_DEFINED
diff --git a/modules/sksg/include/SkSGPlane.h b/modules/sksg/include/SkSGPlane.h
new file mode 100644
index 0000000000..c0a26375b2
--- /dev/null
+++ b/modules/sksg/include/SkSGPlane.h
@@ -0,0 +1,40 @@
+/*
+ * 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 SkSGPlane_DEFINED
+#define SkSGPlane_DEFINED
+
+#include "SkSGGeometryNode.h"
+
+class SkCanvas;
+class SkPaint;
+
+namespace sksg {
+
+/**
+ * Concrete Geometry node, representing the whole canvas.
+ */
+class Plane final : public GeometryNode {
+public:
+ static sk_sp<Plane> Make() { return sk_sp<Plane>(new Plane()); }
+
+protected:
+ void onClip(SkCanvas*, bool antiAlias) const override;
+ void onDraw(SkCanvas*, const SkPaint&) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+ SkPath onAsPath() const override;
+
+private:
+ Plane();
+
+ using INHERITED = GeometryNode;
+};
+
+} // namespace sksg
+
+#endif // SkSGPlane_DEFINED
diff --git a/modules/sksg/include/SkSGRect.h b/modules/sksg/include/SkSGRect.h
new file mode 100644
index 0000000000..f5fcb962c6
--- /dev/null
+++ b/modules/sksg/include/SkSGRect.h
@@ -0,0 +1,76 @@
+/*
+ * 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 SkSGRect_DEFINED
+#define SkSGRect_DEFINED
+
+#include "SkSGGeometryNode.h"
+
+#include "SkRect.h"
+#include "SkRRect.h"
+
+class SkCanvas;
+class SkPaint;
+
+namespace sksg {
+
+/**
+ * Concrete Geometry node, wrapping an SkRect.
+ */
+class Rect final : public GeometryNode {
+public:
+ static sk_sp<Rect> Make() { return sk_sp<Rect>(new Rect(SkRect::MakeEmpty())); }
+ static sk_sp<Rect> Make(const SkRect& r) { return sk_sp<Rect>(new Rect(r)); }
+
+ SG_ATTRIBUTE(L, SkScalar, fRect.fLeft )
+ SG_ATTRIBUTE(T, SkScalar, fRect.fTop )
+ SG_ATTRIBUTE(R, SkScalar, fRect.fRight )
+ SG_ATTRIBUTE(B, SkScalar, fRect.fBottom)
+
+protected:
+ void onClip(SkCanvas*, bool antiAlias) const override;
+ void onDraw(SkCanvas*, const SkPaint&) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+ SkPath onAsPath() const override;
+
+private:
+ explicit Rect(const SkRect&);
+
+ SkRect fRect;
+
+ using INHERITED = GeometryNode;
+};
+
+/**
+ * Concrete Geometry node, wrapping an SkRRect.
+ */
+class RRect final : public GeometryNode {
+public:
+ static sk_sp<RRect> Make() { return sk_sp<RRect>(new RRect(SkRRect())); }
+ static sk_sp<RRect> Make(const SkRRect& rr) { return sk_sp<RRect>(new RRect(rr)); }
+
+ SG_ATTRIBUTE(RRect, SkRRect, fRRect)
+
+protected:
+ void onClip(SkCanvas*, bool antiAlias) const override;
+ void onDraw(SkCanvas*, const SkPaint&) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+ SkPath onAsPath() const override;
+
+private:
+ explicit RRect(const SkRRect&);
+
+ SkRRect fRRect;
+
+ using INHERITED = GeometryNode;
+};
+
+} // namespace sksg
+
+#endif // SkSGRect_DEFINED
diff --git a/modules/sksg/include/SkSGRenderNode.h b/modules/sksg/include/SkSGRenderNode.h
new file mode 100644
index 0000000000..4ca1aec616
--- /dev/null
+++ b/modules/sksg/include/SkSGRenderNode.h
@@ -0,0 +1,36 @@
+/*
+ * 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 SkSGRenderNode_DEFINED
+#define SkSGRenderNode_DEFINED
+
+#include "SkSGNode.h"
+
+class SkCanvas;
+
+namespace sksg {
+
+/**
+ * Base class for nodes which can render to a canvas.
+ */
+class RenderNode : public Node {
+public:
+ // Render the node and its descendants to the canvas.
+ void render(SkCanvas*) const;
+
+protected:
+ RenderNode();
+
+ virtual void onRender(SkCanvas*) const = 0;
+
+private:
+ typedef Node INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGRenderNode_DEFINED
diff --git a/modules/sksg/include/SkSGRoundEffect.h b/modules/sksg/include/SkSGRoundEffect.h
new file mode 100644
index 0000000000..67124ca072
--- /dev/null
+++ b/modules/sksg/include/SkSGRoundEffect.h
@@ -0,0 +1,50 @@
+/*
+ * 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 "SkSGGeometryNode.h"
+
+#include "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;
+
+ 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/SkSGScene.h b/modules/sksg/include/SkSGScene.h
new file mode 100644
index 0000000000..2081c1d747
--- /dev/null
+++ b/modules/sksg/include/SkSGScene.h
@@ -0,0 +1,85 @@
+/*
+ * 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 SkSGScene_DEFINED
+#define SkSGScene_DEFINED
+
+#include "SkRefCnt.h"
+#include "SkTypes.h"
+
+#include <memory>
+#include <vector>
+
+class SkCanvas;
+
+namespace sksg {
+
+class RenderNode;
+
+/**
+ * Base class for animators.
+ *
+ */
+class Animator : public SkNoncopyable {
+public:
+ virtual ~Animator();
+
+ void tick(float t);
+
+protected:
+ Animator();
+
+ virtual void onTick(float t) = 0;
+
+private:
+ using INHERITED = SkNoncopyable;
+};
+
+using AnimatorList = std::vector<std::unique_ptr<Animator>>;
+
+class GroupAnimator : public Animator {
+protected:
+ explicit GroupAnimator(AnimatorList&&);
+
+ void onTick(float t) override;
+
+private:
+ const AnimatorList fAnimators;
+
+ using INHERITED = Animator;
+};
+
+/**
+ * Holds a scene root and a list of animators.
+ *
+ * Provides high-level mehods for driving rendering and animations.
+ *
+ */
+class Scene final : SkNoncopyable {
+public:
+ static std::unique_ptr<Scene> Make(sk_sp<RenderNode> root, AnimatorList&& animators);
+ ~Scene();
+
+ void render(SkCanvas*) const;
+ void animate(float t);
+
+ void setShowInval(bool show) { fShowInval = show; }
+
+private:
+ Scene(sk_sp<RenderNode> root, AnimatorList&& animators);
+
+ const sk_sp<RenderNode> fRoot;
+ const AnimatorList fAnimators;
+
+ bool fShowInval = false;
+
+ using INHERITED = SkNoncopyable;
+};
+
+} // namespace sksg
+
+#endif // SkSGScene_DEFINED
diff --git a/modules/sksg/include/SkSGText.h b/modules/sksg/include/SkSGText.h
new file mode 100644
index 0000000000..eb43337a10
--- /dev/null
+++ b/modules/sksg/include/SkSGText.h
@@ -0,0 +1,69 @@
+/*
+ * 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 SkSGText_DEFINED
+#define SkSGText_DEFINED
+
+#include "SkSGGeometryNode.h"
+
+#include "SkPaintDefaults.h"
+#include "SkPoint.h"
+#include "SkString.h"
+
+class SkCanvas;
+class SkPaint;
+class SkTextBlob;
+class SkTypeface;
+
+namespace sksg {
+
+/**
+ * Concrete Geometry node, wrapping a (shaped) SkTextBlob.
+ */
+class Text final : public GeometryNode {
+public:
+ static sk_sp<Text> Make(sk_sp<SkTypeface> tf, const SkString& text);
+ ~Text() override;
+
+ SG_ATTRIBUTE(Text , SkString , fText )
+ SG_ATTRIBUTE(Flags , uint32_t , fFlags )
+ SG_ATTRIBUTE(Position, SkPoint , fPosition)
+ SG_ATTRIBUTE(Size , SkScalar , fSize )
+ SG_ATTRIBUTE(ScaleX , SkScalar , fScaleX )
+ SG_ATTRIBUTE(SkewX , SkScalar , fSkewX )
+ SG_ATTRIBUTE(Align , SkPaint::Align, fAlign )
+
+ // TODO: add shaping functionality.
+
+protected:
+ void onClip(SkCanvas*, bool antiAlias) const override;
+ void onDraw(SkCanvas*, const SkPaint&) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+ SkPath onAsPath() const override;
+
+private:
+ explicit Text(sk_sp<SkTypeface>, const SkString&);
+
+ const sk_sp<SkTypeface> fTypeface;
+ SkString fText;
+ uint32_t fFlags = SkPaintDefaults_Flags;
+ SkPoint fPosition = SkPoint::Make(0, 0);
+ SkScalar fSize = SkPaintDefaults_TextSize;
+ SkScalar fScaleX = 1;
+ SkScalar fSkewX = 0;
+ SkPaint::Align fAlign = SkPaint::kLeft_Align;
+ SkPaint::Hinting fHinting = SkPaintDefaults_Hinting;
+
+ sk_sp<SkTextBlob> fBlob; // cached text blob
+
+ using INHERITED = GeometryNode;
+};
+
+} // namespace sksg
+
+#endif // SkSGText_DEFINED
diff --git a/modules/sksg/include/SkSGTransform.h b/modules/sksg/include/SkSGTransform.h
new file mode 100644
index 0000000000..6b7fbc010b
--- /dev/null
+++ b/modules/sksg/include/SkSGTransform.h
@@ -0,0 +1,81 @@
+/*
+ * 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 SkSGTransform_DEFINED
+#define SkSGTransform_DEFINED
+
+#include "SkSGEffectNode.h"
+
+#include "SkMatrix.h"
+
+namespace sksg {
+
+/**
+ * Concrete node, wrapping an SkMatrix, with an optional parent Matrix (to allow chaining):
+ *
+ * M' = parent x M
+ */
+class Matrix : public Node {
+public:
+ static sk_sp<Matrix> Make(const SkMatrix& m, sk_sp<Matrix> parent = nullptr) {
+ return sk_sp<Matrix>(new Matrix(m, std::move(parent)));
+ }
+
+ ~Matrix() override;
+
+ SG_ATTRIBUTE(Matrix, SkMatrix, fLocalMatrix)
+
+ const SkMatrix& getTotalMatrix() const { return fTotalMatrix; }
+
+protected:
+ Matrix(const SkMatrix&, sk_sp<Matrix>);
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ sk_sp<Matrix> fParent;
+ SkMatrix fLocalMatrix,
+ fTotalMatrix; // cached during revalidation
+
+ typedef Node INHERITED;
+};
+
+/**
+ * Concrete Effect node, binding a Matrix to a RenderNode.
+ */
+class Transform final : public EffectNode {
+public:
+ static sk_sp<Transform> Make(sk_sp<RenderNode> child, sk_sp<Matrix> matrix) {
+ return child && matrix
+ ? sk_sp<Transform>(new Transform(std::move(child), std::move(matrix)))
+ : nullptr;
+ }
+
+ static sk_sp<Transform> Make(sk_sp<RenderNode> child, const SkMatrix& m) {
+ return Make(std::move(child), Matrix::Make(m));
+ }
+
+ ~Transform() override;
+
+ const sk_sp<Matrix>& getMatrix() const { return fMatrix; }
+
+protected:
+ void onRender(SkCanvas*) const override;
+
+ SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ Transform(sk_sp<RenderNode>, sk_sp<Matrix>);
+
+ const sk_sp<Matrix> fMatrix;
+
+ typedef EffectNode INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGTransform_DEFINED
diff --git a/modules/sksg/include/SkSGTrimEffect.h b/modules/sksg/include/SkSGTrimEffect.h
new file mode 100644
index 0000000000..18f15921e7
--- /dev/null
+++ b/modules/sksg/include/SkSGTrimEffect.h
@@ -0,0 +1,58 @@
+/*
+ * 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 "SkSGGeometryNode.h"
+
+#include "SkPath.h"
+#include "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;
+
+ 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/samples/SampleSVGPong.cpp b/modules/sksg/samples/SampleSVGPong.cpp
new file mode 100644
index 0000000000..993af38be8
--- /dev/null
+++ b/modules/sksg/samples/SampleSVGPong.cpp
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SampleCode.h"
+#include "SkAnimTimer.h"
+#include "SkColor.h"
+#include "SkRandom.h"
+#include "SkRRect.h"
+
+#include "SkSGColor.h"
+#include "SkSGDraw.h"
+#include "SkSGGroup.h"
+#include "SkSGPath.h"
+#include "SkSGRect.h"
+#include "SkSGScene.h"
+#include "SkSGTransform.h"
+
+namespace {
+
+static const SkRect kBounds = SkRect::MakeLTRB(0.1f, 0.1f, 0.9f, 0.9f);
+static const SkSize kPaddleSize = SkSize::Make(0.03f, 0.1f);
+static const SkScalar kBallSize = 0.04f;
+static const SkScalar kShadowOpacity = 0.40f;
+static const SkScalar kShadowParallax = 0.04f;
+static const SkScalar kBackgroundStroke = 0.01f;
+static const uint32_t kBackgroundDashCount = 20;
+
+static const SkScalar kBallSpeedMax = 0.0020f;
+static const SkScalar kBallSpeedMin = 0.0005f;
+static const SkScalar kBallSpeedFuzz = 0.0002f;
+
+static const SkScalar kTimeScaleMin = 0.0f;
+static const SkScalar kTimeScaleMax = 5.0f;
+
+// Box the value within [min, max), by applying infinite reflection on the interval endpoints.
+SkScalar box_reflect(SkScalar v, SkScalar min, SkScalar max) {
+ const SkScalar intervalLen = max - min;
+ SkASSERT(intervalLen > 0);
+
+ // f(v) is periodic in 2 * intervalLen: one normal progression + one reflection
+ const SkScalar P = intervalLen * 2;
+ // relative to P origin
+ const SkScalar vP = v - min;
+ // map to [0, P)
+ const SkScalar vMod = (vP < 0) ? P - SkScalarMod(-vP, P) : SkScalarMod(vP, P);
+ // reflect if needed, to map to [0, intervalLen)
+ const SkScalar vInterval = vMod < intervalLen ? vMod : P - vMod;
+ // finally, reposition relative to min
+ return vInterval + min;
+}
+
+// Compute <t, y> for the trajectory intersection with the next vertical edge.
+std::tuple<SkScalar, SkScalar> find_yintercept(const SkPoint& pos, const SkVector& spd,
+ const SkRect& box) {
+ const SkScalar edge = spd.fX > 0 ? box.fRight : box.fLeft;
+ const SkScalar t = (edge - pos.fX) / spd.fX;
+ SkASSERT(t >= 0);
+ const SkScalar dY = t * spd.fY;
+
+ return std::make_tuple(t, box_reflect(pos.fY + dY, box.fTop, box.fBottom));
+}
+
+void update_pos(const sk_sp<sksg::RRect>& rr, const SkPoint& pos) {
+ // TODO: position setters on RRect?
+
+ const auto r = rr->getRRect().rect();
+ const auto offsetX = pos.x() - r.x(),
+ offsetY = pos.y() - r.y();
+ rr->setRRect(rr->getRRect().makeOffset(offsetX, offsetY));
+}
+
+} // anonymous ns
+
+class PongView final : public SampleView {
+public:
+ PongView() = default;
+
+protected:
+ void onOnceBeforeDraw() override {
+ const SkRect fieldBounds = kBounds.makeOutset(kBallSize / 2, kBallSize / 2);
+ const SkRRect ball = SkRRect::MakeOval(SkRect::MakeWH(kBallSize, kBallSize));
+ const SkRRect paddle = SkRRect::MakeRectXY(SkRect::MakeWH(kPaddleSize.width(),
+ kPaddleSize.height()),
+ kPaddleSize.width() / 2,
+ kPaddleSize.width() / 2);
+ fBall.initialize(ball,
+ SkPoint::Make(kBounds.centerX(), kBounds.centerY()),
+ SkVector::Make(fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax),
+ fRand.nextRangeScalar(kBallSpeedMin, kBallSpeedMax)));
+ fPaddle0.initialize(paddle,
+ SkPoint::Make(fieldBounds.left() - kPaddleSize.width() / 2,
+ fieldBounds.centerY()),
+ SkVector::Make(0, 0));
+ fPaddle1.initialize(paddle,
+ SkPoint::Make(fieldBounds.right() + kPaddleSize.width() / 2,
+ fieldBounds.centerY()),
+ SkVector::Make(0, 0));
+
+ // Background decoration.
+ SkPath bgPath;
+ bgPath.moveTo(kBounds.left() , fieldBounds.top());
+ bgPath.lineTo(kBounds.right(), fieldBounds.top());
+ bgPath.moveTo(kBounds.left() , fieldBounds.bottom());
+ bgPath.lineTo(kBounds.right(), fieldBounds.bottom());
+ // TODO: stroke-dash support would come in handy right about now.
+ for (uint32_t i = 0; i < kBackgroundDashCount; ++i) {
+ bgPath.moveTo(kBounds.centerX(),
+ kBounds.top() + (i + 0.25f) * kBounds.height() / kBackgroundDashCount);
+ bgPath.lineTo(kBounds.centerX(),
+ kBounds.top() + (i + 0.75f) * kBounds.height() / kBackgroundDashCount);
+ }
+
+ auto bg_path = sksg::Path::Make(bgPath);
+ auto bg_paint = sksg::Color::Make(SK_ColorBLACK);
+ bg_paint->setStyle(SkPaint::kStroke_Style);
+ bg_paint->setStrokeWidth(kBackgroundStroke);
+
+ auto ball_paint = sksg::Color::Make(SK_ColorGREEN),
+ paddle0_paint = sksg::Color::Make(SK_ColorBLUE),
+ paddle1_paint = sksg::Color::Make(SK_ColorRED),
+ shadow_paint = sksg::Color::Make(SK_ColorBLACK);
+ ball_paint->setAntiAlias(true);
+ paddle0_paint->setAntiAlias(true);
+ paddle1_paint->setAntiAlias(true);
+ shadow_paint->setAntiAlias(true);
+ shadow_paint->setOpacity(kShadowOpacity);
+
+ // Build the scene graph.
+ auto group = sksg::Group::Make();
+ group->addChild(sksg::Draw::Make(std::move(bg_path), std::move(bg_paint)));
+ group->addChild(sksg::Draw::Make(fPaddle0.shadowNode, shadow_paint));
+ group->addChild(sksg::Draw::Make(fPaddle1.shadowNode, shadow_paint));
+ group->addChild(sksg::Draw::Make(fBall.shadowNode, shadow_paint));
+ group->addChild(sksg::Draw::Make(fPaddle0.objectNode, paddle0_paint));
+ group->addChild(sksg::Draw::Make(fPaddle1.objectNode, paddle1_paint));
+ group->addChild(sksg::Draw::Make(fBall.objectNode, ball_paint));
+
+ // Handle everything in a normalized 1x1 space.
+ fContentMatrix = sksg::Matrix::Make(
+ SkMatrix::MakeRectToRect(SkRect::MakeWH(1, 1),
+ SkRect::MakeIWH(this->width(), this->height()),
+ SkMatrix::kFill_ScaleToFit));
+ auto root = sksg::Transform::Make(std::move(group), fContentMatrix);
+ fScene = sksg::Scene::Make(std::move(root), sksg::AnimatorList());
+
+ // Off we go.
+ this->updatePaddleStrategy();
+ }
+
+ bool onQuery(SkEvent* evt) override {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "SGPong");
+ return true;
+ }
+
+ SkUnichar uni;
+ if (SampleCode::CharQ(*evt, &uni)) {
+ switch (uni) {
+ case '[':
+ fTimeScale = SkTPin(fTimeScale - 0.1f, kTimeScaleMin, kTimeScaleMax);
+ return true;
+ case ']':
+ fTimeScale = SkTPin(fTimeScale + 0.1f, kTimeScaleMin, kTimeScaleMax);
+ return true;
+ case 'I':
+ fShowInval = !fShowInval;
+ fScene->setShowInval(fShowInval);
+ return true;
+ default:
+ break;
+ }
+ }
+ return this->INHERITED::onQuery(evt);
+ }
+
+ void onSizeChange() override {
+ if (fContentMatrix) {
+ fContentMatrix->setMatrix(SkMatrix::MakeRectToRect(SkRect::MakeWH(1, 1),
+ SkRect::MakeIWH(this->width(),
+ this->height()),
+ SkMatrix::kFill_ScaleToFit));
+ }
+
+ this->INHERITED::onSizeChange();
+ }
+
+ void onDrawContent(SkCanvas* canvas) override {
+ fScene->render(canvas);
+ }
+
+ bool onAnimate(const SkAnimTimer& timer) override {
+ // onAnimate may fire before the first draw.
+ if (fScene) {
+ SkScalar dt = (timer.msec() - fLastTick) * fTimeScale;
+ fLastTick = timer.msec();
+
+ fPaddle0.posTick(dt);
+ fPaddle1.posTick(dt);
+ fBall.posTick(dt);
+
+ this->enforceConstraints();
+
+ fPaddle0.updateDom();
+ fPaddle1.updateDom();
+ fBall.updateDom();
+ }
+ return true;
+ }
+
+private:
+ struct Object {
+ void initialize(const SkRRect& rrect, const SkPoint& p, const SkVector& s) {
+ objectNode = sksg::RRect::Make(rrect);
+ shadowNode = sksg::RRect::Make(rrect);
+
+ pos = p;
+ spd = s;
+ size = SkSize::Make(rrect.width(), rrect.height());
+ }
+
+ void posTick(SkScalar dt) {
+ pos += spd * dt;
+ }
+
+ void updateDom() {
+ const SkPoint corner = pos - SkPoint::Make(size.width() / 2, size.height() / 2);
+ update_pos(objectNode, corner);
+
+ // Simulate parallax shadow for a centered light source.
+ SkPoint shadowOffset = pos - SkPoint::Make(kBounds.centerX(), kBounds.centerY());
+ shadowOffset.scale(kShadowParallax);
+ const SkPoint shadowCorner = corner + shadowOffset;
+
+ update_pos(shadowNode, shadowCorner);
+ }
+
+ sk_sp<sksg::RRect> objectNode,
+ shadowNode;
+ SkPoint pos;
+ SkVector spd;
+ SkSize size;
+ };
+
+ void enforceConstraints() {
+ // Perfect vertical reflection.
+ if (fBall.pos.fY < kBounds.fTop || fBall.pos.fY >= kBounds.fBottom) {
+ fBall.spd.fY = -fBall.spd.fY;
+ fBall.pos.fY = box_reflect(fBall.pos.fY, kBounds.fTop, kBounds.fBottom);
+ }
+
+ // Horizontal bounce - introduces a speed fuzz.
+ if (fBall.pos.fX < kBounds.fLeft || fBall.pos.fX >= kBounds.fRight) {
+ fBall.spd.fX = this->fuzzBallSpeed(-fBall.spd.fX);
+ fBall.spd.fY = this->fuzzBallSpeed(fBall.spd.fY);
+ fBall.pos.fX = box_reflect(fBall.pos.fX, kBounds.fLeft, kBounds.fRight);
+ this->updatePaddleStrategy();
+ }
+ }
+
+ SkScalar fuzzBallSpeed(SkScalar spd) {
+ // The speed limits are absolute values.
+ const SkScalar sign = spd >= 0 ? 1.0f : -1.0f;
+ const SkScalar fuzzed = fabs(spd) + fRand.nextRangeScalar(-kBallSpeedFuzz, kBallSpeedFuzz);
+
+ return sign * SkTPin(fuzzed, kBallSpeedMin, kBallSpeedMax);
+ }
+
+ void updatePaddleStrategy() {
+ Object* pitcher = fBall.spd.fX > 0 ? &fPaddle0 : &fPaddle1;
+ Object* catcher = fBall.spd.fX > 0 ? &fPaddle1 : &fPaddle0;
+
+ SkScalar t, yIntercept;
+ std::tie(t, yIntercept) = find_yintercept(fBall.pos, fBall.spd, kBounds);
+
+ // The pitcher aims for a neutral/centered position.
+ pitcher->spd.fY = (kBounds.centerY() - pitcher->pos.fY) / t;
+
+ // The catcher goes for the ball. Duh.
+ catcher->spd.fY = (yIntercept - catcher->pos.fY) / t;
+ }
+
+ std::unique_ptr<sksg::Scene> fScene;
+ sk_sp<sksg::Matrix> fContentMatrix;
+ Object fPaddle0, fPaddle1, fBall;
+ SkRandom fRand;
+
+ SkMSec fLastTick = 0;
+ SkScalar fTimeScale = 1.0f;
+ bool fShowInval = false;
+
+ typedef SampleView INHERITED;
+};
+
+static SkView* PongFactory() { return new PongView; }
+static SkViewRegister reg(PongFactory);
diff --git a/modules/sksg/src/SkSGClipEffect.cpp b/modules/sksg/src/SkSGClipEffect.cpp
new file mode 100644
index 0000000000..b2d68fc8cf
--- /dev/null
+++ b/modules/sksg/src/SkSGClipEffect.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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 "SkSGClipEffect.h"
+
+#include "SkCanvas.h"
+#include "SkPath.h"
+#include "SkSGGeometryNode.h"
+
+namespace sksg {
+
+ClipEffect::ClipEffect(sk_sp<RenderNode> child, sk_sp<GeometryNode> clip, bool aa)
+ : INHERITED(std::move(child))
+ , fClipNode(std::move(clip))
+ , fAntiAlias(aa) {
+ this->observeInval(fClipNode);
+}
+
+ClipEffect::~ClipEffect() {
+ this->unobserveInval(fClipNode);
+}
+
+void ClipEffect::onRender(SkCanvas* canvas) const {
+ if (this->bounds().isEmpty())
+ return;
+
+ SkAutoCanvasRestore acr(canvas, !fNoop);
+ if (!fNoop) {
+ fClipNode->clip(canvas, fAntiAlias);
+ }
+
+ this->INHERITED::onRender(canvas);
+}
+
+SkRect ClipEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->hasInval());
+
+ const auto clipBounds = fClipNode->revalidate(ic, ctm);
+ auto childBounds = this->INHERITED::onRevalidate(ic, ctm);
+
+ fNoop = fClipNode->asPath().conservativelyContainsRect(childBounds);
+
+ return childBounds.intersect(clipBounds) ? childBounds : SkRect::MakeEmpty();
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGColor.cpp b/modules/sksg/src/SkSGColor.cpp
new file mode 100644
index 0000000000..d5d4d1ce62
--- /dev/null
+++ b/modules/sksg/src/SkSGColor.cpp
@@ -0,0 +1,18 @@
+/*
+ * 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 "SkSGColor.h"
+
+namespace sksg {
+
+Color::Color(SkColor c) : fColor(c) {}
+
+void Color::onApplyToPaint(SkPaint* paint) const {
+ paint->setColor(fColor);
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGDraw.cpp b/modules/sksg/src/SkSGDraw.cpp
new file mode 100644
index 0000000000..b73bf3b577
--- /dev/null
+++ b/modules/sksg/src/SkSGDraw.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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 "SkSGDraw.h"
+
+#include "SkSGGeometryNode.h"
+#include "SkSGInvalidationController.h"
+#include "SkSGPaintNode.h"
+
+namespace sksg {
+
+Draw::Draw(sk_sp<GeometryNode> geometry, sk_sp<PaintNode> paint)
+ : fGeometry(std::move(geometry))
+ , fPaint(std::move(paint)) {
+ this->observeInval(fGeometry);
+ this->observeInval(fPaint);
+}
+
+Draw::~Draw() {
+ this->unobserveInval(fGeometry);
+ this->unobserveInval(fPaint);
+}
+
+void Draw::onRender(SkCanvas* canvas) const {
+ const auto& paint = fPaint->makePaint();
+ const auto skipDraw = paint.nothingToDraw() ||
+ (paint.getStyle() == SkPaint::kStroke_Style && paint.getStrokeWidth() <= 0);
+
+ if (!skipDraw) {
+ fGeometry->draw(canvas, paint);
+ }
+}
+
+SkRect Draw::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->hasInval());
+
+ auto bounds = fGeometry->revalidate(ic, ctm);
+ fPaint->revalidate(ic, ctm);
+
+ const auto& paint = fPaint->makePaint();
+ SkASSERT(paint.canComputeFastBounds());
+
+ return paint.computeFastBounds(bounds, &bounds);
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGEffectNode.cpp b/modules/sksg/src/SkSGEffectNode.cpp
new file mode 100644
index 0000000000..70050ccb70
--- /dev/null
+++ b/modules/sksg/src/SkSGEffectNode.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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 "SkSGEffectNode.h"
+
+namespace sksg {
+
+EffectNode::EffectNode(sk_sp<RenderNode> child)
+ : fChild(std::move(child)) {
+ this->observeInval(fChild);
+}
+
+EffectNode::~EffectNode() {
+ this->unobserveInval(fChild);
+}
+
+void EffectNode::onRender(SkCanvas* canvas) const {
+ fChild->render(canvas);
+}
+
+SkRect EffectNode::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->hasInval());
+
+ return fChild->revalidate(ic, ctm);
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGGeometryNode.cpp b/modules/sksg/src/SkSGGeometryNode.cpp
new file mode 100644
index 0000000000..6b78c488b7
--- /dev/null
+++ b/modules/sksg/src/SkSGGeometryNode.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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 "SkSGGeometryNode.h"
+
+#include "SkPath.h"
+
+namespace sksg {
+
+// Geometry nodes don't generate damage on their own, but via their aggregation ancestor Draw nodes.
+GeometryNode::GeometryNode() : INHERITED(kBubbleDamage_Trait) {}
+
+void GeometryNode::clip(SkCanvas* canvas, bool aa) const {
+ SkASSERT(!this->hasInval());
+ this->onClip(canvas, aa);
+}
+
+void GeometryNode::draw(SkCanvas* canvas, const SkPaint& paint) const {
+ SkASSERT(!this->hasInval());
+ this->onDraw(canvas, paint);
+}
+
+SkPath GeometryNode::asPath() const {
+ SkASSERT(!this->hasInval());
+ return this->onAsPath();
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGGeometryTransform.cpp b/modules/sksg/src/SkSGGeometryTransform.cpp
new file mode 100644
index 0000000000..5b366b9620
--- /dev/null
+++ b/modules/sksg/src/SkSGGeometryTransform.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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 "SkSGGeometryTransform.h"
+
+#include "SkCanvas.h"
+
+namespace sksg {
+
+GeometryTransform::GeometryTransform(sk_sp<GeometryNode> child, sk_sp<Matrix> matrix)
+ : fChild(std::move(child))
+ , fMatrix(std::move(matrix)) {
+ this->observeInval(fChild);
+ this->observeInval(fMatrix);
+}
+
+GeometryTransform::~GeometryTransform() {
+ this->unobserveInval(fChild);
+ this->unobserveInval(fMatrix);
+}
+
+void GeometryTransform::onClip(SkCanvas* canvas, bool antiAlias) const {
+ canvas->clipPath(fTransformed, SkClipOp::kIntersect, antiAlias);
+}
+
+void GeometryTransform::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
+ canvas->drawPath(fTransformed, paint);
+}
+
+SkRect GeometryTransform::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->hasInval());
+
+ // We don't care about matrix reval results.
+ fMatrix->revalidate(ic, ctm);
+ const auto& m = fMatrix->getMatrix();
+
+ auto bounds = fChild->revalidate(ic, ctm);
+ fTransformed = fChild->asPath();
+ fTransformed.transform(m);
+
+ m.mapRect(&bounds);
+ return bounds;
+}
+
+SkPath GeometryTransform::onAsPath() const {
+ return fTransformed;
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGGradient.cpp b/modules/sksg/src/SkSGGradient.cpp
new file mode 100644
index 0000000000..98e7f395f8
--- /dev/null
+++ b/modules/sksg/src/SkSGGradient.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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 "SkSGGradient.h"
+
+#include "SkGradientShader.h"
+#include "SkPaint.h"
+
+namespace sksg {
+
+void Gradient::onApplyToPaint(SkPaint* paint) const {
+ if (fColorStops.empty()) {
+ paint->setShader(nullptr);
+ return;
+ }
+
+ std::vector<SkColor> colors;
+ std::vector<SkScalar> positions;
+ colors.reserve(fColorStops.size());
+ positions.reserve(fColorStops.size());
+
+ SkScalar position = 0;
+ for (const auto& stop : fColorStops) {
+ colors.push_back(stop.fColor);
+ position = SkTPin(stop.fPosition, position, 1.0f);
+ positions.push_back(position);
+ }
+
+ // TODO: detect even stop distributions, pass null for positions.
+ paint->setShader(this->onMakeShader(colors, positions));
+}
+
+sk_sp<SkShader> LinearGradient::onMakeShader(const std::vector<SkColor>& colors,
+ const std::vector<SkScalar>& positions) const {
+ SkASSERT(colors.size() == positions.size());
+
+ const SkPoint pts[] = { fStartPoint, fEndPoint };
+ return SkGradientShader::MakeLinear(pts, colors.data(), positions.data(), colors.size(),
+ this->getTileMode());
+}
+
+sk_sp<SkShader> RadialGradient::onMakeShader(const std::vector<SkColor>& colors,
+ const std::vector<SkScalar>& positions) const {
+ SkASSERT(colors.size() == positions.size());
+
+ return (fStartRadius <= 0 && fStartCenter == fEndCenter)
+ ? SkGradientShader::MakeRadial(fEndCenter, fEndRadius,
+ colors.data(), positions.data(), colors.size(),
+ this->getTileMode())
+ : SkGradientShader::MakeTwoPointConical(fStartCenter, fStartRadius,
+ fEndCenter, fEndRadius,
+ colors.data(), positions.data(), colors.size(),
+ this->getTileMode());
+}
+
+} //namespace sksg
diff --git a/modules/sksg/src/SkSGGroup.cpp b/modules/sksg/src/SkSGGroup.cpp
new file mode 100644
index 0000000000..aeccf233f7
--- /dev/null
+++ b/modules/sksg/src/SkSGGroup.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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 "SkSGGroup.h"
+
+namespace sksg {
+
+Group::Group() {}
+
+Group::~Group() {
+ for (const auto& child : fChildren) {
+ this->unobserveInval(child);
+ }
+}
+
+void Group::addChild(sk_sp<RenderNode> node) {
+ // should we allow duplicates?
+ for (const auto& child : fChildren) {
+ if (child == node) {
+ return;
+ }
+ }
+
+ this->observeInval(node);
+ fChildren.push_back(std::move(node));
+
+ this->invalidate();
+}
+
+void Group::removeChild(const sk_sp<RenderNode>& node) {
+ int origCount = fChildren.count();
+ for (int i = 0; i < origCount; ++i) {
+ if (fChildren[i] == node) {
+ fChildren.removeShuffle(i);
+ this->unobserveInval(node);
+ break;
+ }
+ }
+ SkASSERT(fChildren.count() == origCount - 1);
+
+ this->invalidate();
+}
+
+void Group::onRender(SkCanvas* canvas) const {
+ for (const auto& child : fChildren) {
+ child->render(canvas);
+ }
+}
+
+SkRect Group::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->hasInval());
+
+ SkRect bounds = SkRect::MakeEmpty();
+
+ for (const auto& child : fChildren) {
+ bounds.join(child->revalidate(ic, ctm));
+ }
+
+ return bounds;
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGImage.cpp b/modules/sksg/src/SkSGImage.cpp
new file mode 100644
index 0000000000..a0c3a759dc
--- /dev/null
+++ b/modules/sksg/src/SkSGImage.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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 "SkSGImage.h"
+
+#include "SkCanvas.h"
+#include "SkImage.h"
+
+namespace sksg {
+
+Image::Image(sk_sp<SkImage> image) : fImage(std::move(image)) {}
+
+void Image::onRender(SkCanvas* canvas) const {
+ SkPaint paint;
+ paint.setAntiAlias(fAntiAlias);
+ paint.setFilterQuality(fQuality);
+
+ canvas->drawImage(fImage, 0, 0);
+}
+
+SkRect Image::onRevalidate(InvalidationController*, const SkMatrix& ctm) {
+ return SkRect::Make(fImage->bounds());
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGInvalidationController.cpp b/modules/sksg/src/SkSGInvalidationController.cpp
new file mode 100644
index 0000000000..81a3376bf6
--- /dev/null
+++ b/modules/sksg/src/SkSGInvalidationController.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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 "SkSGInvalidationController.h"
+
+#include "SkRect.h"
+#include "SkTLazy.h"
+
+namespace sksg {
+
+InvalidationController::InvalidationController() : fBounds(SkRect::MakeEmpty()) {}
+
+void InvalidationController::inval(const SkRect& r, const SkMatrix& ctm) {
+ if (r.isEmpty()) {
+ return;
+ }
+
+ SkTCopyOnFirstWrite<SkRect> rect(r);
+
+ if (!ctm.isIdentity()) {
+ ctm.mapRect(rect.writable());
+ }
+
+ fRects.push(*rect);
+ fBounds.join(*rect);
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGMaskEffect.cpp b/modules/sksg/src/SkSGMaskEffect.cpp
new file mode 100644
index 0000000000..16e4c0dd8d
--- /dev/null
+++ b/modules/sksg/src/SkSGMaskEffect.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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 "SkSGMaskEffect.h"
+
+#include "SkCanvas.h"
+
+namespace sksg {
+
+MaskEffect::MaskEffect(sk_sp<RenderNode> child, sk_sp<RenderNode> mask, Mode mode)
+ : INHERITED(std::move(child))
+ , fMaskNode(std::move(mask))
+ , fMaskMode(mode) {
+ this->observeInval(fMaskNode);
+}
+
+MaskEffect::~MaskEffect() {
+ this->unobserveInval(fMaskNode);
+}
+
+void MaskEffect::onRender(SkCanvas* canvas) const {
+ if (this->bounds().isEmpty())
+ return;
+
+ SkAutoCanvasRestore acr(canvas, false);
+
+ canvas->saveLayer(this->bounds(), nullptr);
+ fMaskNode->render(canvas);
+
+
+ SkPaint p;
+ p.setBlendMode(fMaskMode == Mode::kNormal ? SkBlendMode::kSrcIn : SkBlendMode::kSrcOut);
+ canvas->saveLayer(this->bounds(), &p);
+
+ this->INHERITED::onRender(canvas);
+}
+
+
+SkRect MaskEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->hasInval());
+
+ const auto maskBounds = fMaskNode->revalidate(ic, ctm);
+ auto childBounds = this->INHERITED::onRevalidate(ic, ctm);
+
+ return (fMaskMode == Mode::kInvert || childBounds.intersect(maskBounds))
+ ? childBounds
+ : SkRect::MakeEmpty();
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGMerge.cpp b/modules/sksg/src/SkSGMerge.cpp
new file mode 100644
index 0000000000..be1ff4123a
--- /dev/null
+++ b/modules/sksg/src/SkSGMerge.cpp
@@ -0,0 +1,84 @@
+/*
+ * 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 "SkSGMerge.h"
+
+#include "SkCanvas.h"
+#include "SkPathOps.h"
+
+namespace sksg {
+
+Merge::Merge(std::vector<sk_sp<GeometryNode>>&& geos, Mode mode)
+ : fGeos(std::move(geos))
+ , fMode(mode) {
+ for (const auto& geo : fGeos) {
+ this->observeInval(geo);
+ }
+}
+
+Merge::~Merge() {
+ for (const auto& geo : fGeos) {
+ this->unobserveInval(geo);
+ }
+}
+
+void Merge::onClip(SkCanvas* canvas, bool antiAlias) const {
+ canvas->clipPath(fMerged, SkClipOp::kIntersect, antiAlias);
+}
+
+void Merge::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
+ canvas->drawPath(fMerged, paint);
+}
+
+SkPath Merge::onAsPath() const {
+ return fMerged;
+}
+
+static SkPathOp mode_to_op(Merge::Mode mode) {
+ switch (mode) {
+ case Merge::Mode::kUnion:
+ return kUnion_SkPathOp;
+ case Merge::Mode::kIntersect:
+ return kIntersect_SkPathOp;
+ case Merge::Mode::kDifference:
+ return kDifference_SkPathOp;
+ case Merge::Mode::kReverseDifference:
+ return kReverseDifference_SkPathOp;
+ case Merge::Mode::kXOR:
+ return kXOR_SkPathOp;
+ default:
+ break;
+ }
+
+ return kUnion_SkPathOp;
+}
+
+SkRect Merge::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->hasInval());
+
+ const auto op = mode_to_op(fMode);
+ SkOpBuilder builder;
+
+ fMerged.reset();
+
+ for (const auto& geo : fGeos) {
+ geo->revalidate(ic, ctm);
+ if (fMode == Mode::kMerge) {
+ fMerged.addPath(geo->asPath());
+ } else {
+ builder.add(geo->asPath(), geo == fGeos.front() ? kUnion_SkPathOp : op);
+ }
+ }
+
+ if (fMode != Mode::kMerge) {
+ builder.resolve(&fMerged);
+ }
+
+ return fMerged.computeTightBounds();
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGNode.cpp b/modules/sksg/src/SkSGNode.cpp
new file mode 100644
index 0000000000..35b2640dbb
--- /dev/null
+++ b/modules/sksg/src/SkSGNode.cpp
@@ -0,0 +1,152 @@
+/*
+ * 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 "SkRectPriv.h"
+#include "SkSGNode.h"
+#include "SkSGInvalidationController.h"
+
+namespace sksg {
+
+class Node::ScopedFlag {
+public:
+ ScopedFlag(Node* node, uint32_t flag)
+ : fNode(node)
+ , fFlag(flag)
+ , fWasSet(node->fFlags & flag) {
+ node->fFlags |= flag;
+ }
+ ~ScopedFlag() {
+ if (!fWasSet) {
+ fNode->fFlags &= ~fFlag;;
+ }
+ }
+
+ bool wasSet() const { return fWasSet; }
+
+private:
+ Node* fNode;
+ uint32_t fFlag;
+ bool fWasSet;
+};
+
+#define TRAVERSAL_GUARD \
+ ScopedFlag traversal_guard(this, kInTraversal_Flag); \
+ if (traversal_guard.wasSet()) \
+ return
+
+Node::Node(uint32_t invalTraits)
+ : fInvalObserver(nullptr)
+ , fBounds(SkRectPriv::MakeLargeS32())
+ , fInvalTraits(invalTraits)
+ , fFlags(kInvalidated_Flag) {}
+
+Node::~Node() {
+ if (fFlags & kObserverArray_Flag) {
+ SkASSERT(fInvalObserverArray->isEmpty());
+ delete fInvalObserverArray;
+ } else {
+ SkASSERT(!fInvalObserver);
+ }
+}
+
+void Node::observeInval(const sk_sp<Node>& node) {
+ SkASSERT(node);
+ if (!(node->fFlags & kObserverArray_Flag)) {
+ if (!node->fInvalObserver) {
+ node->fInvalObserver = this;
+ return;
+ }
+
+ auto observers = new SkTDArray<Node*>();
+ observers->setReserve(2);
+ observers->push(node->fInvalObserver);
+
+ node->fInvalObserverArray = observers;
+ node->fFlags |= kObserverArray_Flag;
+ }
+
+ // No duplicate observers.
+ SkASSERT(node->fInvalObserverArray->find(this) < 0);
+
+ node->fInvalObserverArray->push(this);
+}
+
+void Node::unobserveInval(const sk_sp<Node>& node) {
+ SkASSERT(node);
+ if (!(node->fFlags & kObserverArray_Flag)) {
+ SkASSERT(node->fInvalObserver == this);
+ node->fInvalObserver = nullptr;
+ return;
+ }
+
+ const auto idx = node->fInvalObserverArray->find(this);
+ SkASSERT(idx >= 0);
+ node->fInvalObserverArray->remove(idx);
+}
+
+template <typename Func>
+void Node::forEachInvalObserver(Func&& func) const {
+ if (fFlags & kObserverArray_Flag) {
+ for (const auto& parent : *fInvalObserverArray) {
+ func(parent);
+ }
+ return;
+ }
+
+ if (fInvalObserver) {
+ func(fInvalObserver);
+ }
+}
+
+void Node::invalidate(bool damageBubbling) {
+ TRAVERSAL_GUARD;
+
+ if (this->hasInval() && (!damageBubbling || (fFlags & kDamage_Flag))) {
+ // All done.
+ return;
+ }
+
+ if (damageBubbling && !(fInvalTraits & kBubbleDamage_Trait)) {
+ // Found a damage observer.
+ fFlags |= kDamage_Flag;
+ damageBubbling = false;
+ }
+
+ fFlags |= kInvalidated_Flag;
+
+ forEachInvalObserver([&](Node* observer) {
+ observer->invalidate(damageBubbling);
+ });
+}
+
+const SkRect& Node::revalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ TRAVERSAL_GUARD fBounds;
+
+ if (!this->hasInval()) {
+ return fBounds;
+ }
+
+ SkRect prevBounds;
+ if (fFlags & kDamage_Flag) {
+ prevBounds = fBounds;
+ }
+
+ fBounds = this->onRevalidate(ic, ctm);
+
+ if (fFlags & kDamage_Flag) {
+ ic->inval(prevBounds, ctm);
+ if (fBounds != prevBounds) {
+ ic->inval(fBounds, ctm);
+ }
+ }
+
+ fFlags &= ~(kInvalidated_Flag | kDamage_Flag);
+
+ return fBounds;
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGOpacityEffect.cpp b/modules/sksg/src/SkSGOpacityEffect.cpp
new file mode 100644
index 0000000000..b1ff10d217
--- /dev/null
+++ b/modules/sksg/src/SkSGOpacityEffect.cpp
@@ -0,0 +1,42 @@
+/*
+ * 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 "SkSGOpacityEffect.h"
+
+#include "SkCanvas.h"
+
+#include <math.h>
+
+namespace sksg {
+
+OpacityEffect::OpacityEffect(sk_sp<RenderNode> child, float opacity)
+ : INHERITED(std::move(child))
+ , fOpacity(opacity) {}
+
+void OpacityEffect::onRender(SkCanvas* canvas) const {
+ // opacity <= 0 disables rendering
+ if (fOpacity <= 0)
+ return;
+
+ // TODO: we could avoid savelayer if there is no more than one drawing primitive
+ // in the sub-DAG.
+ SkAutoCanvasRestore acr(canvas, false);
+ if (fOpacity < 1) {
+ canvas->saveLayerAlpha(&this->bounds(), roundf(fOpacity * 255));
+ }
+
+ this->INHERITED::onRender(canvas);
+}
+
+SkRect OpacityEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->hasInval());
+
+ // opacity <= 0 disables rendering AND revalidation for the sub-DAG
+ return fOpacity > 0 ? this->INHERITED::onRevalidate(ic, ctm) : SkRect::MakeEmpty();
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGPaintNode.cpp b/modules/sksg/src/SkSGPaintNode.cpp
new file mode 100644
index 0000000000..9220b0f0af
--- /dev/null
+++ b/modules/sksg/src/SkSGPaintNode.cpp
@@ -0,0 +1,41 @@
+/*
+ * 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 "SkSGPaintNode.h"
+
+namespace sksg {
+
+// Paint nodes don't generate damage on their own, but via their aggregation ancestor Draw nodes.
+PaintNode::PaintNode() : INHERITED(kBubbleDamage_Trait) {}
+
+const SkPaint& PaintNode::makePaint() {
+ SkASSERT(!this->hasInval());
+
+ return fPaint;
+}
+
+SkRect PaintNode::onRevalidate(InvalidationController*, const SkMatrix&) {
+ SkASSERT(this->hasInval());
+
+ fPaint.reset();
+ fPaint.setAntiAlias(fAntiAlias);
+ fPaint.setBlendMode(fBlendMode);
+ fPaint.setStyle(fStyle);
+ fPaint.setStrokeWidth(fStrokeWidth);
+ fPaint.setStrokeMiter(fStrokeMiter);
+ fPaint.setStrokeJoin(fStrokeJoin);
+ fPaint.setStrokeCap(fStrokeCap);
+
+ this->onApplyToPaint(&fPaint);
+
+ // Compose opacity on top of the subclass value.
+ fPaint.setAlpha(SkScalarRoundToInt(fPaint.getAlpha() * SkTPin<SkScalar>(fOpacity, 0, 1)));
+
+ return SkRect::MakeEmpty();
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGPath.cpp b/modules/sksg/src/SkSGPath.cpp
new file mode 100644
index 0000000000..230442d409
--- /dev/null
+++ b/modules/sksg/src/SkSGPath.cpp
@@ -0,0 +1,41 @@
+/*
+ * 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 "SkSGPath.h"
+
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkRectPriv.h"
+
+namespace sksg {
+
+Path::Path(const SkPath& path) : fPath(path) {}
+
+void Path::onClip(SkCanvas* canvas, bool antiAlias) const {
+ canvas->clipPath(fPath, SkClipOp::kIntersect, antiAlias);
+}
+
+void Path::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
+ canvas->drawPath(fPath, paint);
+}
+
+SkRect Path::onRevalidate(InvalidationController*, const SkMatrix&) {
+ SkASSERT(this->hasInval());
+
+ const auto ft = fPath.getFillType();
+ return (ft == SkPath::kWinding_FillType || ft == SkPath::kEvenOdd_FillType)
+ // "Containing" fills have finite bounds.
+ ? fPath.computeTightBounds()
+ // Inverse fills are "infinite".
+ : SkRectPriv::MakeLargeS32();
+}
+
+SkPath Path::onAsPath() const {
+ return fPath;
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGPlane.cpp b/modules/sksg/src/SkSGPlane.cpp
new file mode 100644
index 0000000000..806fcc7d29
--- /dev/null
+++ b/modules/sksg/src/SkSGPlane.cpp
@@ -0,0 +1,36 @@
+/*
+ * 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 "SkSGPlane.h"
+
+#include "SkCanvas.h"
+#include "SkPath.h"
+
+namespace sksg {
+
+Plane::Plane() = default;
+
+void Plane::onClip(SkCanvas*, bool) const {}
+
+void Plane::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
+ canvas->drawPaint(paint);
+}
+
+SkRect Plane::onRevalidate(InvalidationController*, const SkMatrix&) {
+ SkASSERT(this->hasInval());
+
+ return SkRect::MakeLTRB(SK_ScalarMin, SK_ScalarMin, SK_ScalarMax, SK_ScalarMax);
+}
+
+SkPath Plane::onAsPath() const {
+ SkPath path;
+ path.setFillType(SkPath::kInverseWinding_FillType);
+
+ return path;
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGRect.cpp b/modules/sksg/src/SkSGRect.cpp
new file mode 100644
index 0000000000..16f0a6f1e1
--- /dev/null
+++ b/modules/sksg/src/SkSGRect.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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 "SkSGRect.h"
+
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+
+namespace sksg {
+
+Rect::Rect(const SkRect& rect) : fRect(rect) {}
+
+void Rect::onClip(SkCanvas* canvas, bool antiAlias) const {
+ canvas->clipRect(fRect, SkClipOp::kIntersect, antiAlias);
+}
+
+void Rect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
+ canvas->drawRect(fRect, paint);
+}
+
+SkRect Rect::onRevalidate(InvalidationController*, const SkMatrix&) {
+ SkASSERT(this->hasInval());
+
+ return fRect;
+}
+
+SkPath Rect::onAsPath() const {
+ SkPath path;
+ path.addRect(fRect);
+ return path;
+}
+
+RRect::RRect(const SkRRect& rr) : fRRect(rr) {}
+
+void RRect::onClip(SkCanvas* canvas, bool antiAlias) const {
+ canvas->clipRRect(fRRect, SkClipOp::kIntersect, antiAlias);
+}
+
+void RRect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
+ canvas->drawRRect(fRRect, paint);
+}
+
+SkRect RRect::onRevalidate(InvalidationController*, const SkMatrix&) {
+ SkASSERT(this->hasInval());
+
+ return fRRect.getBounds();
+}
+
+SkPath RRect::onAsPath() const {
+ SkPath path;
+ path.addRRect(fRRect);
+ return path;
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGRenderNode.cpp b/modules/sksg/src/SkSGRenderNode.cpp
new file mode 100644
index 0000000000..e952c69f20
--- /dev/null
+++ b/modules/sksg/src/SkSGRenderNode.cpp
@@ -0,0 +1,19 @@
+/*
+ * 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 "SkSGRenderNode.h"
+
+namespace sksg {
+
+RenderNode::RenderNode() : INHERITED(0) {}
+
+void RenderNode::render(SkCanvas* canvas) const {
+ SkASSERT(!this->hasInval());
+ this->onRender(canvas);
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGRoundEffect.cpp b/modules/sksg/src/SkSGRoundEffect.cpp
new file mode 100644
index 0000000000..8cf9068f65
--- /dev/null
+++ b/modules/sksg/src/SkSGRoundEffect.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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 "SkSGRoundEffect.h"
+
+#include "SkCanvas.h"
+#include "SkCornerPathEffect.h"
+#include "SkStrokeRec.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);
+}
+
+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;
+ }
+
+ return fRoundedPath.computeTightBounds();
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGScene.cpp b/modules/sksg/src/SkSGScene.cpp
new file mode 100644
index 0000000000..8d7e0b369b
--- /dev/null
+++ b/modules/sksg/src/SkSGScene.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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 "SkSGScene.h"
+
+#include "SkCanvas.h"
+#include "SkMatrix.h"
+#include "SkPaint.h"
+#include "SkSGInvalidationController.h"
+#include "SkSGRenderNode.h"
+
+namespace sksg {
+
+Animator::Animator() = default;
+Animator::~Animator() = default;
+
+void Animator::tick(float t) {
+ this->onTick(t);
+}
+
+GroupAnimator::GroupAnimator(AnimatorList&& animators)
+ : fAnimators(std::move(animators)) {}
+
+void GroupAnimator::onTick(float t) {
+ for (const auto& a : fAnimators) {
+ a->tick(t);
+ }
+}
+
+std::unique_ptr<Scene> Scene::Make(sk_sp<RenderNode> root, AnimatorList&& anims) {
+ return root ? std::unique_ptr<Scene>(new Scene(std::move(root), std::move(anims))) : nullptr;
+}
+
+Scene::Scene(sk_sp<RenderNode> root, AnimatorList&& animators)
+ : fRoot(std::move(root))
+ , fAnimators(std::move(animators)) {}
+
+Scene::~Scene() = default;
+
+void Scene::render(SkCanvas* canvas) const {
+ InvalidationController ic;
+ fRoot->revalidate(&ic, SkMatrix::I());
+ fRoot->render(canvas);
+
+ if (fShowInval) {
+ SkPaint fill, stroke;
+ fill.setAntiAlias(true);
+ fill.setColor(0x40ff0000);
+ stroke.setAntiAlias(true);
+ stroke.setColor(0xffff0000);
+ stroke.setStyle(SkPaint::kStroke_Style);
+
+ for (const auto& r : ic) {
+ canvas->drawRect(r, fill);
+ canvas->drawRect(r, stroke);
+ }
+ }
+}
+
+void Scene::animate(float t) {
+ for (const auto& anim : fAnimators) {
+ anim->tick(t);
+ }
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGText.cpp b/modules/sksg/src/SkSGText.cpp
new file mode 100644
index 0000000000..c149390023
--- /dev/null
+++ b/modules/sksg/src/SkSGText.cpp
@@ -0,0 +1,77 @@
+/*
+ * 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 "SkSGText.h"
+
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkPath.h"
+#include "SkTArray.h"
+#include "SkTextBlob.h"
+#include "SkTypeface.h"
+
+namespace sksg {
+
+sk_sp<Text> Text::Make(sk_sp<SkTypeface> tf, const SkString& text) {
+ return sk_sp<Text>(new Text(std::move(tf), text));
+}
+
+Text::Text(sk_sp<SkTypeface> tf, const SkString& text)
+ : fTypeface(std::move(tf))
+ , fText(text) {}
+
+Text::~Text() = default;
+
+SkRect Text::onRevalidate(InvalidationController*, const SkMatrix&) {
+ // TODO: we could potentially track invals which don't require rebuilding the blob.
+
+ SkPaint font;
+ font.setFlags(fFlags);
+ font.setTypeface(fTypeface);
+ font.setTextSize(fSize);
+ font.setTextScaleX(fScaleX);
+ font.setTextSkewX(fSkewX);
+ font.setTextAlign(fAlign);
+ font.setHinting(fHinting);
+
+ // First, convert to glyphIDs.
+ font.setTextEncoding(SkPaint::kUTF8_TextEncoding);
+ SkSTArray<256, SkGlyphID, true> glyphs;
+ glyphs.reset(font.textToGlyphs(fText.c_str(), fText.size(), nullptr));
+ SkAssertResult(font.textToGlyphs(fText.c_str(), fText.size(), glyphs.begin()) == glyphs.count());
+ font.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+
+ // Next, build the cached blob.
+ SkTextBlobBuilder builder;
+ const auto& buf = builder.allocRun(font, glyphs.count(), 0, 0, nullptr);
+ if (!buf.glyphs) {
+ fBlob.reset();
+ return SkRect::MakeEmpty();
+ }
+
+ memcpy(buf.glyphs, glyphs.begin(), glyphs.count() * sizeof(SkGlyphID));
+
+ fBlob = builder.make();
+ return fBlob
+ ? fBlob->bounds().makeOffset(fPosition.x(), fPosition.y())
+ : SkRect::MakeEmpty();
+}
+
+void Text::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
+ canvas->drawTextBlob(fBlob, fPosition.x(), fPosition.y(), paint);
+}
+
+SkPath Text::onAsPath() const {
+ // TODO
+ return SkPath();
+}
+
+void Text::onClip(SkCanvas* canvas, bool antiAlias) const {
+ canvas->clipPath(this->asPath(), antiAlias);
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGTransform.cpp b/modules/sksg/src/SkSGTransform.cpp
new file mode 100644
index 0000000000..6a985a971e
--- /dev/null
+++ b/modules/sksg/src/SkSGTransform.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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 "SkSGTransform.h"
+
+#include "SkCanvas.h"
+
+namespace sksg {
+// Matrix nodes don't generate damage on their own, but via aggregation ancestor Transform nodes.
+Matrix::Matrix(const SkMatrix& m, sk_sp<Matrix> parent)
+ : INHERITED(kBubbleDamage_Trait)
+ , fParent(std::move(parent))
+ , fLocalMatrix(m) {
+ if (fParent) {
+ this->observeInval(fParent);
+ }
+}
+
+Matrix::~Matrix() {
+ if (fParent) {
+ this->unobserveInval(fParent);
+ }
+}
+
+SkRect Matrix::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ fTotalMatrix = fLocalMatrix;
+
+ if (fParent) {
+ fParent->revalidate(ic, ctm);
+ fTotalMatrix.postConcat(fParent->getTotalMatrix());
+ }
+
+ return SkRect::MakeEmpty();
+}
+
+Transform::Transform(sk_sp<RenderNode> child, sk_sp<Matrix> matrix)
+ : INHERITED(std::move(child))
+ , fMatrix(std::move(matrix)) {
+ this->observeInval(fMatrix);
+}
+
+Transform::~Transform() {
+ this->unobserveInval(fMatrix);
+}
+
+void Transform::onRender(SkCanvas* canvas) const {
+ const auto& m = fMatrix->getTotalMatrix();
+ SkAutoCanvasRestore acr(canvas, !m.isIdentity());
+ canvas->concat(m);
+ this->INHERITED::onRender(canvas);
+}
+
+SkRect Transform::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->hasInval());
+
+ // We don't care about matrix reval results.
+ fMatrix->revalidate(ic, ctm);
+
+ const auto& m = fMatrix->getTotalMatrix();
+ auto bounds = this->INHERITED::onRevalidate(ic, SkMatrix::Concat(ctm, m));
+ m.mapRect(&bounds);
+
+ return bounds;
+}
+
+} // namespace sksg
diff --git a/modules/sksg/src/SkSGTrimEffect.cpp b/modules/sksg/src/SkSGTrimEffect.cpp
new file mode 100644
index 0000000000..b8c59bcfe8
--- /dev/null
+++ b/modules/sksg/src/SkSGTrimEffect.cpp
@@ -0,0 +1,56 @@
+/*
+ * 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 "SkSGTrimEffect.h"
+
+#include "SkCanvas.h"
+#include "SkStrokeRec.h"
+#include "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);
+}
+
+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;
+ }
+
+ return fTrimmedPath.computeTightBounds();
+}
+
+} // namespace sksg
diff --git a/modules/sksg/tests/SGTest.cpp b/modules/sksg/tests/SGTest.cpp
new file mode 100644
index 0000000000..1228e666e7
--- /dev/null
+++ b/modules/sksg/tests/SGTest.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkRect.h"
+#include "SkRectPriv.h"
+
+#if !defined(SK_BUILD_FOR_GOOGLE3)
+
+#include "SkSGColor.h"
+#include "SkSGDraw.h"
+#include "SkSGGroup.h"
+#include "SkSGInvalidationController.h"
+#include "SkSGRect.h"
+#include "SkSGTransform.h"
+
+#include "Test.h"
+
+#include <vector>
+
+static void check_inval(skiatest::Reporter* reporter, const sk_sp<sksg::Node>& root,
+ const SkRect& expected_bounds,
+ const SkRect& expected_inval_bounds,
+ const std::vector<SkRect>* expected_damage) {
+ sksg::InvalidationController ic;
+ const auto bbox = root->revalidate(&ic, SkMatrix::I());
+
+ if (0) {
+ SkDebugf("** bbox: [%f %f %f %f], ibbox: [%f %f %f %f]\n",
+ bbox.fLeft, bbox.fTop, bbox.fRight, bbox.fBottom,
+ ic.bounds().left(), ic.bounds().top(), ic.bounds().right(), ic.bounds().bottom());
+ }
+
+ REPORTER_ASSERT(reporter, bbox == expected_bounds);
+ REPORTER_ASSERT(reporter, ic.bounds() == expected_inval_bounds);
+
+ if (expected_damage) {
+ const auto damage_count = SkTo<size_t>(ic.end() - ic.begin());
+ REPORTER_ASSERT(reporter, expected_damage->size() == damage_count);
+ for (size_t i = 0; i < std::min(expected_damage->size(), damage_count); ++i) {
+ const auto r1 = (*expected_damage)[i],
+ r2 = ic.begin()[i];
+ if (0) {
+ SkDebugf("*** expected inval: [%f %f %f %f], actual: [%f %f %f %f]\n",
+ r1.left(), r1.top(), r1.right(), r1.bottom(),
+ r2.left(), r2.top(), r2.right(), r2.bottom());
+ }
+ REPORTER_ASSERT(reporter, r1 == r2);
+ }
+ }
+}
+
+static void inval_test1(skiatest::Reporter* reporter) {
+ auto color = sksg::Color::Make(0xff000000);
+ auto r1 = sksg::Rect::Make(SkRect::MakeWH(100, 100)),
+ r2 = sksg::Rect::Make(SkRect::MakeWH(100, 100));
+ auto grp = sksg::Group::Make();
+ auto matrix = sksg::Matrix::Make(SkMatrix::I());
+ auto root = sksg::Transform::Make(grp, matrix);
+
+ grp->addChild(sksg::Draw::Make(r1, color));
+ grp->addChild(sksg::Draw::Make(r2, color));
+
+ {
+ // Initial revalidation.
+ check_inval(reporter, root,
+ SkRect::MakeWH(100, 100),
+ SkRectPriv::MakeLargeS32(),
+ nullptr);
+ }
+
+ {
+ // Move r2 to (200 100).
+ r2->setL(200); r2->setT(100); r2->setR(300); r2->setB(200);
+ std::vector<SkRect> damage = { {0, 0, 100, 100}, { 200, 100, 300, 200} };
+ check_inval(reporter, root,
+ SkRect::MakeWH(300, 200),
+ SkRect::MakeWH(300, 200),
+ &damage);
+ }
+
+ {
+ // Update the common color.
+ color->setColor(0xffff0000);
+ std::vector<SkRect> damage = { {0, 0, 100, 100}, { 200, 100, 300, 200} };
+ check_inval(reporter, root,
+ SkRect::MakeWH(300, 200),
+ SkRect::MakeWH(300, 200),
+ &damage);
+ }
+
+ {
+ // Shrink r1.
+ r1->setR(50);
+ std::vector<SkRect> damage = { {0, 0, 100, 100}, { 0, 0, 50, 100} };
+ check_inval(reporter, root,
+ SkRect::MakeWH(300, 200),
+ SkRect::MakeWH(100, 100),
+ &damage);
+ }
+
+ {
+ // Update transform.
+ matrix->setMatrix(SkMatrix::MakeScale(2, 2));
+ std::vector<SkRect> damage = { {0, 0, 300, 200}, { 0, 0, 600, 400} };
+ check_inval(reporter, root,
+ SkRect::MakeWH(600, 400),
+ SkRect::MakeWH(600, 400),
+ &damage);
+ }
+
+ {
+ // Shrink r2 under transform.
+ r2->setR(250);
+ std::vector<SkRect> damage = { {400, 200, 600, 400}, { 400, 200, 500, 400} };
+ check_inval(reporter, root,
+ SkRect::MakeWH(500, 400),
+ SkRect::MakeLTRB(400, 200, 600, 400),
+ &damage);
+ }
+}
+
+static void inval_test2(skiatest::Reporter* reporter) {
+ auto color = sksg::Color::Make(0xff000000);
+ auto rect = sksg::Rect::Make(SkRect::MakeWH(100, 100));
+ auto m1 = sksg::Matrix::Make(SkMatrix::I()),
+ m2 = sksg::Matrix::Make(SkMatrix::I(), m1);
+ auto t1 = sksg::Transform::Make(sksg::Draw::Make(rect, color), m2),
+ t2 = sksg::Transform::Make(sksg::Draw::Make(rect, color), m1);
+ auto root = sksg::Group::Make();
+ root->addChild(t1);
+ root->addChild(t2);
+
+ {
+ // Initial revalidation.
+ check_inval(reporter, root,
+ SkRect::MakeWH(100, 100),
+ SkRectPriv::MakeLargeS32(),
+ nullptr);
+ }
+
+ {
+ // Update the shared color.
+ color->setColor(0xffff0000);
+ std::vector<SkRect> damage = { {0, 0, 100, 100}, { 0, 0, 100, 100} };
+ check_inval(reporter, root,
+ SkRect::MakeWH(100, 100),
+ SkRect::MakeWH(100, 100),
+ &damage);
+ }
+
+ {
+ // Update m2.
+ m2->setMatrix(SkMatrix::MakeScale(2, 2));
+ std::vector<SkRect> damage = { {0, 0, 100, 100}, { 0, 0, 200, 200} };
+ check_inval(reporter, root,
+ SkRect::MakeWH(200, 200),
+ SkRect::MakeWH(200, 200),
+ &damage);
+ }
+
+ {
+ // Update shared m1.
+ m1->setMatrix(SkMatrix::MakeTrans(100, 100));
+ std::vector<SkRect> damage = { { 0, 0, 200, 200}, // draw1 prev bounds
+ { 100, 100, 300, 300}, // draw1 new bounds
+ { 0, 0, 100, 100}, // draw2 prev bounds
+ { 100, 100, 200, 200} }; // draw2 new bounds
+ check_inval(reporter, root,
+ SkRect::MakeLTRB(100, 100, 300, 300),
+ SkRect::MakeLTRB( 0, 0, 300, 300),
+ &damage);
+ }
+
+ {
+ // Update shared rect.
+ rect->setR(50);
+ std::vector<SkRect> damage = { { 100, 100, 300, 300}, // draw1 prev bounds
+ { 100, 100, 200, 300}, // draw1 new bounds
+ { 100, 100, 200, 200}, // draw2 prev bounds
+ { 100, 100, 150, 200} }; // draw2 new bounds
+ check_inval(reporter, root,
+ SkRect::MakeLTRB(100, 100, 200, 300),
+ SkRect::MakeLTRB(100, 100, 300, 300),
+ &damage);
+ }
+}
+
+DEF_TEST(SGInvalidation, reporter) {
+ inval_test1(reporter);
+ inval_test2(reporter);
+}
+
+#endif // !defined(SK_BUILD_FOR_GOOGLE3)