diff options
-rw-r--r-- | experimental/skotty/Skotty.cpp | 124 | ||||
-rw-r--r-- | experimental/skotty/SkottyProperties.cpp | 6 | ||||
-rw-r--r-- | experimental/skotty/SkottyProperties.h | 10 | ||||
-rw-r--r-- | experimental/sksg/SkSGNode.cpp | 4 | ||||
-rw-r--r-- | experimental/sksg/SkSGNode.h | 5 | ||||
-rw-r--r-- | experimental/sksg/effects/SkSGTransform.cpp | 51 | ||||
-rw-r--r-- | experimental/sksg/effects/SkSGTransform.h | 50 | ||||
-rw-r--r-- | tests/SGTest.cpp | 25 |
8 files changed, 210 insertions, 65 deletions
diff --git a/experimental/skotty/Skotty.cpp b/experimental/skotty/Skotty.cpp index e84abcd4a8..3635c2c83b 100644 --- a/experimental/skotty/Skotty.cpp +++ b/experimental/skotty/Skotty.cpp @@ -29,6 +29,7 @@ #include "SkTHash.h" #include <cmath> +#include <unordered_map> #include <vector> #include "stdlib.h" @@ -81,33 +82,34 @@ bool AttachProperty(const Json::Value& jprop, AttachContext* ctx, const sk_sp<No return true; } -sk_sp<sksg::RenderNode> AttachTransform(const Json::Value& t, AttachContext* ctx, - sk_sp<sksg::RenderNode> wrapped_node) { - if (!t.isObject() || !wrapped_node) - return wrapped_node; +sk_sp<sksg::Matrix> AttachMatrix(const Json::Value& t, AttachContext* ctx, + sk_sp<sksg::Matrix> parentMatrix) { + if (!t.isObject()) + return nullptr; - auto xform = sk_make_sp<CompositeTransform>(wrapped_node); - auto anchor_attached = AttachProperty<VectorValue, SkPoint>(t["a"], ctx, xform, + auto matrix = sksg::Matrix::Make(SkMatrix::I(), std::move(parentMatrix)); + auto composite = sk_make_sp<CompositeTransform>(matrix); + auto anchor_attached = AttachProperty<VectorValue, SkPoint>(t["a"], ctx, composite, [](const sk_sp<CompositeTransform>& node, const SkPoint& a) { node->setAnchorPoint(a); }); - auto position_attached = AttachProperty<VectorValue, SkPoint>(t["p"], ctx, xform, + auto position_attached = AttachProperty<VectorValue, SkPoint>(t["p"], ctx, composite, [](const sk_sp<CompositeTransform>& node, const SkPoint& p) { node->setPosition(p); }); - auto scale_attached = AttachProperty<VectorValue, SkVector>(t["s"], ctx, xform, + auto scale_attached = AttachProperty<VectorValue, SkVector>(t["s"], ctx, composite, [](const sk_sp<CompositeTransform>& node, const SkVector& s) { node->setScale(s); }); - auto rotation_attached = AttachProperty<ScalarValue, SkScalar>(t["r"], ctx, xform, + auto rotation_attached = AttachProperty<ScalarValue, SkScalar>(t["r"], ctx, composite, [](const sk_sp<CompositeTransform>& node, SkScalar r) { node->setRotation(r); }); - auto skew_attached = AttachProperty<ScalarValue, SkScalar>(t["sk"], ctx, xform, + auto skew_attached = AttachProperty<ScalarValue, SkScalar>(t["sk"], ctx, composite, [](const sk_sp<CompositeTransform>& node, SkScalar sk) { node->setSkew(sk); }); - auto skewaxis_attached = AttachProperty<ScalarValue, SkScalar>(t["sa"], ctx, xform, + auto skewaxis_attached = AttachProperty<ScalarValue, SkScalar>(t["sa"], ctx, composite, [](const sk_sp<CompositeTransform>& node, SkScalar sa) { node->setSkewAxis(sa); }); @@ -119,10 +121,10 @@ sk_sp<sksg::RenderNode> AttachTransform(const Json::Value& t, AttachContext* ctx !skew_attached && !skewaxis_attached) { LogFail(t, "Could not parse transform"); - return wrapped_node; + return nullptr; } - return xform->node(); + return matrix; } sk_sp<sksg::RenderNode> AttachShape(const Json::Value&, AttachContext* ctx); @@ -327,12 +329,6 @@ static constexpr GroupAttacherT gGroupAttachers[] = { AttachShapeGroup, }; -using TransformAttacherT = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*, - sk_sp<sksg::RenderNode>); -static constexpr TransformAttacherT gTransformAttachers[] = { - AttachTransform, -}; - using GeometryEffectAttacherT = std::vector<sk_sp<sksg::GeometryNode>> (*)(const Json::Value&, AttachContext*, @@ -365,7 +361,7 @@ const ShapeInfo* FindShapeInfo(const Json::Value& shape) { { "sh", ShapeType::kGeometry , 0 }, // shape -> AttachPathGeometry { "sr", ShapeType::kGeometry , 3 }, // polystar -> AttachPolyStarGeometry { "st", ShapeType::kPaint , 1 }, // stroke -> AttachStrokePaint - { "tr", ShapeType::kTransform , 0 }, // transform -> AttachTransform + { "tr", ShapeType::kTransform , 0 }, // transform -> In-place handler }; if (!shape.isObject()) @@ -449,8 +445,10 @@ sk_sp<sksg::RenderNode> AttachShape(const Json::Value& shapeArray, AttachContext } break; case ShapeType::kTransform: { // TODO: BM appears to transform the geometry, not the draw op itself. - SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gTransformAttachers)); - xformed_group = gTransformAttachers[info->fAttacherIndex](s, ctx, xformed_group); + if (auto matrix = AttachMatrix(s, ctx, nullptr)) { + xformed_group = sksg::Transform::Make(std::move(xformed_group), + std::move(matrix)); + } } break; } } @@ -503,7 +501,8 @@ sk_sp<sksg::RenderNode> AttachImageLayer(const Json::Value& layer, AttachContext sk_sp<sksg::RenderNode> AttachNullLayer(const Json::Value& layer, AttachContext*) { SkASSERT(layer.isObject()); - LOG("?? Null layer stub\n"); + // Null layers are used solely to drive dependent transforms, + // but we use free-floating sksg::Matrices for that purpose. return nullptr; } @@ -522,8 +521,65 @@ sk_sp<sksg::RenderNode> AttachTextLayer(const Json::Value& layer, AttachContext* return nullptr; } -sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& layer, AttachContext* ctx) { - if (!layer.isObject()) +struct AttachLayerContext { + AttachLayerContext(const Json::Value& jlayers, AttachContext* ctx) + : fLayerList(jlayers), fCtx(ctx) {} + + const Json::Value& fLayerList; + AttachContext* fCtx; + std::unordered_map<const Json::Value*, sk_sp<sksg::Matrix>> fLayerMatrixCache; + std::unordered_map<int, const Json::Value*> fLayerIndexCache; + + const Json::Value* findLayer(int index) { + SkASSERT(fLayerList.isArray()); + + if (index < 0) { + return nullptr; + } + + const auto cached = fLayerIndexCache.find(index); + if (cached != fLayerIndexCache.end()) { + return cached->second; + } + + for (const auto& l : fLayerList) { + if (!l.isObject()) { + continue; + } + + if (ParseInt(l["ind"], -1) == index) { + fLayerIndexCache.insert(std::make_pair(index, &l)); + return &l; + } + } + + return nullptr; + } + + sk_sp<sksg::Matrix> AttachLayerMatrix(const Json::Value& jlayer) { + SkASSERT(jlayer.isObject()); + + const auto cached = fLayerMatrixCache.find(&jlayer); + if (cached != fLayerMatrixCache.end()) { + return cached->second; + } + + const auto* parentLayer = this->findLayer(ParseInt(jlayer["parent"], -1)); + + // TODO: cycle detection? + auto parentMatrix = (parentLayer && parentLayer != &jlayer) + ? this->AttachLayerMatrix(*parentLayer) : nullptr; + + auto layerMatrix = AttachMatrix(jlayer["ks"], fCtx, std::move(parentMatrix)); + fLayerMatrixCache.insert(std::make_pair(&jlayer, layerMatrix)); + + return layerMatrix; + } +}; + +sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& jlayer, + AttachLayerContext* layerCtx) { + if (!jlayer.isObject()) return nullptr; using LayerAttacher = sk_sp<sksg::RenderNode> (*)(const Json::Value&, AttachContext*); @@ -536,22 +592,32 @@ sk_sp<sksg::RenderNode> AttachLayer(const Json::Value& layer, AttachContext* ctx AttachTextLayer, // 'ty': 5 }; - int type = ParseInt(layer["ty"], -1); + int type = ParseInt(jlayer["ty"], -1); if (type < 0 || type >= SkTo<int>(SK_ARRAY_COUNT(gLayerAttachers))) { return nullptr; } - return AttachTransform(layer["ks"], ctx, gLayerAttachers[type](layer, ctx)); + auto layer = gLayerAttachers[type](jlayer, layerCtx->fCtx); + auto layerMatrix = layerCtx->AttachLayerMatrix(jlayer); + + return layerMatrix + ? sksg::Transform::Make(std::move(layer), std::move(layerMatrix)) + : layer; } sk_sp<sksg::RenderNode> AttachComposition(const Json::Value& comp, AttachContext* ctx) { if (!comp.isObject()) return nullptr; + const auto& jlayers = comp["layers"]; + if (!jlayers.isArray()) + return nullptr; + SkSTArray<16, sk_sp<sksg::RenderNode>, true> layers; + AttachLayerContext layerCtx(jlayers, ctx); - for (const auto& l : comp["layers"]) { - if (auto layer_fragment = AttachLayer(l, ctx)) { + for (const auto& l : jlayers) { + if (auto layer_fragment = AttachLayer(l, &layerCtx)) { layers.push_back(std::move(layer_fragment)); } } diff --git a/experimental/skotty/SkottyProperties.cpp b/experimental/skotty/SkottyProperties.cpp index 3029409f6c..0783ce8aaa 100644 --- a/experimental/skotty/SkottyProperties.cpp +++ b/experimental/skotty/SkottyProperties.cpp @@ -174,8 +174,8 @@ void CompositeRRect::apply() { fRRectNode->setRRect(rr); } -CompositeTransform::CompositeTransform(sk_sp<sksg::RenderNode> wrapped_node) - : fTransformNode(sksg::Transform::Make(std::move(wrapped_node), SkMatrix::I())) {} +CompositeTransform::CompositeTransform(sk_sp<sksg::Matrix> matrix) + : fMatrixNode(std::move(matrix)) {} void CompositeTransform::apply() { SkMatrix t = SkMatrix::MakeTrans(-fAnchorPoint.x(), -fAnchorPoint.y()); @@ -185,7 +185,7 @@ void CompositeTransform::apply() { t.postTranslate(fPosition.x(), fPosition.y()); // TODO: skew - fTransformNode->setMatrix(t); + fMatrixNode->setMatrix(t); } CompositePolyStar::CompositePolyStar(sk_sp<sksg::Path> wrapped_node, Type t) diff --git a/experimental/skotty/SkottyProperties.h b/experimental/skotty/SkottyProperties.h index 8730f61352..164f78093c 100644 --- a/experimental/skotty/SkottyProperties.h +++ b/experimental/skotty/SkottyProperties.h @@ -21,10 +21,10 @@ class SkPath; namespace sksg { +class Matrix; class Path; class RRect; -class RenderNode; -class Transform; +class RenderNode;; } namespace skotty { @@ -139,9 +139,7 @@ private: class CompositeTransform final : public SkRefCnt { public: - explicit CompositeTransform(sk_sp<sksg::RenderNode>); - - const sk_sp<sksg::Transform>& node() const { return fTransformNode; } + explicit CompositeTransform(sk_sp<sksg::Matrix>); COMPOSITE_PROPERTY(AnchorPoint, SkPoint , SkPoint::Make(0, 0)) COMPOSITE_PROPERTY(Position , SkPoint , SkPoint::Make(0, 0)) @@ -153,7 +151,7 @@ public: private: void apply(); - sk_sp<sksg::Transform> fTransformNode; + sk_sp<sksg::Matrix> fMatrixNode; using INHERITED = SkRefCnt; }; diff --git a/experimental/sksg/SkSGNode.cpp b/experimental/sksg/SkSGNode.cpp index 13e9864147..fc4d278580 100644 --- a/experimental/sksg/SkSGNode.cpp +++ b/experimental/sksg/SkSGNode.cpp @@ -128,8 +128,8 @@ const SkRect& Node::revalidate(InvalidationController* ic, const SkMatrix& ctm) } const auto result = this->onRevalidate(ic, ctm); - const auto selfDamage = result.fReval == Damage::kForceSelf || - (this->hasSelfInval() && result.fReval != Damage::kBlockSelf); + const auto selfDamage = result.fDamage == Damage::kForceSelf || + (this->hasSelfInval() && result.fDamage != Damage::kBlockSelf); if (selfDamage) { // old bounds diff --git a/experimental/sksg/SkSGNode.h b/experimental/sksg/SkSGNode.h index 58456cf387..1a7560684c 100644 --- a/experimental/sksg/SkSGNode.h +++ b/experimental/sksg/SkSGNode.h @@ -54,18 +54,21 @@ protected: }; struct RevalidationResult { SkRect fBounds; - Damage fReval; + Damage fDamage; }; virtual RevalidationResult onRevalidate(InvalidationController*, const SkMatrix& ctm) = 0; private: void addInvalReceiver(Node*); void removeInvalReceiver(Node*); + // TODO: too friendly, find another way. friend class Draw; friend class EffectNode; friend class Group; + friend class Matrix; friend class Merge; friend class Stroke; + friend class Transform; template <typename Func> void forEachInvalReceiver(Func&&) const; diff --git a/experimental/sksg/effects/SkSGTransform.cpp b/experimental/sksg/effects/SkSGTransform.cpp index dc31623db2..1ea1e619a8 100644 --- a/experimental/sksg/effects/SkSGTransform.cpp +++ b/experimental/sksg/effects/SkSGTransform.cpp @@ -11,21 +11,60 @@ namespace sksg { -Transform::Transform(sk_sp<RenderNode> child, const SkMatrix& matrix) +Matrix::Matrix(const SkMatrix& m, sk_sp<Matrix> parent) + : fParent(std::move(parent)) + , fLocalMatrix(m) { + if (fParent) { + fParent->addInvalReceiver(this); + } +} + +Matrix::~Matrix() { + if (fParent) { + fParent->removeInvalReceiver(this); + } +} + +Node::RevalidationResult Matrix::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) { + fTotalMatrix = fLocalMatrix; + + if (fParent) { + fParent->revalidate(ic, ctm); + fTotalMatrix.postConcat(fParent->getTotalMatrix()); + } + + // A free-floating matrix contributes no damage. + return { SkRect::MakeEmpty(), Damage::kBlockSelf }; +} + +Transform::Transform(sk_sp<RenderNode> child, sk_sp<Matrix> matrix) : INHERITED(std::move(child)) - , fMatrix(matrix) {} + , fMatrix(std::move(matrix)) { + fMatrix->addInvalReceiver(this); +} + +Transform::~Transform() { + fMatrix->removeInvalReceiver(this); +} void Transform::onRender(SkCanvas* canvas) const { - SkAutoCanvasRestore acr(canvas, !fMatrix.isIdentity()); - canvas->concat(fMatrix); + const auto& m = fMatrix->getTotalMatrix(); + SkAutoCanvasRestore acr(canvas, !m.isIdentity()); + canvas->concat(m); this->INHERITED::onRender(canvas); } Node::RevalidationResult Transform::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) { SkASSERT(this->hasInval()); - auto result = this->INHERITED::onRevalidate(ic, SkMatrix::Concat(ctm, fMatrix)); - fMatrix.mapRect(&result.fBounds); + // We don't care about matrix reval results, but we do care whether it was invalidated. + const auto localDamage = fMatrix->hasInval() ? Damage::kForceSelf : Damage::kDefault; + fMatrix->revalidate(ic, ctm); + + const auto& m = fMatrix->getTotalMatrix(); + auto result = this->INHERITED::onRevalidate(ic, SkMatrix::Concat(ctm, m)); + m.mapRect(&result.fBounds); + result.fDamage = localDamage; return result; } diff --git a/experimental/sksg/effects/SkSGTransform.h b/experimental/sksg/effects/SkSGTransform.h index 8a97a679ed..0d11739f30 100644 --- a/experimental/sksg/effects/SkSGTransform.h +++ b/experimental/sksg/effects/SkSGTransform.h @@ -15,25 +15,63 @@ namespace sksg { /** - * Concrete Effect node, wrapping an SkMatrix. + * Concrete node, wrapping an SkMatrix, with an optional parent Matrix (to allow chaining): + * + * M' = parent x M + */ +class Matrix : public Node { +public: + static sk_sp<Matrix> Make(const SkMatrix& m, sk_sp<Matrix> parent = nullptr) { + return sk_sp<Matrix>(new Matrix(m, std::move(parent))); + } + + ~Matrix() override; + + SG_ATTRIBUTE(Matrix, SkMatrix, fLocalMatrix) + + const SkMatrix& getTotalMatrix() const { return fTotalMatrix; } + +protected: + explicit Matrix(const SkMatrix&, sk_sp<Matrix>); + + RevalidationResult onRevalidate(InvalidationController*, const SkMatrix&) override; + +private: + sk_sp<Matrix> fParent; + SkMatrix fLocalMatrix, + fTotalMatrix; // cached during revalidation + + typedef Node INHERITED; +}; + +/** + * Concrete Effect node, binding a Matrix to a RenderNode. */ class Transform : public EffectNode { public: - static sk_sp<Transform> Make(sk_sp<RenderNode> child, const SkMatrix& matrix) { - return sk_sp<Transform>(new Transform(std::move(child), matrix)); + static sk_sp<Transform> Make(sk_sp<RenderNode> child, sk_sp<Matrix> matrix) { + return child && matrix + ? sk_sp<Transform>(new Transform(std::move(child), std::move(matrix))) + : nullptr; } - SG_ATTRIBUTE(Matrix, SkMatrix, fMatrix) + static sk_sp<Transform> Make(sk_sp<RenderNode> child, const SkMatrix& m) { + return Make(std::move(child), Matrix::Make(m)); + } + + ~Transform() override; + + const sk_sp<Matrix>& getMatrix() const { return fMatrix; } protected: - Transform(sk_sp<RenderNode>, const SkMatrix&); + Transform(sk_sp<RenderNode>, sk_sp<Matrix>); void onRender(SkCanvas*) const override; RevalidationResult onRevalidate(InvalidationController*, const SkMatrix&) override; private: - SkMatrix fMatrix; + sk_sp<Matrix> fMatrix; typedef EffectNode INHERITED; }; diff --git a/tests/SGTest.cpp b/tests/SGTest.cpp index ca6c23d163..64b83b5bf8 100644 --- a/tests/SGTest.cpp +++ b/tests/SGTest.cpp @@ -52,18 +52,19 @@ static void check_inval(skiatest::Reporter* reporter, const sk_sp<sksg::Node>& r } DEF_TEST(SGInvalidation, 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 tr = sksg::Transform::Make(grp, SkMatrix::I()); + 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, tr, + check_inval(reporter, root, SkRect::MakeWH(100, 100), SkRect::MakeLargestS32(), nullptr); @@ -73,7 +74,7 @@ DEF_TEST(SGInvalidation, reporter) { // Move r2 to (200 100). r2->setL(200); r2->setT(100); r2->setR(300); r2->setB(200); std::vector<SkRect> damage = { {0, 0, 100, 100}, { 200, 100, 300, 200} }; - check_inval(reporter, tr, + check_inval(reporter, root, SkRect::MakeWH(300, 200), SkRect::MakeWH(300, 200), &damage); @@ -83,7 +84,7 @@ DEF_TEST(SGInvalidation, reporter) { // Update the common color. color->setColor(0xffff0000); std::vector<SkRect> damage = { {0, 0, 100, 100}, { 200, 100, 300, 200} }; - check_inval(reporter, tr, + check_inval(reporter, root, SkRect::MakeWH(300, 200), SkRect::MakeWH(300, 200), &damage); @@ -93,7 +94,7 @@ DEF_TEST(SGInvalidation, reporter) { // Shrink r1. r1->setR(50); std::vector<SkRect> damage = { {0, 0, 100, 100}, { 0, 0, 50, 100} }; - check_inval(reporter, tr, + check_inval(reporter, root, SkRect::MakeWH(300, 200), SkRect::MakeWH(100, 100), &damage); @@ -101,9 +102,9 @@ DEF_TEST(SGInvalidation, reporter) { { // Update transform. - tr->setMatrix(SkMatrix::MakeScale(2, 2)); + matrix->setMatrix(SkMatrix::MakeScale(2, 2)); std::vector<SkRect> damage = { {0, 0, 300, 200}, { 0, 0, 600, 400} }; - check_inval(reporter, tr, + check_inval(reporter, root, SkRect::MakeWH(600, 400), SkRect::MakeWH(600, 400), &damage); @@ -113,7 +114,7 @@ DEF_TEST(SGInvalidation, reporter) { // Shrink r2 under transform. r2->setR(250); std::vector<SkRect> damage = { {400, 200, 600, 400}, { 400, 200, 500, 400} }; - check_inval(reporter, tr, + check_inval(reporter, root, SkRect::MakeWH(500, 400), SkRect::MakeLTRB(400, 200, 600, 400), &damage); |