From 3b526b05d652ad6c310d9c636187b20b51c7648c Mon Sep 17 00:00:00 2001 From: Florin Malita Date: Fri, 25 May 2018 12:43:51 -0400 Subject: "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 Reviewed-by: Mike Klein --- modules/sksg/BUILD.gn | 90 +++++++ modules/sksg/include/SkSGClipEffect.h | 50 ++++ modules/sksg/include/SkSGColor.h | 37 +++ modules/sksg/include/SkSGDraw.h | 48 ++++ modules/sksg/include/SkSGEffectNode.h | 38 +++ modules/sksg/include/SkSGGeometryNode.h | 49 ++++ modules/sksg/include/SkSGGeometryTransform.h | 58 +++++ modules/sksg/include/SkSGGradient.h | 105 ++++++++ modules/sksg/include/SkSGGroup.h | 47 ++++ modules/sksg/include/SkSGImage.h | 49 ++++ modules/sksg/include/SkSGInvalidationController.h | 43 ++++ modules/sksg/include/SkSGMaskEffect.h | 51 ++++ modules/sksg/include/SkSGMerge.h | 64 +++++ modules/sksg/include/SkSGNode.h | 107 ++++++++ modules/sksg/include/SkSGOpacityEffect.h | 42 +++ modules/sksg/include/SkSGPaintNode.h | 60 +++++ modules/sksg/include/SkSGPath.h | 48 ++++ modules/sksg/include/SkSGPlane.h | 40 +++ modules/sksg/include/SkSGRect.h | 76 ++++++ modules/sksg/include/SkSGRenderNode.h | 36 +++ modules/sksg/include/SkSGRoundEffect.h | 50 ++++ modules/sksg/include/SkSGScene.h | 85 ++++++ modules/sksg/include/SkSGText.h | 69 +++++ modules/sksg/include/SkSGTransform.h | 81 ++++++ modules/sksg/include/SkSGTrimEffect.h | 58 +++++ modules/sksg/samples/SampleSVGPong.cpp | 299 ++++++++++++++++++++++ modules/sksg/src/SkSGClipEffect.cpp | 50 ++++ modules/sksg/src/SkSGColor.cpp | 18 ++ modules/sksg/src/SkSGDraw.cpp | 50 ++++ modules/sksg/src/SkSGEffectNode.cpp | 31 +++ modules/sksg/src/SkSGGeometryNode.cpp | 32 +++ modules/sksg/src/SkSGGeometryTransform.cpp | 53 ++++ modules/sksg/src/SkSGGradient.cpp | 60 +++++ modules/sksg/src/SkSGGroup.cpp | 66 +++++ modules/sksg/src/SkSGImage.cpp | 29 +++ modules/sksg/src/SkSGInvalidationController.cpp | 32 +++ modules/sksg/src/SkSGMaskEffect.cpp | 54 ++++ modules/sksg/src/SkSGMerge.cpp | 84 ++++++ modules/sksg/src/SkSGNode.cpp | 152 +++++++++++ modules/sksg/src/SkSGOpacityEffect.cpp | 42 +++ modules/sksg/src/SkSGPaintNode.cpp | 41 +++ modules/sksg/src/SkSGPath.cpp | 41 +++ modules/sksg/src/SkSGPlane.cpp | 36 +++ modules/sksg/src/SkSGRect.cpp | 60 +++++ modules/sksg/src/SkSGRenderNode.cpp | 19 ++ modules/sksg/src/SkSGRoundEffect.cpp | 56 ++++ modules/sksg/src/SkSGScene.cpp | 70 +++++ modules/sksg/src/SkSGText.cpp | 77 ++++++ modules/sksg/src/SkSGTransform.cpp | 70 +++++ modules/sksg/src/SkSGTrimEffect.cpp | 56 ++++ modules/sksg/tests/SGTest.cpp | 197 ++++++++++++++ 51 files changed, 3256 insertions(+) create mode 100644 modules/sksg/BUILD.gn create mode 100644 modules/sksg/include/SkSGClipEffect.h create mode 100644 modules/sksg/include/SkSGColor.h create mode 100644 modules/sksg/include/SkSGDraw.h create mode 100644 modules/sksg/include/SkSGEffectNode.h create mode 100644 modules/sksg/include/SkSGGeometryNode.h create mode 100644 modules/sksg/include/SkSGGeometryTransform.h create mode 100644 modules/sksg/include/SkSGGradient.h create mode 100644 modules/sksg/include/SkSGGroup.h create mode 100644 modules/sksg/include/SkSGImage.h create mode 100644 modules/sksg/include/SkSGInvalidationController.h create mode 100644 modules/sksg/include/SkSGMaskEffect.h create mode 100644 modules/sksg/include/SkSGMerge.h create mode 100644 modules/sksg/include/SkSGNode.h create mode 100644 modules/sksg/include/SkSGOpacityEffect.h create mode 100644 modules/sksg/include/SkSGPaintNode.h create mode 100644 modules/sksg/include/SkSGPath.h create mode 100644 modules/sksg/include/SkSGPlane.h create mode 100644 modules/sksg/include/SkSGRect.h create mode 100644 modules/sksg/include/SkSGRenderNode.h create mode 100644 modules/sksg/include/SkSGRoundEffect.h create mode 100644 modules/sksg/include/SkSGScene.h create mode 100644 modules/sksg/include/SkSGText.h create mode 100644 modules/sksg/include/SkSGTransform.h create mode 100644 modules/sksg/include/SkSGTrimEffect.h create mode 100644 modules/sksg/samples/SampleSVGPong.cpp create mode 100644 modules/sksg/src/SkSGClipEffect.cpp create mode 100644 modules/sksg/src/SkSGColor.cpp create mode 100644 modules/sksg/src/SkSGDraw.cpp create mode 100644 modules/sksg/src/SkSGEffectNode.cpp create mode 100644 modules/sksg/src/SkSGGeometryNode.cpp create mode 100644 modules/sksg/src/SkSGGeometryTransform.cpp create mode 100644 modules/sksg/src/SkSGGradient.cpp create mode 100644 modules/sksg/src/SkSGGroup.cpp create mode 100644 modules/sksg/src/SkSGImage.cpp create mode 100644 modules/sksg/src/SkSGInvalidationController.cpp create mode 100644 modules/sksg/src/SkSGMaskEffect.cpp create mode 100644 modules/sksg/src/SkSGMerge.cpp create mode 100644 modules/sksg/src/SkSGNode.cpp create mode 100644 modules/sksg/src/SkSGOpacityEffect.cpp create mode 100644 modules/sksg/src/SkSGPaintNode.cpp create mode 100644 modules/sksg/src/SkSGPath.cpp create mode 100644 modules/sksg/src/SkSGPlane.cpp create mode 100644 modules/sksg/src/SkSGRect.cpp create mode 100644 modules/sksg/src/SkSGRenderNode.cpp create mode 100644 modules/sksg/src/SkSGRoundEffect.cpp create mode 100644 modules/sksg/src/SkSGScene.cpp create mode 100644 modules/sksg/src/SkSGText.cpp create mode 100644 modules/sksg/src/SkSGTransform.cpp create mode 100644 modules/sksg/src/SkSGTrimEffect.cpp create mode 100644 modules/sksg/tests/SGTest.cpp (limited to 'modules/sksg') 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 Make(sk_sp child, sk_sp clip, + bool aa = false) { + return (child && clip) + ? sk_sp(new ClipEffect(std::move(child), std::move(clip), aa)) + : nullptr; + } + + ~ClipEffect() override; + +protected: + ClipEffect(sk_sp, sk_sp, bool aa); + + void onRender(SkCanvas*) const override; + + SkRect onRevalidate(InvalidationController*, const SkMatrix&) override; + +private: + const sk_sp 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 Make(SkColor c) { return sk_sp(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 Make(sk_sp geo, sk_sp paint) { + return (geo && paint) ? sk_sp(new Draw(std::move(geo), std::move(paint))) : nullptr; + } + +protected: + Draw(sk_sp, sk_sp paint); + ~Draw() override; + + void onRender(SkCanvas*) const override; + + SkRect onRevalidate(InvalidationController*, const SkMatrix&) override; + +private: + sk_sp fGeometry; + sk_sp 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); + ~EffectNode() override; + + void onRender(SkCanvas*) const override; + + SkRect onRevalidate(InvalidationController*, const SkMatrix&) override; + +private: + sk_sp 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 Make(sk_sp child, sk_sp matrix) { + return child && matrix + ? sk_sp(new GeometryTransform(std::move(child), std::move(matrix))) + : nullptr; + } + + static sk_sp Make(sk_sp child, const SkMatrix& m) { + return Make(std::move(child), Matrix::Make(m)); + } + + ~GeometryTransform() override; + + const sk_sp& 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, sk_sp); + + const sk_sp fChild; + const sk_sp 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 + +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, fColorStops) + SG_ATTRIBUTE(TileMode , SkShader::TileMode , fTileMode ) + +protected: + void onApplyToPaint(SkPaint*) const final; + + virtual sk_sp onMakeShader(const std::vector& colors, + const std::vector& positions) const = 0; + +protected: + Gradient() = default; + +private: + std::vector fColorStops; + SkShader::TileMode fTileMode = SkShader::kClamp_TileMode; + + using INHERITED = PaintNode; +}; + +class LinearGradient final : public Gradient { +public: + static sk_sp Make() { + return sk_sp(new LinearGradient()); + } + + SG_ATTRIBUTE(StartPoint, SkPoint, fStartPoint) + SG_ATTRIBUTE(EndPoint , SkPoint, fEndPoint ) + +protected: + sk_sp onMakeShader(const std::vector& colors, + const std::vector& 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 Make() { + return sk_sp(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 onMakeShader(const std::vector& colors, + const std::vector& 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 Make() { + return sk_sp(new Group()); + } + + void addChild(sk_sp); + void removeChild(const sk_sp&); + + size_t size() const { return SkTo(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, 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 Make(sk_sp image) { + return image ? sk_sp(new Image(std::move(image))) : nullptr; + } + + SG_ATTRIBUTE(Quality , SkFilterQuality, fQuality ) + SG_ATTRIBUTE(AntiAlias, bool , fAntiAlias) + +protected: + explicit Image(sk_sp); + + void onRender(SkCanvas*) const override; + + SkRect onRevalidate(InvalidationController*, const SkMatrix&) override; + +private: + const sk_sp 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 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 Make(sk_sp child, sk_sp mask, + Mode mode = Mode::kNormal) { + return (child && mask) + ? sk_sp(new MaskEffect(std::move(child), std::move(mask), mode)) + : nullptr; + } + + ~MaskEffect() override; + +protected: + MaskEffect(sk_sp, sk_sp mask, Mode); + + void onRender(SkCanvas*) const override; + + SkRect onRevalidate(InvalidationController*, const SkMatrix&) override; + +private: + const sk_sp 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 + +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 Make(std::vector>&& geos, Mode mode) { + return sk_sp(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>&& geos, Mode); + + std::vector> 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&); + void unobserveInval(const sk_sp&); + +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 + void forEachInvalObserver(Func&&) const; + + class ScopedFlag; + + union { + Node* fInvalObserver; + SkTDArray* 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 Make(sk_sp child, float opacity = 1) { + return child ? sk_sp(new OpacityEffect(std::move(child), opacity)) : nullptr; + } + + SG_ATTRIBUTE(Opacity, float, fOpacity) + +protected: + OpacityEffect(sk_sp, 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 Make() { return sk_sp(new Path(SkPath())); } + static sk_sp Make(const SkPath& r) { return sk_sp(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 Make() { return sk_sp(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 Make() { return sk_sp(new Rect(SkRect::MakeEmpty())); } + static sk_sp Make(const SkRect& r) { return sk_sp(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 Make() { return sk_sp(new RRect(SkRRect())); } + static sk_sp Make(const SkRRect& rr) { return sk_sp(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 Make(sk_sp child) { + return child ? sk_sp(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); + + const sk_sp 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 +#include + +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>; + +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 Make(sk_sp root, AnimatorList&& animators); + ~Scene(); + + void render(SkCanvas*) const; + void animate(float t); + + void setShowInval(bool show) { fShowInval = show; } + +private: + Scene(sk_sp root, AnimatorList&& animators); + + const sk_sp 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 Make(sk_sp 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, const SkString&); + + const sk_sp 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 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 Make(const SkMatrix& m, sk_sp parent = nullptr) { + return sk_sp(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); + + SkRect onRevalidate(InvalidationController*, const SkMatrix&) override; + +private: + sk_sp 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 Make(sk_sp child, sk_sp matrix) { + return child && matrix + ? sk_sp(new Transform(std::move(child), std::move(matrix))) + : nullptr; + } + + static sk_sp Make(sk_sp child, const SkMatrix& m) { + return Make(std::move(child), Matrix::Make(m)); + } + + ~Transform() override; + + const sk_sp& getMatrix() const { return fMatrix; } + +protected: + void onRender(SkCanvas*) const override; + + SkRect onRevalidate(InvalidationController*, const SkMatrix&) override; + +private: + Transform(sk_sp, sk_sp); + + const sk_sp 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 Make(sk_sp child) { + return child ? sk_sp(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); + + const sk_sp 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 for the trajectory intersection with the next vertical edge. +std::tuple 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& 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 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 fScene; + sk_sp 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 child, sk_sp 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 geometry, sk_sp 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 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 child, sk_sp 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 colors; + std::vector 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 LinearGradient::onMakeShader(const std::vector& colors, + const std::vector& 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 RadialGradient::onMakeShader(const std::vector& colors, + const std::vector& 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 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& 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 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 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 child, sk_sp 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>&& 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) { + SkASSERT(node); + if (!(node->fFlags & kObserverArray_Flag)) { + if (!node->fInvalObserver) { + node->fInvalObserver = this; + return; + } + + auto observers = new SkTDArray(); + 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) { + 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 +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 + +namespace sksg { + +OpacityEffect::OpacityEffect(sk_sp 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(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 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::Make(sk_sp root, AnimatorList&& anims) { + return root ? std::unique_ptr(new Scene(std::move(root), std::move(anims))) : nullptr; +} + +Scene::Scene(sk_sp 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::Make(sk_sp tf, const SkString& text) { + return sk_sp(new Text(std::move(tf), text)); +} + +Text::Text(sk_sp 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 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 child, sk_sp 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 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 + +static void check_inval(skiatest::Reporter* reporter, const sk_sp& root, + const SkRect& expected_bounds, + const SkRect& expected_inval_bounds, + const std::vector* 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(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 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 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 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 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 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 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 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 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 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) -- cgit v1.2.3