aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Florin Malita <fmalita@chromium.org>2017-12-19 12:21:02 -0500
committerGravatar Skia Commit-Bot <skia-commit-bot@chromium.org>2017-12-21 20:56:32 +0000
commit4aa4441186b06565a597ec4a9baac5a972fddb51 (patch)
tree8ae7cb5e988f3b89dfea886e118931e78013ff63
parentf059e7ca181ad20fa94edea65c8bfd839eceab46 (diff)
Initial scene graph (SkSG)
Sketching a thin (as in close-to-skia-semantics) scene graph API, focused on external animation, inval tracking and minimal repaint. Only a few concrete classes/features so far: * Rect/Color/Transform/Group * basic inval tracking * a trivial animated sample with inval visualization Pretty much everything (especially naming) is volatile, so treat accordingly. The interesting bits to review are likely in Node.{h,cpp} for inval and SampleSGInval.cpp for usage. Initial class hierarchy: * Node: invalidation/ancestors tracking | -- * RenderNode: onRender(SkCanvas) | | | -- * Draw (concrete): rendering a [geometry, paint] tuple | | | -- * Group (concrete): grouping multiple RenderNodes | | | -- * EffectNode: single-descendant effect wrapper | | | -- * Transform (concrete): transform effect | -- * PaintNode: onMakePaint() | | | -- * Color (concrete): SkColor paint wrapper | -- * GeometryNode: onComputeBounds(), onDraw(SkCanvas, SkPaint) | -- * Rect (concrete): SkRect wrapper TBR= Change-Id: Iacf9b773c181a7582ecd31ee968562f179d1aa1b Reviewed-on: https://skia-review.googlesource.com/85502 Reviewed-by: Florin Malita <fmalita@chromium.org> Commit-Queue: Florin Malita <fmalita@chromium.org>
-rw-r--r--BUILD.gn30
-rw-r--r--experimental/sksg/SkSGDraw.cpp38
-rw-r--r--experimental/sksg/SkSGDraw.h48
-rw-r--r--experimental/sksg/SkSGEffectNode.cpp29
-rw-r--r--experimental/sksg/SkSGEffectNode.h38
-rw-r--r--experimental/sksg/SkSGGeometryNode.cpp48
-rw-r--r--experimental/sksg/SkSGGeometryNode.h51
-rw-r--r--experimental/sksg/SkSGGroup.cpp56
-rw-r--r--experimental/sksg/SkSGGroup.h44
-rw-r--r--experimental/sksg/SkSGInvalidationController.cpp20
-rw-r--r--experimental/sksg/SkSGInvalidationController.h40
-rw-r--r--experimental/sksg/SkSGNode.cpp117
-rw-r--r--experimental/sksg/SkSGNode.h86
-rw-r--r--experimental/sksg/SkSGPaintNode.cpp26
-rw-r--r--experimental/sksg/SkSGPaintNode.h43
-rw-r--r--experimental/sksg/SkSGRenderNode.cpp19
-rw-r--r--experimental/sksg/SkSGRenderNode.h36
-rw-r--r--experimental/sksg/effects/SkSGTransform.cpp29
-rw-r--r--experimental/sksg/effects/SkSGTransform.h43
-rw-r--r--experimental/sksg/geometry/SkSGRect.cpp25
-rw-r--r--experimental/sksg/geometry/SkSGRect.h46
-rw-r--r--experimental/sksg/paint/SkSGColor.cpp20
-rw-r--r--experimental/sksg/paint/SkSGColor.h37
-rw-r--r--gn/samples.gni1
-rw-r--r--samplecode/SampleSGInval.cpp90
25 files changed, 1060 insertions, 0 deletions
diff --git a/BUILD.gn b/BUILD.gn
index 3f1ce7fb8f..69bcb6835b 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1278,6 +1278,7 @@ if (skia_enable_tools) {
public_include_dirs = [ "gm" ]
sources = gm_sources
deps = [
+ ":experimental_sksg",
":flags",
":gpu_tool_utils",
":skia",
@@ -1293,6 +1294,7 @@ if (skia_enable_tools) {
sources -= [ "//tests/FontMgrAndroidParserTest.cpp" ]
}
deps = [
+ ":experimental_sksg",
":experimental_svg_model",
":flags",
":skia",
@@ -1350,6 +1352,31 @@ if (skia_enable_tools) {
]
}
+ test_lib("experimental_sksg") {
+ public_include_dirs = [
+ "experimental/sksg",
+ "experimental/sksg/effects",
+ "experimental/sksg/geometry",
+ "experimental/sksg/paint",
+ ]
+ sources = [
+ "experimental/sksg/SkSGDraw.cpp",
+ "experimental/sksg/SkSGEffectNode.cpp",
+ "experimental/sksg/SkSGGeometryNode.cpp",
+ "experimental/sksg/SkSGGroup.cpp",
+ "experimental/sksg/SkSGInvalidationController.cpp",
+ "experimental/sksg/SkSGNode.cpp",
+ "experimental/sksg/SkSGPaintNode.cpp",
+ "experimental/sksg/SkSGRenderNode.cpp",
+ "experimental/sksg/effects/SkSGTransform.cpp",
+ "experimental/sksg/geometry/SkSGRect.cpp",
+ "experimental/sksg/paint/SkSGColor.cpp",
+ ]
+ deps = [
+ ":skia",
+ ]
+ }
+
if (target_cpu != "wasm") {
test_lib("views") {
public_include_dirs = [ "include/views" ]
@@ -1432,6 +1459,7 @@ if (skia_enable_tools) {
"src/core/SkThreadedBMPDevice.h",
]
deps = [
+ ":experimental_sksg",
":experimental_svg_model",
":flags",
":gm",
@@ -1459,6 +1487,7 @@ if (skia_enable_tools) {
include_dirs = [ "tests" ]
deps = [
":common_flags",
+ ":experimental_sksg",
":experimental_svg_model",
":flags",
":gm",
@@ -1497,6 +1526,7 @@ if (skia_enable_tools) {
deps = [
":bench",
":common_flags",
+ ":experimental_sksg",
":experimental_svg_model",
":flags",
":gm",
diff --git a/experimental/sksg/SkSGDraw.cpp b/experimental/sksg/SkSGDraw.cpp
new file mode 100644
index 0000000000..7319c635fe
--- /dev/null
+++ b/experimental/sksg/SkSGDraw.cpp
@@ -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.
+ */
+
+#include "SkSGDraw.h"
+
+#include "SkSGGeometryNode.h"
+#include "SkSGPaintNode.h"
+
+namespace sksg {
+
+Draw::Draw(sk_sp<GeometryNode> geometry, sk_sp<PaintNode> paint)
+ : fGeometry(std::move(geometry))
+ , fPaint(std::move(paint)) {
+ fGeometry->addInvalReceiver(this);
+ fPaint->addInvalReceiver(this);
+}
+
+Draw::~Draw() {
+ fGeometry->removeInvalReceiver(this);
+ fPaint->removeInvalReceiver(this);
+}
+
+void Draw::onRender(SkCanvas* canvas) const {
+ fGeometry->draw(canvas, fPaint->makePaint());
+}
+
+void Draw::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->isInvalidated());
+
+ fGeometry->revalidate(ic, ctm);
+ fPaint->revalidate(ic, ctm);
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/SkSGDraw.h b/experimental/sksg/SkSGDraw.h
new file mode 100644
index 0000000000..f5146dee28
--- /dev/null
+++ b/experimental/sksg/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;
+
+ void 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/experimental/sksg/SkSGEffectNode.cpp b/experimental/sksg/SkSGEffectNode.cpp
new file mode 100644
index 0000000000..c09bcf0955
--- /dev/null
+++ b/experimental/sksg/SkSGEffectNode.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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)) {
+ fChild->addInvalReceiver(this);
+}
+
+EffectNode::~EffectNode() {
+ fChild->removeInvalReceiver(this);
+}
+
+void EffectNode::onRender(SkCanvas* canvas) const {
+ fChild->render(canvas);
+}
+
+void EffectNode::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ fChild->revalidate(ic, ctm);
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/SkSGEffectNode.h b/experimental/sksg/SkSGEffectNode.h
new file mode 100644
index 0000000000..b9a44a6cad
--- /dev/null
+++ b/experimental/sksg/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;
+
+ void onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ sk_sp<RenderNode> fChild;
+
+ typedef RenderNode INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGEffectNode_DEFINED
diff --git a/experimental/sksg/SkSGGeometryNode.cpp b/experimental/sksg/SkSGGeometryNode.cpp
new file mode 100644
index 0000000000..ea14ec0255
--- /dev/null
+++ b/experimental/sksg/SkSGGeometryNode.cpp
@@ -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.
+ */
+
+#include "SkSGGeometryNode.h"
+
+#include "SkMatrix.h"
+#include "SkSGInvalidationController.h"
+
+namespace sksg {
+
+GeometryNode::GeometryNode()
+ : fBounds(SkRect::MakeLTRB(SK_ScalarMin, SK_ScalarMin, SK_ScalarMax, SK_ScalarMax)) {}
+
+void GeometryNode::draw(SkCanvas* canvas, const SkPaint& paint) const {
+ SkASSERT(!this->isInvalidated());
+ this->onDraw(canvas, paint);
+}
+
+static void inval_rect(const SkRect& r, const SkMatrix& ctm, InvalidationController* ic) {
+ if (ctm.isIdentity()) {
+ ic->inval(r);
+ return;
+ }
+
+ SkRect mappedRect;
+ if (!ctm.mapRect(&mappedRect, r)) {
+ mappedRect = SkRect::MakeLTRB(SK_ScalarMin, SK_ScalarMin, SK_ScalarMax, SK_ScalarMax);
+ }
+ ic->inval(mappedRect);
+}
+
+void GeometryNode::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ SkASSERT(this->isInvalidated());
+
+ const auto oldBounds = fBounds;
+ fBounds = this->onComputeBounds();
+
+ inval_rect(oldBounds, ctm, ic);
+ if (fBounds != oldBounds) {
+ inval_rect(fBounds, ctm, ic);
+ }
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/SkSGGeometryNode.h b/experimental/sksg/SkSGGeometryNode.h
new file mode 100644
index 0000000000..52b2df0367
--- /dev/null
+++ b/experimental/sksg/SkSGGeometryNode.h
@@ -0,0 +1,51 @@
+/*
+ * 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"
+
+#include "SkRect.h"
+
+class SkCanvas;
+class SkPaint;
+
+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 draw(SkCanvas*, const SkPaint&) const;
+
+ // SkPath asPath() const; // unused for now
+
+protected:
+ GeometryNode();
+
+ virtual void onDraw(SkCanvas*, const SkPaint&) const = 0;
+
+ virtual SkRect onComputeBounds() const = 0;
+
+ // virtual SkPath onAsPath() const = 0; // unused for now
+
+ void onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ SkRect fBounds;
+
+ typedef Node INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGGeometryNode_DEFINED
diff --git a/experimental/sksg/SkSGGroup.cpp b/experimental/sksg/SkSGGroup.cpp
new file mode 100644
index 0000000000..b8e28f7286
--- /dev/null
+++ b/experimental/sksg/SkSGGroup.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 "SkSGGroup.h"
+
+namespace sksg {
+
+Group::Group() {}
+
+Group::~Group() {
+ for (const auto& child : fChildren) {
+ child->removeInvalReceiver(this);
+ }
+}
+
+void Group::addChild(sk_sp<RenderNode> node) {
+ // should we allow duplicates?
+ for (const auto& child : fChildren) {
+ if (child == node) {
+ return;
+ }
+ }
+
+ node->addInvalReceiver(this);
+ fChildren.push_back(std::move(node));
+}
+
+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);
+ node->removeInvalReceiver(this);
+ break;
+ }
+ }
+ SkASSERT(fChildren.count() == origCount - 1);
+}
+
+void Group::onRender(SkCanvas* canvas) const {
+ for (const auto& child : fChildren) {
+ child->render(canvas);
+ }
+}
+
+void Group::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ for (const auto& child : fChildren) {
+ child->revalidate(ic, ctm);
+ }
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/SkSGGroup.h b/experimental/sksg/SkSGGroup.h
new file mode 100644
index 0000000000..2ed9d0ecb5
--- /dev/null
+++ b/experimental/sksg/SkSGGroup.h
@@ -0,0 +1,44 @@
+/*
+ * 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>&);
+
+protected:
+ Group();
+ ~Group() override;
+
+ void onRender(SkCanvas*) const override;
+ void onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ SkTArray<sk_sp<RenderNode>, true> fChildren;
+
+ typedef RenderNode INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGGroup_DEFINED
diff --git a/experimental/sksg/SkSGInvalidationController.cpp b/experimental/sksg/SkSGInvalidationController.cpp
new file mode 100644
index 0000000000..9693f1e754
--- /dev/null
+++ b/experimental/sksg/SkSGInvalidationController.cpp
@@ -0,0 +1,20 @@
+/*
+ * 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"
+
+namespace sksg {
+
+InvalidationController::InvalidationController() {}
+
+void InvalidationController::inval(const SkRect& r) {
+ fRects.push(r);
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/SkSGInvalidationController.h b/experimental/sksg/SkSGInvalidationController.h
new file mode 100644
index 0000000000..e00ece52fc
--- /dev/null
+++ b/experimental/sksg/SkSGInvalidationController.h
@@ -0,0 +1,40 @@
+/*
+ * 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 "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 SkRect* begin() const { return fRects.begin(); }
+ const SkRect* end() const { return fRects.end(); }
+
+private:
+ SkTDArray<SkRect> fRects;
+
+ typedef SkNoncopyable INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGInvalidationController_DEFINED
diff --git a/experimental/sksg/SkSGNode.cpp b/experimental/sksg/SkSGNode.cpp
new file mode 100644
index 0000000000..3b404edd50
--- /dev/null
+++ b/experimental/sksg/SkSGNode.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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 "SkSGNode.h"
+
+namespace sksg {
+
+class Node::ScopedFlag {
+public:
+ ScopedFlag(Node* node, uint32_t flag)
+ : fNode(node)
+ , fFlag(flag) {
+ SkASSERT(!(fNode->fFlags & fFlag));
+ fNode->fFlags |= fFlag;
+ }
+ ~ScopedFlag() {
+ fNode->fFlags &= ~fFlag;;
+ }
+
+private:
+ Node* fNode;
+ uint32_t fFlag;
+};
+
+#define TRAVERSAL_GUARD \
+ if (this->fFlags & kInTraversal_Flag) { \
+ return; \
+ } \
+ ScopedFlag traversal_guard(this, kInTraversal_Flag);
+
+Node::Node()
+ : fInvalReceiver(nullptr)
+ , fFlags(kInvalidated_Flag) {}
+
+Node::~Node() {
+ if (fFlags & kReceiverArray_Flag) {
+ SkASSERT(fInvalReceiverArray->isEmpty());
+ delete fInvalReceiverArray;
+ } else {
+ SkASSERT(!fInvalReceiver);
+ }
+}
+
+void Node::addInvalReceiver(Node* receiver) {
+ if (!(fFlags & kReceiverArray_Flag)) {
+ if (!fInvalReceiver) {
+ fInvalReceiver = receiver;
+ return;
+ }
+
+ auto receivers = new SkTDArray<Node*>();
+ receivers->setReserve(2);
+ receivers->push(fInvalReceiver);
+
+ fInvalReceiverArray = receivers;
+ fFlags |= kReceiverArray_Flag;
+ }
+
+ // No duplicate receivers.
+ SkASSERT(fInvalReceiverArray->find(receiver) < 0);
+
+ fInvalReceiverArray->push(receiver);
+}
+
+void Node::removeInvalReceiver(Node* receiver) {
+ if (!(fFlags & kReceiverArray_Flag)) {
+ SkASSERT(fInvalReceiver == receiver);
+ fInvalReceiver = nullptr;
+ return;
+ }
+
+ const auto idx = fInvalReceiverArray->find(receiver);
+ SkASSERT(idx >= 0);
+ fInvalReceiverArray->remove(idx);
+}
+
+template <typename Func>
+void Node::forEachInvalReceiver(Func&& func) const {
+ if (fFlags & kReceiverArray_Flag) {
+ for (const auto& parent : *fInvalReceiverArray) {
+ func(parent);
+ }
+ return;
+ }
+
+ if (fInvalReceiver) {
+ func(fInvalReceiver);
+ }
+}
+
+void Node::invalidate() {
+ TRAVERSAL_GUARD
+
+ if (this->isInvalidated()) {
+ return;
+ }
+
+ fFlags |= kInvalidated_Flag;
+ forEachInvalReceiver([&](Node* receiver) {
+ receiver->invalidate();
+ });
+}
+
+void Node::revalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ TRAVERSAL_GUARD
+
+ if (this->isInvalidated()) {
+ this->onRevalidate(ic, ctm);
+ fFlags &= ~kInvalidated_Flag;
+ }
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/SkSGNode.h b/experimental/sksg/SkSGNode.h
new file mode 100644
index 0000000000..08ee784a1e
--- /dev/null
+++ b/experimental/sksg/SkSGNode.h
@@ -0,0 +1,86 @@
+/*
+ * 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 "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 connected/invalidated nodes.
+ void revalidate(InvalidationController*, const SkMatrix&);
+
+protected:
+ Node();
+ ~Node() override;
+
+ // Mark this node and (transitively) any invalidation receivers for revalidation.
+ void invalidate();
+
+ bool isInvalidated() const { return fFlags & kInvalidated_Flag; }
+
+ // Dispatched on revalidation. Subclasses are expected to recompute their geometry
+ // and push dirty rects to the InvalidationController.
+ virtual void onRevalidate(InvalidationController*, const SkMatrix& ctm) = 0;
+
+private:
+ void addInvalReceiver(Node*);
+ void removeInvalReceiver(Node*);
+ friend class Draw;
+ friend class EffectNode;
+ friend class Group;
+
+ template <typename Func>
+ void forEachInvalReceiver(Func&&) const;
+
+ enum Flags {
+ kInvalidated_Flag = 1 << 0, // the node requires revalidation
+ kReceiverArray_Flag = 1 << 1, // the node has more than one inval receiver
+ kInTraversal_Flag = 1 << 2, // the node is part of a traversal (cycle detection)
+ };
+
+ class ScopedFlag;
+
+ union {
+ Node* fInvalReceiver;
+ SkTDArray<Node*>* fInvalReceiverArray;
+ };
+ uint32_t fFlags;
+
+ typedef SkRefCnt INHERITED;
+};
+
+// Helper for defining attribute getters/setters in subclasses.
+#define SG_ATTRIBUTE(attr_name, attr_type, attr_container) \
+ attr_type get##attr_name() const { return attr_container; } \
+ void set##attr_name(attr_type v) { \
+ if (attr_container == v) return; \
+ attr_container = v; \
+ this->invalidate(); \
+ }
+
+} // namespace sksg
+
+#endif // SkSGNode_DEFINED
diff --git a/experimental/sksg/SkSGPaintNode.cpp b/experimental/sksg/SkSGPaintNode.cpp
new file mode 100644
index 0000000000..cd0bc81df5
--- /dev/null
+++ b/experimental/sksg/SkSGPaintNode.cpp
@@ -0,0 +1,26 @@
+/*
+ * 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 {
+
+PaintNode::PaintNode() {}
+
+const SkPaint& PaintNode::makePaint() {
+ SkASSERT(!this->isInvalidated());
+
+ return fPaint;
+}
+
+void PaintNode::onRevalidate(InvalidationController*, const SkMatrix&) {
+ SkASSERT(this->isInvalidated());
+
+ fPaint = this->onMakePaint();
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/SkSGPaintNode.h b/experimental/sksg/SkSGPaintNode.h
new file mode 100644
index 0000000000..d0a7787bc8
--- /dev/null
+++ b/experimental/sksg/SkSGPaintNode.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 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();
+
+protected:
+ PaintNode();
+
+ virtual SkPaint onMakePaint() const = 0;
+
+ void onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ SkPaint fPaint;
+
+ typedef Node INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGGeometryNode_DEFINED
diff --git a/experimental/sksg/SkSGRenderNode.cpp b/experimental/sksg/SkSGRenderNode.cpp
new file mode 100644
index 0000000000..1609bac724
--- /dev/null
+++ b/experimental/sksg/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() {}
+
+void RenderNode::render(SkCanvas* canvas) const {
+ SkASSERT(!this->isInvalidated());
+ this->onRender(canvas);
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/SkSGRenderNode.h b/experimental/sksg/SkSGRenderNode.h
new file mode 100644
index 0000000000..4ca1aec616
--- /dev/null
+++ b/experimental/sksg/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/experimental/sksg/effects/SkSGTransform.cpp b/experimental/sksg/effects/SkSGTransform.cpp
new file mode 100644
index 0000000000..ce83599871
--- /dev/null
+++ b/experimental/sksg/effects/SkSGTransform.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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 {
+
+Transform::Transform(sk_sp<RenderNode> child, const SkMatrix& matrix)
+ : INHERITED(std::move(child))
+ , fMatrix(matrix) {}
+
+void Transform::onRender(SkCanvas* canvas) const {
+ SkAutoCanvasRestore acr(canvas, !fMatrix.isIdentity());
+ canvas->concat(fMatrix);
+ this->INHERITED::onRender(canvas);
+}
+
+void Transform::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
+ const auto localCTM = SkMatrix::Concat(ctm, fMatrix);
+ this->INHERITED::onRevalidate(ic, localCTM);
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/effects/SkSGTransform.h b/experimental/sksg/effects/SkSGTransform.h
new file mode 100644
index 0000000000..d7bc5d4fc8
--- /dev/null
+++ b/experimental/sksg/effects/SkSGTransform.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 SkSGTransform_DEFINED
+#define SkSGTransform_DEFINED
+
+#include "SkSGEffectNode.h"
+
+#include "SkMatrix.h"
+
+namespace sksg {
+
+/**
+ * Concrete Effect node, wrapping an SkMatrix.
+ */
+class Transform : public EffectNode {
+public:
+ static sk_sp<Transform> Make(sk_sp<RenderNode> child, const SkMatrix& matrix) {
+ return sk_sp<Transform>(new Transform(std::move(child), matrix));
+ }
+
+ SG_ATTRIBUTE(Matrix, SkMatrix, fMatrix)
+
+protected:
+ Transform(sk_sp<RenderNode>, const SkMatrix&);
+
+ void onRender(SkCanvas*) const override;
+
+ void onRevalidate(InvalidationController*, const SkMatrix&) override;
+
+private:
+ SkMatrix fMatrix;
+
+ typedef EffectNode INHERITED;
+};
+
+} // namespace sksg
+
+#endif // SkSGTransform_DEFINED
diff --git a/experimental/sksg/geometry/SkSGRect.cpp b/experimental/sksg/geometry/SkSGRect.cpp
new file mode 100644
index 0000000000..69bd94681b
--- /dev/null
+++ b/experimental/sksg/geometry/SkSGRect.cpp
@@ -0,0 +1,25 @@
+/*
+ * 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"
+
+namespace sksg {
+
+Rect::Rect(const SkRect& rect) : fRect(rect) {}
+
+void Rect::onDraw(SkCanvas* canvas, const SkPaint& paint) const {
+ canvas->drawRect(fRect, paint);
+}
+
+SkRect Rect::onComputeBounds() const {
+ return fRect;
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/geometry/SkSGRect.h b/experimental/sksg/geometry/SkSGRect.h
new file mode 100644
index 0000000000..a0e5ec62e8
--- /dev/null
+++ b/experimental/sksg/geometry/SkSGRect.h
@@ -0,0 +1,46 @@
+/*
+ * 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"
+
+class SkCanvas;
+class SkPaint;
+
+namespace sksg {
+
+/**
+ * Concrete Geometry node, wrapping an SkRect.
+ */
+class Rect : 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 onDraw(SkCanvas*, const SkPaint&) const override;
+
+ SkRect onComputeBounds() const override;
+
+private:
+ explicit Rect(const SkRect&);
+
+ SkRect fRect;
+};
+
+} // namespace sksg
+
+#endif // SkSGRect_DEFINED
diff --git a/experimental/sksg/paint/SkSGColor.cpp b/experimental/sksg/paint/SkSGColor.cpp
new file mode 100644
index 0000000000..826bc4bc7c
--- /dev/null
+++ b/experimental/sksg/paint/SkSGColor.cpp
@@ -0,0 +1,20 @@
+/*
+ * 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) {}
+
+SkPaint Color::onMakePaint() const {
+ SkPaint paint;
+ paint.setColor(fColor);
+ return paint;
+}
+
+} // namespace sksg
diff --git a/experimental/sksg/paint/SkSGColor.h b/experimental/sksg/paint/SkSGColor.h
new file mode 100644
index 0000000000..a4e0862892
--- /dev/null
+++ b/experimental/sksg/paint/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:
+ SkPaint onMakePaint() const override;
+
+private:
+ explicit Color(SkColor);
+
+ SkColor fColor;
+};
+
+} // namespace sksg
+
+#endif // SkSGColor_DEFINED
diff --git a/gn/samples.gni b/gn/samples.gni
index 6bef4af8fb..fe2ab4eba7 100644
--- a/gn/samples.gni
+++ b/gn/samples.gni
@@ -87,6 +87,7 @@ samples_sources = [
"$_samplecode/SampleStrokePath.cpp",
"$_samplecode/SampleStrokeRect.cpp",
"$_samplecode/SampleSubpixelTranslate.cpp",
+ "$_samplecode/SampleSGInval.cpp",
"$_samplecode/SampleSVGFile.cpp",
"$_samplecode/SampleSVGPong.cpp",
"$_samplecode/SampleText.cpp",
diff --git a/samplecode/SampleSGInval.cpp b/samplecode/SampleSGInval.cpp
new file mode 100644
index 0000000000..08bfd64377
--- /dev/null
+++ b/samplecode/SampleSGInval.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 "SampleCode.h"
+#include "SkCanvas.h"
+#include "SkSGColor.h"
+#include "SkSGDraw.h"
+#include "SkSGGroup.h"
+#include "SkSGInvalidationController.h"
+#include "SkSGRect.h"
+#include "SkSGTransform.h"
+#include "SkAnimTimer.h"
+
+#include <cmath>
+
+class SGInvalView final : public SampleView {
+public:
+ SGInvalView() {}
+
+protected:
+ void onOnceBeforeDraw() override {
+ fRect1 = sksg::Rect::Make(SkRect::MakeLTRB(100, 100, 100, 100));
+ fRect2 = sksg::Rect::Make(SkRect::MakeLTRB(300, 200, 300, 200));
+ fColor1 = sksg::Color::Make(0);
+ fColor2 = sksg::Color::Make(0);
+
+ fRoot = sksg::Group::Make();
+ fRoot->addChild(sksg::Draw::Make(fRect1, fColor1));
+ fRoot->addChild(sksg::Transform::Make(sksg::Draw::Make(fRect2, fColor2),
+ SkMatrix::MakeScale(1.5f, 1.5f)));
+ }
+
+ bool onQuery(SkEvent* evt) override {
+ if (SampleCode::TitleQ(*evt)) {
+ SampleCode::TitleR(evt, "SGInval");
+ return true;
+ }
+
+ return this->INHERITED::onQuery(evt);
+ }
+
+ void onDrawContent(SkCanvas* canvas) override {
+ sksg::InvalidationController ic;
+ fRoot->revalidate(&ic, SkMatrix::I());
+
+ // TODO: clip/cull
+ fRoot->render(canvas);
+
+ SkPaint p;
+ p.setColor(0xffff0000);
+ p.setStyle(SkPaint::kStroke_Style);
+ p.setAntiAlias(true);
+ p.setStrokeWidth(0);
+
+ for (const auto& r : ic) {
+ canvas->drawRect(r, p);
+ }
+ }
+
+ bool onAnimate(const SkAnimTimer& timer) override {
+ static constexpr SkScalar kSize = 50;
+ static constexpr SkScalar kRate = 1.0f / 500;
+ const auto t = timer.msec() * kRate;
+
+ fRect1->setR(fRect1->getL() + kSize * (1 + std::sin(t)));
+ fRect1->setB(fRect1->getT() + kSize * (1 + std::cos(t)));
+ fRect2->setR(fRect2->getL() + kSize * (1 + std::cos(SK_ScalarPI / 2 + t)));
+ fRect2->setB(fRect2->getT() + kSize * (1 + std::sin(SK_ScalarPI / 2 + t)));
+
+ fColor1->setColor(SkColorSetARGB(128 * (1 + std::sin(t)), 0, 0x80, 0));
+ fColor2->setColor(SkColorSetARGB(128 * (1 + std::cos(t)), 0, 0, 0x80));
+ return true;
+ }
+
+private:
+ typedef SampleView INHERITED;
+
+ sk_sp<sksg::Rect> fRect1,
+ fRect2;
+ sk_sp<sksg::Color> fColor1,
+ fColor2;
+ sk_sp<sksg::Group> fRoot;
+};
+
+static SkView* SGInvalFactory() { return new SGInvalView; }
+static SkViewRegister reg(SGInvalFactory);