[skottie] Cleanup: track animator scoping in AnimationBuilder

Instead of passing an explicit scope all over, keep track of the current
scope in AnimationBuilder.

Removes a bunch or redundant plumbing.

TBR=
Change-Id: I9e587f4ae7a1d12f86d13f30144816492a4ce147
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/229762
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/src/Composition.cpp b/modules/skottie/src/Composition.cpp
index f7209b9..3efad42 100644
--- a/modules/skottie/src/Composition.cpp
+++ b/modules/skottie/src/Composition.cpp
@@ -17,8 +17,7 @@
 namespace skottie {
 namespace internal {
 
-sk_sp<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name,
-                                                                AnimatorScope* ascope) const {
+sk_sp<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name) const {
     class SkottieSGAdapter final : public sksg::RenderNode {
     public:
         explicit SkottieSGAdapter(sk_sp<Animation> animation)
@@ -79,16 +78,15 @@
         return nullptr;
     }
 
-    ascope->push_back(sk_make_sp<SkottieAnimatorAdapter>(animation,
-                                                         animation->duration() / fDuration));
+    fCurrentAnimatorScope->push_back(
+            sk_make_sp<SkottieAnimatorAdapter>(animation, animation->duration() / fDuration));
 
     return sk_make_sp<SkottieSGAdapter>(std::move(animation));
 }
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachAssetRef(
-    const skjson::ObjectValue& jlayer, AnimatorScope* ascope,
-    const std::function<sk_sp<sksg::RenderNode>(const skjson::ObjectValue&,
-                                                AnimatorScope*)>& func) const {
+    const skjson::ObjectValue& jlayer,
+    const std::function<sk_sp<sksg::RenderNode>(const skjson::ObjectValue&)>& func) const {
 
     const auto refId = ParseDefault<SkString>(jlayer["refId"], SkString());
     if (refId.isEmpty()) {
@@ -97,7 +95,7 @@
     }
 
     if (refId.startsWith("$")) {
-        return this->attachNestedAnimation(refId.c_str() + 1, ascope);
+        return this->attachNestedAnimation(refId.c_str() + 1);
     }
 
     const auto* asset_info = fAssets.find(refId);
@@ -113,14 +111,14 @@
     }
 
     asset_info->fIsAttaching = true;
-    auto asset = func(*asset_info->fAsset, ascope);
+    auto asset = func(*asset_info->fAsset);
     asset_info->fIsAttaching = false;
 
     return asset;
 }
 
-sk_sp<sksg::RenderNode> AnimationBuilder::attachComposition(const skjson::ObjectValue& jcomp,
-                                                            AnimatorScope* scope) const {
+sk_sp<sksg::RenderNode> AnimationBuilder::attachComposition(
+        const skjson::ObjectValue& jcomp) const {
     const skjson::ArrayValue* jlayers = jcomp["layers"];
     if (!jlayers) return nullptr;
 
@@ -138,7 +136,7 @@
 
     layers.reserve(jlayers->size());
     for (const auto& l : *jlayers) {
-        if (auto layer = this->attachLayer(l, scope, &layerCtx)) {
+        if (auto layer = this->attachLayer(l, &layerCtx)) {
             layers.push_back(std::move(layer));
         }
     }
diff --git a/modules/skottie/src/Layer.cpp b/modules/skottie/src/Layer.cpp
index f6c9178..053ff7c 100644
--- a/modules/skottie/src/Layer.cpp
+++ b/modules/skottie/src/Layer.cpp
@@ -62,7 +62,6 @@
 
 sk_sp<sksg::RenderNode> AttachMask(const skjson::ArrayValue* jmask,
                                    const AnimationBuilder* abuilder,
-                                   AnimatorScope* ascope,
                                    sk_sp<sksg::RenderNode> childNode) {
     if (!jmask) return childNode;
 
@@ -99,7 +98,7 @@
             continue;
         }
 
-        auto mask_path = abuilder->attachPath((*m)["pt"], ascope);
+        auto mask_path = abuilder->attachPath((*m)["pt"]);
         if (!mask_path) {
             abuilder->log(Logger::Level::kError, m, "Could not parse mask path.");
             continue;
@@ -117,13 +116,13 @@
         mask_paint->setBlendMode(mask_stack.empty() ? SkBlendMode::kSrc
                                                     : mask_info->fBlendMode);
 
-        has_effect |= abuilder->bindProperty<ScalarValue>((*m)["o"], ascope,
+        has_effect |= abuilder->bindProperty<ScalarValue>((*m)["o"],
             [mask_paint](const ScalarValue& o) {
                 mask_paint->setOpacity(o * 0.01f);
         }, 100.0f);
 
         static const VectorValue default_feather = { 0, 0 };
-        if (abuilder->bindProperty<VectorValue>((*m)["f"], ascope,
+        if (abuilder->bindProperty<VectorValue>((*m)["f"],
             [blur_effect](const VectorValue& feather) {
                 // Close enough to AE.
                 static constexpr SkScalar kFeatherToSigma = 0.38f;
@@ -311,7 +310,6 @@
 sk_sp<sksg::Transform>
 AnimationBuilder::AttachLayerContext::attachTransformNode(const skjson::ObjectValue& jlayer,
                                                           const AnimationBuilder* abuilder,
-                                                          AnimatorScope* ascope,
                                                           sk_sp<sksg::Transform> parent_transform,
                                                           TransformType type) const {
     const skjson::ObjectValue* jtransform = jlayer["ks"];
@@ -322,7 +320,7 @@
     if (type == TransformType::kCamera) {
         auto camera_adapter = sk_make_sp<CameraAdapter>(abuilder->fSize);
 
-        abuilder->bindProperty<ScalarValue>(jlayer["pe"], ascope,
+        abuilder->bindProperty<ScalarValue>(jlayer["pe"],
             [camera_adapter] (const ScalarValue& pe) {
                 // 'pe' (perspective?) corresponds to AE's "zoom" camera property.
                 camera_adapter->setZoom(pe);
@@ -335,15 +333,15 @@
         //
         parent_transform = sksg::Transform::MakeInverse(std::move(parent_transform));
 
-        return abuilder->attachMatrix3D(*jtransform, ascope,
+        return abuilder->attachMatrix3D(*jtransform,
                                         std::move(parent_transform),
                                         std::move(camera_adapter),
                                         true); // pre-compose parent
     }
 
     return (ParseDefault<int>(jlayer["ddd"], 0) == 0)
-            ? abuilder->attachMatrix2D(*jtransform, ascope, std::move(parent_transform))
-            : abuilder->attachMatrix3D(*jtransform, ascope, std::move(parent_transform));
+            ? abuilder->attachMatrix2D(*jtransform, std::move(parent_transform))
+            : abuilder->attachMatrix3D(*jtransform, std::move(parent_transform));
 }
 
 AnimationBuilder::AttachLayerContext::TransformRec*
@@ -358,14 +356,13 @@
 
     auto parent_matrix = this->attachParentLayerTransform(jlayer, abuilder, layer_index);
 
-    AnimatorScope ascope;
+    AutoScope ascope(abuilder);
     auto transform = this->attachTransformNode(jlayer,
                                                abuilder,
-                                               &ascope,
                                                std::move(parent_matrix),
                                                type);
 
-    return fLayerTransformMap.set(layer_index, { std::move(transform), std::move(ascope) });
+    return fLayerTransformMap.set(layer_index, { std::move(transform), ascope.release() });
 }
 
 bool AnimationBuilder::AttachLayerContext::hasMotionBlur(const skjson::ObjectValue& jlayer) const {
@@ -375,7 +372,6 @@
 }
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachLayer(const skjson::ObjectValue* jlayer,
-                                                      AnimatorScope* ascope,
                                                       AttachLayerContext* layerCtx) const {
     if (!jlayer) {
         return nullptr;
@@ -395,8 +391,7 @@
     const AutoPropertyTracker apt(this, *jlayer);
 
     using LayerBuilder = sk_sp<sksg::RenderNode> (AnimationBuilder::*)(const skjson::ObjectValue&,
-                                                                       LayerInfo*,
-                                                                       AnimatorScope*) const;
+                                                                       LayerInfo*) const;
 
     // AE is annoyingly inconsistent in how effects interact with layer transforms: depending on
     // the layer type, effects are applied before or after the content is transformed.
@@ -443,14 +438,14 @@
         layerCtx->fCameraTransform = layer_transform_rec.fTransformNode;
     }
 
-    AnimatorScope layer_animators = std::move(layer_transform_rec.fTransformScope);
-    const auto transform_animator_count = layer_animators.size();
+    AutoScope ascope(this, std::move(layer_transform_rec.fTransformScope));
+    const auto transform_animator_count = fCurrentAnimatorScope->size();
 
     const auto is_hidden = ParseDefault<bool>((*jlayer)["hd"], false) || type == kCameraLayerType;
     const auto& build_info = gLayerBuildInfo[is_hidden ? kNullLayerType : type];
 
     // Build the layer content fragment.
-    auto layer = (this->*(build_info.fBuilder))(*jlayer, &layer_info, &layer_animators);
+    auto layer = (this->*(build_info.fBuilder))(*jlayer, &layer_info);
 
     // Clip layers with explicit dimensions.
     float w = 0, h = 0;
@@ -461,7 +456,7 @@
     }
 
     // Optional layer mask.
-    layer = AttachMask((*jlayer)["masksProperties"], this, &layer_animators, std::move(layer));
+    layer = AttachMask((*jlayer)["masksProperties"], this, std::move(layer));
 
     // Does the transform apply to effects also?
     // (AE quirk: it doesn't - except for solid layers)
@@ -474,8 +469,7 @@
 
     // Optional layer effects.
     if (const skjson::ArrayValue* jeffects = (*jlayer)["ef"]) {
-        layer = EffectBuilder(this, layer_info.fSize, &layer_animators)
-                    .attachEffects(*jeffects, std::move(layer));
+        layer = EffectBuilder(this, layer_info.fSize).attachEffects(*jeffects, std::move(layer));
     }
 
     // Attach the transform after effects, when needed.
@@ -487,15 +481,15 @@
     // Optional layer opacity.
     // TODO: de-dupe this "ks" lookup with matrix above.
     if (const skjson::ObjectValue* jtransform = (*jlayer)["ks"]) {
-        layer = this->attachOpacity(*jtransform, &layer_animators, std::move(layer));
+        layer = this->attachOpacity(*jtransform, std::move(layer));
     }
 
     // Optional blend mode.
     layer = this->attachBlendMode(*jlayer, std::move(layer));
 
-    const auto has_animators = !layer_animators.empty();
+    const auto has_animators = !fCurrentAnimatorScope->empty();
 
-    sk_sp<sksg::Animator> controller = sk_make_sp<LayerController>(std::move(layer_animators),
+    sk_sp<sksg::Animator> controller = sk_make_sp<LayerController>(ascope.release(),
                                                                    layer,
                                                                    transform_animator_count,
                                                                    layer_info.fInPoint,
@@ -514,7 +508,7 @@
         layer = std::move(motion_blur);
     }
 
-    ascope->push_back(std::move(controller));
+    fCurrentAnimatorScope->push_back(std::move(controller));
 
     if (!layer) {
         return nullptr;
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index 1c14170..2afb33b 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -67,7 +67,6 @@
 }
 
 sk_sp<sksg::Transform> AnimationBuilder::attachMatrix2D(const skjson::ObjectValue& t,
-                                                        AnimatorScope* ascope,
                                                         sk_sp<sksg::Transform> parent) const {
     static const VectorValue g_default_vec_0   = {  0,   0},
                              g_default_vec_100 = {100, 100};
@@ -75,15 +74,15 @@
     auto matrix = sksg::Matrix<SkMatrix>::Make(SkMatrix::I());
     auto adapter = sk_make_sp<TransformAdapter2D>(matrix);
 
-    auto bound = this->bindProperty<VectorValue>(t["a"], ascope,
+    auto bound = this->bindProperty<VectorValue>(t["a"],
             [adapter](const VectorValue& a) {
                 adapter->setAnchorPoint(ValueTraits<VectorValue>::As<SkPoint>(a));
             }, g_default_vec_0);
-    bound |= this->bindProperty<VectorValue>(t["p"], ascope,
+    bound |= this->bindProperty<VectorValue>(t["p"],
             [adapter](const VectorValue& p) {
                 adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
             }, g_default_vec_0);
-    bound |= this->bindProperty<VectorValue>(t["s"], ascope,
+    bound |= this->bindProperty<VectorValue>(t["s"],
             [adapter](const VectorValue& s) {
                 adapter->setScale(ValueTraits<VectorValue>::As<SkVector>(s));
             }, g_default_vec_100);
@@ -94,15 +93,15 @@
         // we can still make use of rz.
         jrotation = &t["rz"];
     }
-    bound |= this->bindProperty<ScalarValue>(*jrotation, ascope,
+    bound |= this->bindProperty<ScalarValue>(*jrotation,
             [adapter](const ScalarValue& r) {
                 adapter->setRotation(r);
             }, 0.0f);
-    bound |= this->bindProperty<ScalarValue>(t["sk"], ascope,
+    bound |= this->bindProperty<ScalarValue>(t["sk"],
             [adapter](const ScalarValue& sk) {
                 adapter->setSkew(sk);
             }, 0.0f);
-    bound |= this->bindProperty<ScalarValue>(t["sa"], ascope,
+    bound |= this->bindProperty<ScalarValue>(t["sa"],
             [adapter](const ScalarValue& sa) {
                 adapter->setSkewAxis(sa);
             }, 0.0f);
@@ -115,7 +114,6 @@
 }
 
 sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValue& t,
-                                                        AnimatorScope* ascope,
                                                         sk_sp<sksg::Transform> parent,
                                                         sk_sp<TransformAdapter3D> adapter,
                                                         bool precompose_parent) const {
@@ -127,39 +125,39 @@
         adapter = sk_make_sp<TransformAdapter3D>();
     }
 
-    auto bound = this->bindProperty<VectorValue>(t["a"], ascope,
+    auto bound = this->bindProperty<VectorValue>(t["a"],
             [adapter](const VectorValue& a) {
                 adapter->setAnchorPoint(TransformAdapter3D::Vec3(a));
             }, g_default_vec_0);
-    bound |= this->bindProperty<VectorValue>(t["p"], ascope,
+    bound |= this->bindProperty<VectorValue>(t["p"],
             [adapter](const VectorValue& p) {
                 adapter->setPosition(TransformAdapter3D::Vec3(p));
             }, g_default_vec_0);
-    bound |= this->bindProperty<VectorValue>(t["s"], ascope,
+    bound |= this->bindProperty<VectorValue>(t["s"],
             [adapter](const VectorValue& s) {
                 adapter->setScale(TransformAdapter3D::Vec3(s));
             }, g_default_vec_100);
 
     // Orientation and rx/ry/rz are mapped to the same rotation property -- the difference is
     // in how they get interpolated (vector vs. scalar/decomposed interpolation).
-    bound |= this->bindProperty<VectorValue>(t["or"], ascope,
+    bound |= this->bindProperty<VectorValue>(t["or"],
             [adapter](const VectorValue& o) {
                 adapter->setRotation(TransformAdapter3D::Vec3(o));
             }, g_default_vec_0);
 
-    bound |= this->bindProperty<ScalarValue>(t["rx"], ascope,
+    bound |= this->bindProperty<ScalarValue>(t["rx"],
             [adapter](const ScalarValue& rx) {
                 const auto& r = adapter->getRotation();
                 adapter->setRotation(TransformAdapter3D::Vec3({rx, r.fY, r.fZ}));
             }, 0.0f);
 
-    bound |= this->bindProperty<ScalarValue>(t["ry"], ascope,
+    bound |= this->bindProperty<ScalarValue>(t["ry"],
             [adapter](const ScalarValue& ry) {
                 const auto& r = adapter->getRotation();
                 adapter->setRotation(TransformAdapter3D::Vec3({r.fX, ry, r.fZ}));
             }, 0.0f);
 
-    bound |= this->bindProperty<ScalarValue>(t["rz"], ascope,
+    bound |= this->bindProperty<ScalarValue>(t["rz"],
             [adapter](const ScalarValue& rz) {
                 const auto& r = adapter->getRotation();
                 adapter->setRotation(TransformAdapter3D::Vec3({r.fX, r.fY, rz}));
@@ -177,14 +175,13 @@
 }
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachOpacity(const skjson::ObjectValue& jtransform,
-                                                        AnimatorScope* ascope,
                                                         sk_sp<sksg::RenderNode> childNode) const {
     if (!childNode)
         return nullptr;
 
     auto opacityNode = sksg::OpacityEffect::Make(childNode);
 
-    const auto bound = this->bindProperty<ScalarValue>(jtransform["o"], ascope,
+    const auto bound = this->bindProperty<ScalarValue>(jtransform["o"],
         [opacityNode](const ScalarValue& o) {
             // BM opacity is [0..100]
             opacityNode->setOpacity(o * 0.01f);
@@ -242,10 +239,9 @@
     return child;
 }
 
-sk_sp<sksg::Path> AnimationBuilder::attachPath(const skjson::Value& jpath,
-                                               AnimatorScope* ascope) const {
+sk_sp<sksg::Path> AnimationBuilder::attachPath(const skjson::Value& jpath) const {
     auto path_node = sksg::Path::Make();
-    return this->bindProperty<ShapeValue>(jpath, ascope,
+    return this->bindProperty<ShapeValue>(jpath,
         [path_node](const ShapeValue& p) {
             // FillType is tracked in the SG node, not in keyframes -- make sure we preserve it.
             auto path = ValueTraits<ShapeValue>::As<SkPath>(p);
@@ -257,11 +253,10 @@
 }
 
 sk_sp<sksg::Color> AnimationBuilder::attachColor(const skjson::ObjectValue& jcolor,
-                                                 AnimatorScope* ascope,
                                                  const char prop_name[]) const {
     auto color_node = sksg::Color::Make(SK_ColorBLACK);
 
-    this->bindProperty<VectorValue>(jcolor[prop_name], ascope,
+    this->bindProperty<VectorValue>(jcolor[prop_name],
         [color_node](const VectorValue& c) {
             color_node->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
         });
@@ -292,9 +287,10 @@
     this->parseAssets(jroot["assets"]);
     this->parseFonts(jroot["fonts"], jroot["chars"]);
 
-    AnimatorScope animators;
-    auto root = this->attachComposition(jroot, &animators);
+    AutoScope ascope(this);
+    auto root = this->attachComposition(jroot);
 
+    auto animators = ascope.release();
     fStats->fAnimatorCount = animators.size();
 
     return sksg::Scene::Make(std::move(root), std::move(animators));
diff --git a/modules/skottie/src/SkottieAnimator.cpp b/modules/skottie/src/SkottieAnimator.cpp
index 62f7f80..e9172fa 100644
--- a/modules/skottie/src/SkottieAnimator.cpp
+++ b/modules/skottie/src/SkottieAnimator.cpp
@@ -427,39 +427,35 @@
 
 template <>
 bool AnimationBuilder::bindProperty(const skjson::Value& jv,
-                  AnimatorScope* ascope,
                   std::function<void(const ScalarValue&)>&& apply,
                   const ScalarValue* noop) const {
-    return BindPropertyImpl(jv, this, ascope, std::move(apply), noop);
+    return BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
 }
 
 template <>
 bool AnimationBuilder::bindProperty(const skjson::Value& jv,
-                  AnimatorScope* ascope,
                   std::function<void(const VectorValue&)>&& apply,
                   const VectorValue* noop) const {
     if (!jv.is<skjson::ObjectValue>())
         return false;
 
     return ParseDefault<bool>(jv.as<skjson::ObjectValue>()["s"], false)
-        ? BindSplitPositionProperty(jv, this, ascope, std::move(apply), noop)
-        : BindPropertyImpl(jv, this, ascope, std::move(apply), noop);
+        ? BindSplitPositionProperty(jv, this, fCurrentAnimatorScope, std::move(apply), noop)
+        : BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
 }
 
 template <>
 bool AnimationBuilder::bindProperty(const skjson::Value& jv,
-                  AnimatorScope* ascope,
                   std::function<void(const ShapeValue&)>&& apply,
                   const ShapeValue* noop) const {
-    return BindPropertyImpl(jv, this, ascope, std::move(apply), noop);
+    return BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
 }
 
 template <>
 bool AnimationBuilder::bindProperty(const skjson::Value& jv,
-                  AnimatorScope* ascope,
                   std::function<void(const TextValue&)>&& apply,
                   const TextValue* noop) const {
-    return BindPropertyImpl(jv, this, ascope, std::move(apply), noop);
+    return BindPropertyImpl(jv, this, fCurrentAnimatorScope, std::move(apply), noop);
 }
 
 } // namespace internal
diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h
index ae0bccc..43c7013 100644
--- a/modules/skottie/src/SkottiePriv.h
+++ b/modules/skottie/src/SkottiePriv.h
@@ -67,34 +67,58 @@
     // it will either apply immediately or instantiate and attach a keyframe animator.
     template <typename T>
     bool bindProperty(const skjson::Value&,
-                      AnimatorScope*,
                       std::function<void(const T&)>&&,
                       const T* default_igore = nullptr) const;
 
     template <typename T>
     bool bindProperty(const skjson::Value& jv,
-                      AnimatorScope* ascope,
                       std::function<void(const T&)>&& apply,
                       const T& default_ignore) const {
-        return this->bindProperty(jv, ascope, std::move(apply), &default_ignore);
+        return this->bindProperty(jv, std::move(apply), &default_ignore);
     }
 
     void log(Logger::Level, const skjson::Value*, const char fmt[], ...) const;
 
-    sk_sp<sksg::Color> attachColor(const skjson::ObjectValue&, AnimatorScope*,
-                                   const char prop_name[]) const;
-    sk_sp<sksg::Transform> attachMatrix2D(const skjson::ObjectValue&, AnimatorScope*,
-                                          sk_sp<sksg::Transform>) const;
-    sk_sp<sksg::Transform> attachMatrix3D(const skjson::ObjectValue&, AnimatorScope*,
-                                          sk_sp<sksg::Transform>,
+    sk_sp<sksg::Color> attachColor(const skjson::ObjectValue&, const char prop_name[]) const;
+    sk_sp<sksg::Transform> attachMatrix2D(const skjson::ObjectValue&, sk_sp<sksg::Transform>) const;
+    sk_sp<sksg::Transform> attachMatrix3D(const skjson::ObjectValue&, sk_sp<sksg::Transform>,
                                           sk_sp<TransformAdapter3D> = nullptr,
                                           bool precompose_parent = false) const;
-    sk_sp<sksg::RenderNode> attachOpacity(const skjson::ObjectValue&, AnimatorScope*,
-                                      sk_sp<sksg::RenderNode>) const;
-    sk_sp<sksg::Path> attachPath(const skjson::Value&, AnimatorScope*) const;
+    sk_sp<sksg::RenderNode> attachOpacity(const skjson::ObjectValue&,
+                                          sk_sp<sksg::RenderNode>) const;
+    sk_sp<sksg::Path> attachPath(const skjson::Value&) const;
 
     bool hasNontrivialBlending() const { return fHasNontrivialBlending; }
 
+    class AutoScope final {
+    public:
+        explicit AutoScope(const AnimationBuilder* builder) : AutoScope(builder, AnimatorScope()) {}
+
+        AutoScope(const AnimationBuilder* builder, AnimatorScope&& scope)
+            : fBuilder(builder)
+            , fCurrentScope(std::move(scope))
+            , fPrevScope(fBuilder->fCurrentAnimatorScope) {
+            fBuilder->fCurrentAnimatorScope = &fCurrentScope;
+        }
+
+        AnimatorScope release() {
+            fBuilder->fCurrentAnimatorScope = fPrevScope;
+            SkDEBUGCODE(fBuilder = nullptr);
+
+            return std::move(fCurrentScope);
+        }
+
+        ~AutoScope() { SkASSERT(!fBuilder); }
+
+    private:
+        const AnimationBuilder* fBuilder;
+        AnimatorScope           fCurrentScope;
+        AnimatorScope*          fPrevScope;
+    };
+
+    // TODO: delete
+    AnimatorScope* currentScope() const { return fCurrentAnimatorScope; }
+
 private:
     struct AttachLayerContext;
     struct AttachShapeContext;
@@ -107,36 +131,27 @@
 
     void dispatchMarkers(const skjson::ArrayValue*) const;
 
-    sk_sp<sksg::RenderNode> attachComposition(const skjson::ObjectValue&, AnimatorScope*) const;
+    sk_sp<sksg::RenderNode> attachComposition(const skjson::ObjectValue&) const;
     sk_sp<sksg::RenderNode> attachLayer(const skjson::ObjectValue*,
-                                        AnimatorScope*,
                                         AttachLayerContext*) const;
 
     sk_sp<sksg::RenderNode> attachBlendMode(const skjson::ObjectValue&,
                                             sk_sp<sksg::RenderNode>) const;
 
     sk_sp<sksg::RenderNode> attachShape(const skjson::ArrayValue*, AttachShapeContext*) const;
-    sk_sp<sksg::RenderNode> attachAssetRef(const skjson::ObjectValue&, AnimatorScope*,
-        const std::function<sk_sp<sksg::RenderNode>(const skjson::ObjectValue&,
-                                                    AnimatorScope* ctx)>&) const;
+    sk_sp<sksg::RenderNode> attachAssetRef(const skjson::ObjectValue&,
+        const std::function<sk_sp<sksg::RenderNode>(const skjson::ObjectValue&)>&) const;
     const ImageAssetInfo* loadImageAsset(const skjson::ObjectValue&) const;
-    sk_sp<sksg::RenderNode> attachImageAsset(const skjson::ObjectValue&, LayerInfo*,
-                                             AnimatorScope*) const;
+    sk_sp<sksg::RenderNode> attachImageAsset(const skjson::ObjectValue&, LayerInfo*) const;
 
-    sk_sp<sksg::RenderNode> attachNestedAnimation(const char* name, AnimatorScope* ascope) const;
+    sk_sp<sksg::RenderNode> attachNestedAnimation(const char* name) const;
 
-    sk_sp<sksg::RenderNode> attachImageLayer  (const skjson::ObjectValue&, LayerInfo*,
-                                               AnimatorScope*) const;
-    sk_sp<sksg::RenderNode> attachNullLayer   (const skjson::ObjectValue&, LayerInfo*,
-                                               AnimatorScope*) const;
-    sk_sp<sksg::RenderNode> attachPrecompLayer(const skjson::ObjectValue&, LayerInfo*,
-                                               AnimatorScope*) const;
-    sk_sp<sksg::RenderNode> attachShapeLayer  (const skjson::ObjectValue&, LayerInfo*,
-                                               AnimatorScope*) const;
-    sk_sp<sksg::RenderNode> attachSolidLayer  (const skjson::ObjectValue&, LayerInfo*,
-                                               AnimatorScope*) const;
-    sk_sp<sksg::RenderNode> attachTextLayer   (const skjson::ObjectValue&, LayerInfo*,
-                                               AnimatorScope*) const;
+    sk_sp<sksg::RenderNode> attachImageLayer  (const skjson::ObjectValue&, LayerInfo*) const;
+    sk_sp<sksg::RenderNode> attachNullLayer   (const skjson::ObjectValue&, LayerInfo*) const;
+    sk_sp<sksg::RenderNode> attachPrecompLayer(const skjson::ObjectValue&, LayerInfo*) const;
+    sk_sp<sksg::RenderNode> attachShapeLayer  (const skjson::ObjectValue&, LayerInfo*) const;
+    sk_sp<sksg::RenderNode> attachSolidLayer  (const skjson::ObjectValue&, LayerInfo*) const;
+    sk_sp<sksg::RenderNode> attachTextLayer   (const skjson::ObjectValue&, LayerInfo*) const;
 
     bool dispatchColorProperty(const sk_sp<sksg::Color>&) const;
     bool dispatchOpacityProperty(const sk_sp<sksg::OpacityEffect>&) const;
@@ -191,6 +206,7 @@
     const SkSize               fSize;
     const float                fDuration,
                                fFrameRate;
+    mutable AnimatorScope*     fCurrentAnimatorScope;
     mutable const char*        fPropertyObserverContext;
     mutable bool               fHasNontrivialBlending : 1;
 
@@ -250,7 +266,6 @@
 
     sk_sp<sksg::Transform> attachTransformNode(const skjson::ObjectValue& jlayer,
                                                const AnimationBuilder* abuilder,
-                                               AnimatorScope*,
                                                sk_sp<sksg::Transform> parent_transform,
                                                TransformType type) const;
 
diff --git a/modules/skottie/src/effects/DropShadowEffect.cpp b/modules/skottie/src/effects/DropShadowEffect.cpp
index de5d3e8..6f86db2 100644
--- a/modules/skottie/src/effects/DropShadowEffect.cpp
+++ b/modules/skottie/src/effects/DropShadowEffect.cpp
@@ -69,27 +69,27 @@
     auto shadow_effect  = sksg::DropShadowImageFilter::Make();
     auto shadow_adapter = sk_make_sp<DropShadowAdapter>(shadow_effect);
 
-    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kShadowColor_Index), fScope,
+    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kShadowColor_Index),
         [shadow_adapter](const VectorValue& c) {
             shadow_adapter->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOpacity_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOpacity_Index),
         [shadow_adapter](const ScalarValue& o) {
             shadow_adapter->setOpacity(o);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kDirection_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kDirection_Index),
         [shadow_adapter](const ScalarValue& d) {
             shadow_adapter->setDirection(d);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kDistance_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kDistance_Index),
         [shadow_adapter](const ScalarValue& d) {
             shadow_adapter->setDistance(d);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kSoftness_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kSoftness_Index),
         [shadow_adapter](const ScalarValue& s) {
             shadow_adapter->setSoftness(s);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kShadowOnly_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kShadowOnly_Index),
         [shadow_adapter](const ScalarValue& s) {
             shadow_adapter->setShadowOnly(SkToBool(s));
         });
diff --git a/modules/skottie/src/effects/Effects.cpp b/modules/skottie/src/effects/Effects.cpp
index a141812..4990d9d 100644
--- a/modules/skottie/src/effects/Effects.cpp
+++ b/modules/skottie/src/effects/Effects.cpp
@@ -14,11 +14,9 @@
 namespace skottie {
 namespace internal {
 
-EffectBuilder::EffectBuilder(const AnimationBuilder* abuilder, const SkSize& layer_size,
-                             AnimatorScope* ascope)
+EffectBuilder::EffectBuilder(const AnimationBuilder* abuilder, const SkSize& layer_size)
     : fBuilder(abuilder)
-    , fLayerSize(layer_size)
-    , fScope(ascope) {}
+    , fLayerSize(layer_size) {}
 
 EffectBuilder::EffectBuilderT EffectBuilder::findBuilder(const skjson::ObjectValue& jeffect) const {
     // First, try assigned types.
diff --git a/modules/skottie/src/effects/Effects.h b/modules/skottie/src/effects/Effects.h
index 4e0331c..fd76002 100644
--- a/modules/skottie/src/effects/Effects.h
+++ b/modules/skottie/src/effects/Effects.h
@@ -22,7 +22,7 @@
 
 class EffectBuilder final : public SkNoncopyable {
 public:
-    EffectBuilder(const AnimationBuilder*, const SkSize&, AnimatorScope*);
+    EffectBuilder(const AnimationBuilder*, const SkSize&);
 
     sk_sp<sksg::RenderNode> attachEffects(const skjson::ArrayValue&,
                                           sk_sp<sksg::RenderNode>) const;
@@ -62,7 +62,6 @@
 
     const AnimationBuilder*   fBuilder;
     const SkSize              fLayerSize;
-    AnimatorScope*            fScope;
 };
 
 
diff --git a/modules/skottie/src/effects/FillEffect.cpp b/modules/skottie/src/effects/FillEffect.cpp
index 9a15618..c1ad9f3 100644
--- a/modules/skottie/src/effects/FillEffect.cpp
+++ b/modules/skottie/src/effects/FillEffect.cpp
@@ -38,12 +38,12 @@
     if (!color_prop || !opacity_prop) {
         return nullptr;
     }
-    sk_sp<sksg::Color> color_node = fBuilder->attachColor(*color_prop, fScope, "v");
+    sk_sp<sksg::Color> color_node = fBuilder->attachColor(*color_prop, "v");
     if (!color_node) {
         return nullptr;
     }
 
-    fBuilder->bindProperty<ScalarValue>((*opacity_prop)["v"], fScope,
+    fBuilder->bindProperty<ScalarValue>((*opacity_prop)["v"],
         [color_node](const ScalarValue& o) {
             const auto c = color_node->getColor();
             const auto a = sk_float_round2int_no_saturate(SkTPin(o, 0.0f, 1.0f) * 255);
diff --git a/modules/skottie/src/effects/GaussianBlurEffect.cpp b/modules/skottie/src/effects/GaussianBlurEffect.cpp
index 1a18754..39d8c59 100644
--- a/modules/skottie/src/effects/GaussianBlurEffect.cpp
+++ b/modules/skottie/src/effects/GaussianBlurEffect.cpp
@@ -90,15 +90,15 @@
     auto blur_effect   = sksg::BlurImageFilter::Make();
     auto blur_addapter = sk_make_sp<GaussianBlurEffectAdapter>(blur_effect);
 
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kBlurriness_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kBlurriness_Index),
         [blur_addapter](const ScalarValue& b) {
             blur_addapter->setBlurriness(b);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kDimensions_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kDimensions_Index),
         [blur_addapter](const ScalarValue& d) {
             blur_addapter->setDimensions(d);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRepeatEdge_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRepeatEdge_Index),
         [blur_addapter](const ScalarValue& r) {
             blur_addapter->setRepeatEdge(r);
         });
diff --git a/modules/skottie/src/effects/GradientEffect.cpp b/modules/skottie/src/effects/GradientEffect.cpp
index a4e67b1..2165128 100644
--- a/modules/skottie/src/effects/GradientEffect.cpp
+++ b/modules/skottie/src/effects/GradientEffect.cpp
@@ -110,31 +110,31 @@
 
     auto adapter = sk_make_sp<GradientRampEffectAdapter>(std::move(layer));
 
-    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kStartPoint_Index), fScope,
+    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kStartPoint_Index),
         [adapter](const VectorValue& p0) {
             adapter->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(p0));
         });
-    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kEndPoint_Index), fScope,
+    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kEndPoint_Index),
         [adapter](const VectorValue& p1) {
             adapter->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(p1));
         });
-    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kStartColor_Index), fScope,
+    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kStartColor_Index),
         [adapter](const VectorValue& c0) {
             adapter->setStartColor(ValueTraits<VectorValue>::As<SkColor>(c0));
         });
-    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kEndColor_Index), fScope,
+    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kEndColor_Index),
         [adapter](const VectorValue& c1) {
             adapter->setEndColor(ValueTraits<VectorValue>::As<SkColor>(c1));
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRampShape_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRampShape_Index),
         [adapter](const ScalarValue& shape) {
             adapter->setShape(shape);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kBlendRatio_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kBlendRatio_Index),
         [adapter](const ScalarValue& blend) {
             adapter->setBlend(blend);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRampScatter_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRampScatter_Index),
         [adapter](const ScalarValue& scatter) {
             adapter->setScatter(scatter);
         });
diff --git a/modules/skottie/src/effects/LevelsEffect.cpp b/modules/skottie/src/effects/LevelsEffect.cpp
index bc9ef35..8184341 100644
--- a/modules/skottie/src/effects/LevelsEffect.cpp
+++ b/modules/skottie/src/effects/LevelsEffect.cpp
@@ -150,36 +150,36 @@
 
     auto adapter = sk_make_sp<LevelsEffectAdapter>(std::move(layer));
 
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kChannel_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kChannel_Index),
         [adapter](const ScalarValue& channel) {
             adapter->setChannel(channel);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kInputBlack_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kInputBlack_Index),
         [adapter](const ScalarValue& ib) {
             adapter->setInBlack(ib);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kInputWhite_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kInputWhite_Index),
         [adapter](const ScalarValue& iw) {
             adapter->setInWhite(iw);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputBlack_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputBlack_Index),
         [adapter](const ScalarValue& ob) {
             adapter->setOutBlack(ob);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputWhite_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputWhite_Index),
         [adapter](const ScalarValue& ow) {
             adapter->setOutWhite(ow);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kGamma_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kGamma_Index),
         [adapter](const ScalarValue& g) {
             adapter->setGamma(g);
         });
 
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kClipToOutBlack_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kClipToOutBlack_Index),
         [adapter](const ScalarValue& cb) {
             adapter->setClipBlack(cb);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kClipToOutWhite_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kClipToOutWhite_Index),
         [adapter](const ScalarValue& cw) {
             adapter->setClipWhite(cw);
         });
diff --git a/modules/skottie/src/effects/LinearWipeEffect.cpp b/modules/skottie/src/effects/LinearWipeEffect.cpp
index 42245e8..18b2e73 100644
--- a/modules/skottie/src/effects/LinearWipeEffect.cpp
+++ b/modules/skottie/src/effects/LinearWipeEffect.cpp
@@ -104,15 +104,15 @@
 
     auto adapter = sk_make_sp<LinearWipeAdapter>(std::move(layer), fLayerSize);
 
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kCompletion_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kCompletion_Index),
         [adapter](const ScalarValue& c) {
             adapter->setCompletion(c);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kAngle_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kAngle_Index),
         [adapter](const ScalarValue& a) {
             adapter->setAngle(a);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kFeather_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kFeather_Index),
         [adapter](const ScalarValue& f) {
             adapter->setFeather(f);
         });
diff --git a/modules/skottie/src/effects/MotionTileEffect.cpp b/modules/skottie/src/effects/MotionTileEffect.cpp
index cee630b..1b6fbe0 100644
--- a/modules/skottie/src/effects/MotionTileEffect.cpp
+++ b/modules/skottie/src/effects/MotionTileEffect.cpp
@@ -183,35 +183,35 @@
 
     auto tiler = sk_make_sp<TileRenderNode>(fLayerSize, std::move(layer));
 
-    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kTileCenter_Index), fScope,
+    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kTileCenter_Index),
         [tiler](const VectorValue& tc) {
             tiler->setTileCenter(ValueTraits<VectorValue>::As<SkPoint>(tc));
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kTileWidth_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kTileWidth_Index),
         [tiler](const ScalarValue& tw) {
             tiler->setTileWidth(tw);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kTileHeight_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kTileHeight_Index),
         [tiler](const ScalarValue& th) {
             tiler->setTileHeight(th);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputWidth_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputWidth_Index),
         [tiler](const ScalarValue& ow) {
             tiler->setOutputWidth(ow);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputHeight_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputHeight_Index),
         [tiler](const ScalarValue& oh) {
             tiler->setOutputHeight(oh);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kMirrorEdges_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kMirrorEdges_Index),
         [tiler](const ScalarValue& me) {
             tiler->setMirrorEdges(SkScalarRoundToInt(me));
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kPhase_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kPhase_Index),
         [tiler](const ScalarValue& ph) {
             tiler->setPhase(ph);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kHorizontalPhaseShift_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kHorizontalPhaseShift_Index),
         [tiler](const ScalarValue& hp) {
             tiler->setHorizontalPhase(SkScalarRoundToInt(hp));
         });
diff --git a/modules/skottie/src/effects/RadialWipeEffect.cpp b/modules/skottie/src/effects/RadialWipeEffect.cpp
index 1577b05..7732160 100644
--- a/modules/skottie/src/effects/RadialWipeEffect.cpp
+++ b/modules/skottie/src/effects/RadialWipeEffect.cpp
@@ -145,23 +145,23 @@
 
     auto wiper = sk_make_sp<RWipeRenderNode>(std::move(layer));
 
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kCompletion_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kCompletion_Index),
         [wiper](const ScalarValue& c) {
             wiper->setCompletion(c);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kStartAngle_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kStartAngle_Index),
         [wiper](const ScalarValue& sa) {
             wiper->setStartAngle(sa);
         });
-    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kWipeCenter_Index), fScope,
+    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kWipeCenter_Index),
         [wiper](const VectorValue& c) {
             wiper->setWipeCenter(ValueTraits<VectorValue>::As<SkPoint>(c));
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kWipe_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kWipe_Index),
         [wiper](const ScalarValue& w) {
             wiper->setWipe(w);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kFeather_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kFeather_Index),
         [wiper](const ScalarValue& f) {
             wiper->setFeather(f);
         });
diff --git a/modules/skottie/src/effects/TintEffect.cpp b/modules/skottie/src/effects/TintEffect.cpp
index 69073af..0c12d30 100644
--- a/modules/skottie/src/effects/TintEffect.cpp
+++ b/modules/skottie/src/effects/TintEffect.cpp
@@ -40,13 +40,13 @@
 
     auto tint_node =
             sksg::GradientColorFilter::Make(std::move(layer),
-                                            fBuilder->attachColor(*color0_prop, fScope, "v"),
-                                            fBuilder->attachColor(*color1_prop, fScope, "v"));
+                                            fBuilder->attachColor(*color0_prop, "v"),
+                                            fBuilder->attachColor(*color1_prop, "v"));
     if (!tint_node) {
         return nullptr;
     }
 
-    fBuilder->bindProperty<ScalarValue>((*amount_prop)["v"], fScope,
+    fBuilder->bindProperty<ScalarValue>((*amount_prop)["v"],
         [tint_node](const ScalarValue& w) {
             tint_node->setWeight(w / 100); // 100-based
         });
diff --git a/modules/skottie/src/effects/TransformEffect.cpp b/modules/skottie/src/effects/TransformEffect.cpp
index 5904b9d..e75c061 100644
--- a/modules/skottie/src/effects/TransformEffect.cpp
+++ b/modules/skottie/src/effects/TransformEffect.cpp
@@ -62,36 +62,36 @@
     auto t_adapter = sk_make_sp<TransformAdapter2D>(matrix);
     auto s_adapter = sk_make_sp<ScaleAdapter>(t_adapter);
 
-    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kAnchorPoint_Index), fScope,
+    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kAnchorPoint_Index),
         [t_adapter](const VectorValue& ap) {
             t_adapter->setAnchorPoint(ValueTraits<VectorValue>::As<SkPoint>(ap));
         });
-    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kPosition_Index), fScope,
+    fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kPosition_Index),
         [t_adapter](const VectorValue& p) {
             t_adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRotation_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRotation_Index),
         [t_adapter](const ScalarValue& r) {
             t_adapter->setRotation(r);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kSkew_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kSkew_Index),
         [t_adapter](const ScalarValue& s) {
             t_adapter->setSkew(s);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kSkewAxis_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kSkewAxis_Index),
         [t_adapter](const ScalarValue& sa) {
             t_adapter->setSkewAxis(sa);
         });
 
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kUniformScale_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kUniformScale_Index),
         [s_adapter](const ScalarValue& u) {
             s_adapter->setIsUniform(SkScalarRoundToInt(u));
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kScaleHeight_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kScaleHeight_Index),
         [s_adapter](const ScalarValue& sh) {
             s_adapter->setScaleHeight(sh);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kScaleWidth_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kScaleWidth_Index),
         [s_adapter](const ScalarValue& sw) {
             s_adapter->setScaleWidth(sw);
         });
@@ -99,7 +99,7 @@
     auto opacity_node = sksg::OpacityEffect::Make(sksg::TransformEffect::Make(std::move(layer),
                                                                               std::move(matrix)));
 
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOpacity_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOpacity_Index),
         [opacity_node](const ScalarValue& o) {
             opacity_node->setOpacity(o * 0.01f);
         });
diff --git a/modules/skottie/src/effects/TritoneEffect.cpp b/modules/skottie/src/effects/TritoneEffect.cpp
index 4ef60d3..7427f82 100644
--- a/modules/skottie/src/effects/TritoneEffect.cpp
+++ b/modules/skottie/src/effects/TritoneEffect.cpp
@@ -41,14 +41,14 @@
 
     auto tritone_node =
             sksg::GradientColorFilter::Make(std::move(layer), {
-                                            fBuilder->attachColor(*locolor_prop, fScope, "v"),
-                                            fBuilder->attachColor(*micolor_prop, fScope, "v"),
-                                            fBuilder->attachColor(*hicolor_prop, fScope, "v") });
+                                            fBuilder->attachColor(*locolor_prop, "v"),
+                                            fBuilder->attachColor(*micolor_prop, "v"),
+                                            fBuilder->attachColor(*hicolor_prop, "v") });
     if (!tritone_node) {
         return nullptr;
     }
 
-    fBuilder->bindProperty<ScalarValue>((*blend_prop)["v"], fScope,
+    fBuilder->bindProperty<ScalarValue>((*blend_prop)["v"],
         [tritone_node](const ScalarValue& w) {
             tritone_node->setWeight((100 - w) / 100); // 100-based, inverted (!?).
         });
diff --git a/modules/skottie/src/effects/VenetianBlindsEffect.cpp b/modules/skottie/src/effects/VenetianBlindsEffect.cpp
index 3b770c5..880eb07 100644
--- a/modules/skottie/src/effects/VenetianBlindsEffect.cpp
+++ b/modules/skottie/src/effects/VenetianBlindsEffect.cpp
@@ -146,19 +146,19 @@
 
     auto adapter = sk_make_sp<VenetialBlindsAdapter>(std::move(layer), fLayerSize);
 
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kCompletion_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kCompletion_Index),
         [adapter](const ScalarValue& c) {
             adapter->setCompletion(c);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kDirection_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kDirection_Index),
         [adapter](const ScalarValue& d) {
             adapter->setDirection(d);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kWidth_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kWidth_Index),
         [adapter](const ScalarValue& w) {
             adapter->setWidth(w);
         });
-    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kFeather_Index), fScope,
+    fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kFeather_Index),
         [adapter](const ScalarValue& f) {
             adapter->setFeather(f);
         });
diff --git a/modules/skottie/src/layers/ImageLayer.cpp b/modules/skottie/src/layers/ImageLayer.cpp
index ca21efe..56bce1d 100644
--- a/modules/skottie/src/layers/ImageLayer.cpp
+++ b/modules/skottie/src/layers/ImageLayer.cpp
@@ -42,8 +42,7 @@
 }
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachImageAsset(const skjson::ObjectValue& jimage,
-                                                           LayerInfo* layer_info,
-                                                           AnimatorScope* ascope) const {
+                                                           LayerInfo* layer_info) const {
     const auto* asset_info = this->loadImageAsset(jimage);
     if (!asset_info) {
         return nullptr;
@@ -80,10 +79,10 @@
                                   fTimeScale;
         };
 
-        ascope->push_back(sk_make_sp<MultiFrameAnimator>(asset_info->fAsset,
-                                                         image_node,
-                                                         -layer_info->fInPoint,
-                                                         1 / fFrameRate));
+        fCurrentAnimatorScope->push_back(sk_make_sp<MultiFrameAnimator>(asset_info->fAsset,
+                                                                        image_node,
+                                                                        -layer_info->fInPoint,
+                                                                        1 / fFrameRate));
     }
 
     const auto asset_size = SkISize::Make(
@@ -105,11 +104,10 @@
 }
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachImageLayer(const skjson::ObjectValue& jlayer,
-                                                           LayerInfo* layer_info,
-                                                           AnimatorScope* ascope) const {
-    return this->attachAssetRef(jlayer, ascope,
-        [this, &layer_info] (const skjson::ObjectValue& jimage, AnimatorScope* ascope) {
-            return this->attachImageAsset(jimage, layer_info, ascope);
+                                                           LayerInfo* layer_info) const {
+    return this->attachAssetRef(jlayer,
+        [this, &layer_info] (const skjson::ObjectValue& jimage) {
+            return this->attachImageAsset(jimage, layer_info);
         });
 }
 
diff --git a/modules/skottie/src/layers/NullLayer.cpp b/modules/skottie/src/layers/NullLayer.cpp
index 921dc6e..cb93ce6 100644
--- a/modules/skottie/src/layers/NullLayer.cpp
+++ b/modules/skottie/src/layers/NullLayer.cpp
@@ -13,7 +13,7 @@
 namespace internal {
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachNullLayer(const skjson::ObjectValue& layer,
-                                                          LayerInfo*, AnimatorScope*) const {
+                                                          LayerInfo*) const {
     // Null layers are used solely to drive dependent transforms,
     // but we use free-floating sksg::Matrices for that purpose.
     return nullptr;
diff --git a/modules/skottie/src/layers/PrecompLayer.cpp b/modules/skottie/src/layers/PrecompLayer.cpp
index c22a689..eaf6ccd 100644
--- a/modules/skottie/src/layers/PrecompLayer.cpp
+++ b/modules/skottie/src/layers/PrecompLayer.cpp
@@ -18,8 +18,7 @@
 namespace internal {
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachPrecompLayer(const skjson::ObjectValue& jlayer,
-                                                             LayerInfo* layer_info,
-                                                             AnimatorScope* ascope) const {
+                                                             LayerInfo* layer_info) const {
     const skjson::ObjectValue* time_remap = jlayer["tm"];
     // Empirically, a time mapper supersedes start/stretch.
     const auto start_time = time_remap ? 0.0f : ParseDefault<float>(jlayer["st"], 0.0f),
@@ -32,13 +31,15 @@
     layer_info->fSize = SkSize::Make(ParseDefault<float>(jlayer["w"], 0.0f),
                                      ParseDefault<float>(jlayer["h"], 0.0f));
 
-    AnimatorScope local_animators;
+    SkTLazy<AutoScope> local_scope;
+    if (requires_time_mapping) {
+        local_scope.init(this);
+    }
+
     auto precomp_layer = this->attachAssetRef(jlayer,
-                                              requires_time_mapping ? &local_animators : ascope,
-                                              [this] (const skjson::ObjectValue& jcomp,
-                                                      AnimatorScope* ascope) {
-                                                  return this->attachComposition(jcomp, ascope);
-                                              });
+        [this] (const skjson::ObjectValue& jcomp) {
+            return this->attachComposition(jcomp);
+        });
 
     // Applies a bias/scale/remap t-adjustment to child animators.
     class CompTimeMapper final : public sksg::GroupAnimator {
@@ -70,16 +71,16 @@
     if (requires_time_mapping) {
         const auto t_bias  = -start_time,
                    t_scale = sk_ieee_float_divide(1, stretch_time);
-        auto time_mapper = sk_make_sp<CompTimeMapper>(std::move(local_animators), t_bias,
+        auto time_mapper = sk_make_sp<CompTimeMapper>(local_scope->release(), t_bias,
                                                       sk_float_isfinite(t_scale) ? t_scale : 0);
         if (time_remap) {
             auto  frame_rate = fFrameRate;
-            this->bindProperty<ScalarValue>(*time_remap, ascope,
+            this->bindProperty<ScalarValue>(*time_remap,
                     [time_mapper, frame_rate](const ScalarValue& t) {
                         time_mapper->remapTime(t * frame_rate);
                     });
         }
-        ascope->push_back(std::move(time_mapper));
+        fCurrentAnimatorScope->push_back(std::move(time_mapper));
     }
 
     return precomp_layer;
diff --git a/modules/skottie/src/layers/ShapeLayer.cpp b/modules/skottie/src/layers/ShapeLayer.cpp
index 7901830..3876f2f 100644
--- a/modules/skottie/src/layers/ShapeLayer.cpp
+++ b/modules/skottie/src/layers/ShapeLayer.cpp
@@ -34,14 +34,12 @@
 namespace {
 
 sk_sp<sksg::GeometryNode> AttachPathGeometry(const skjson::ObjectValue& jpath,
-                                             const AnimationBuilder* abuilder,
-                                             AnimatorScope* ascope) {
-    return abuilder->attachPath(jpath["ks"], ascope);
+                                             const AnimationBuilder* abuilder) {
+    return abuilder->attachPath(jpath["ks"]);
 }
 
 sk_sp<sksg::GeometryNode> AttachRRectGeometry(const skjson::ObjectValue& jrect,
-                                              const AnimationBuilder* abuilder,
-                                              AnimatorScope* ascope) {
+                                              const AnimationBuilder* abuilder) {
     auto rect_node = sksg::RRect::Make();
     rect_node->setDirection(ParseDefault(jrect["d"], -1) == 3 ? SkPath::kCCW_Direction
                                                               : SkPath::kCW_Direction);
@@ -49,15 +47,15 @@
 
     auto adapter = sk_make_sp<RRectAdapter>(rect_node);
 
-    auto p_attached = abuilder->bindProperty<VectorValue>(jrect["p"], ascope,
+    auto p_attached = abuilder->bindProperty<VectorValue>(jrect["p"],
         [adapter](const VectorValue& p) {
             adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
         });
-    auto s_attached = abuilder->bindProperty<VectorValue>(jrect["s"], ascope,
+    auto s_attached = abuilder->bindProperty<VectorValue>(jrect["s"],
         [adapter](const VectorValue& s) {
             adapter->setSize(ValueTraits<VectorValue>::As<SkSize>(s));
         });
-    auto r_attached = abuilder->bindProperty<ScalarValue>(jrect["r"], ascope,
+    auto r_attached = abuilder->bindProperty<ScalarValue>(jrect["r"],
         [adapter](const ScalarValue& r) {
             adapter->setRadius(SkSize::Make(r, r));
         });
@@ -70,8 +68,7 @@
 }
 
 sk_sp<sksg::GeometryNode> AttachEllipseGeometry(const skjson::ObjectValue& jellipse,
-                                                const AnimationBuilder* abuilder,
-                                                AnimatorScope* ascope) {
+                                                const AnimationBuilder* abuilder) {
     auto rect_node = sksg::RRect::Make();
     rect_node->setDirection(ParseDefault(jellipse["d"], -1) == 3 ? SkPath::kCCW_Direction
                                                                  : SkPath::kCW_Direction);
@@ -79,11 +76,11 @@
 
     auto adapter = sk_make_sp<RRectAdapter>(rect_node);
 
-    auto p_attached = abuilder->bindProperty<VectorValue>(jellipse["p"], ascope,
+    auto p_attached = abuilder->bindProperty<VectorValue>(jellipse["p"],
         [adapter](const VectorValue& p) {
             adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
         });
-    auto s_attached = abuilder->bindProperty<VectorValue>(jellipse["s"], ascope,
+    auto s_attached = abuilder->bindProperty<VectorValue>(jellipse["s"],
         [adapter](const VectorValue& s) {
             const auto sz = ValueTraits<VectorValue>::As<SkSize>(s);
             adapter->setSize(sz);
@@ -98,8 +95,7 @@
 }
 
 sk_sp<sksg::GeometryNode> AttachPolystarGeometry(const skjson::ObjectValue& jstar,
-                                                 const AnimationBuilder* abuilder,
-                                                 AnimatorScope* ascope) {
+                                                 const AnimationBuilder* abuilder) {
     static constexpr PolyStarAdapter::Type gTypes[] = {
         PolyStarAdapter::Type::kStar, // "sy": 1
         PolyStarAdapter::Type::kPoly, // "sy": 2
@@ -114,31 +110,31 @@
     auto path_node = sksg::Path::Make();
     auto adapter = sk_make_sp<PolyStarAdapter>(path_node, gTypes[type]);
 
-    abuilder->bindProperty<VectorValue>(jstar["p"], ascope,
+    abuilder->bindProperty<VectorValue>(jstar["p"],
         [adapter](const VectorValue& p) {
             adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
         });
-    abuilder->bindProperty<ScalarValue>(jstar["pt"], ascope,
+    abuilder->bindProperty<ScalarValue>(jstar["pt"],
         [adapter](const ScalarValue& pt) {
             adapter->setPointCount(pt);
         });
-    abuilder->bindProperty<ScalarValue>(jstar["ir"], ascope,
+    abuilder->bindProperty<ScalarValue>(jstar["ir"],
         [adapter](const ScalarValue& ir) {
             adapter->setInnerRadius(ir);
         });
-    abuilder->bindProperty<ScalarValue>(jstar["or"], ascope,
+    abuilder->bindProperty<ScalarValue>(jstar["or"],
         [adapter](const ScalarValue& otr) {
             adapter->setOuterRadius(otr);
         });
-    abuilder->bindProperty<ScalarValue>(jstar["is"], ascope,
+    abuilder->bindProperty<ScalarValue>(jstar["is"],
         [adapter](const ScalarValue& is) {
             adapter->setInnerRoundness(is);
         });
-    abuilder->bindProperty<ScalarValue>(jstar["os"], ascope,
+    abuilder->bindProperty<ScalarValue>(jstar["os"],
         [adapter](const ScalarValue& os) {
             adapter->setOuterRoundness(os);
         });
-    abuilder->bindProperty<ScalarValue>(jstar["r"], ascope,
+    abuilder->bindProperty<ScalarValue>(jstar["r"],
         [adapter](const ScalarValue& r) {
             adapter->setRotation(r);
         });
@@ -147,7 +143,7 @@
 }
 
 sk_sp<sksg::ShaderPaint> AttachGradient(const skjson::ObjectValue& jgrad,
-                                        const AnimationBuilder* abuilder, AnimatorScope* ascope) {
+                                        const AnimationBuilder* abuilder) {
     const skjson::ObjectValue* stops = jgrad["g"];
     if (!stops)
         return nullptr;
@@ -171,15 +167,15 @@
         gradient_node = std::move(radial_node);
     }
 
-    abuilder->bindProperty<VectorValue>((*stops)["k"], ascope,
+    abuilder->bindProperty<VectorValue>((*stops)["k"],
         [adapter](const VectorValue& stops) {
             adapter->setColorStops(stops);
         });
-    abuilder->bindProperty<VectorValue>(jgrad["s"], ascope,
+    abuilder->bindProperty<VectorValue>(jgrad["s"],
         [adapter](const VectorValue& s) {
             adapter->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(s));
         });
-    abuilder->bindProperty<VectorValue>(jgrad["e"], ascope,
+    abuilder->bindProperty<VectorValue>(jgrad["e"],
         [adapter](const VectorValue& e) {
             adapter->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(e));
         });
@@ -188,30 +184,30 @@
 }
 
 sk_sp<sksg::PaintNode> AttachPaint(const skjson::ObjectValue& jpaint,
-                                   const AnimationBuilder* abuilder, AnimatorScope* ascope,
+                                   const AnimationBuilder* abuilder,
                                    sk_sp<sksg::PaintNode> paint_node) {
     if (paint_node) {
         paint_node->setAntiAlias(true);
 
-        abuilder->bindProperty<ScalarValue>(jpaint["o"], ascope,
+        abuilder->bindProperty<ScalarValue>(jpaint["o"],
             [paint_node](const ScalarValue& o) {
                 // BM opacity is [0..100]
                 paint_node->setOpacity(o * 0.01f);
-        });
+            });
     }
 
     return paint_node;
 }
 
 sk_sp<sksg::PaintNode> AttachStroke(const skjson::ObjectValue& jstroke,
-                                    const AnimationBuilder* abuilder, AnimatorScope* ascope,
+                                    const AnimationBuilder* abuilder,
                                     sk_sp<sksg::PaintNode> stroke_node) {
     if (!stroke_node)
         return nullptr;
 
     stroke_node->setStyle(SkPaint::kStroke_Style);
 
-    abuilder->bindProperty<ScalarValue>(jstroke["w"], ascope,
+    abuilder->bindProperty<ScalarValue>(jstroke["w"],
         [stroke_node](const ScalarValue& w) {
             stroke_node->setStrokeWidth(w);
         });
@@ -238,29 +234,25 @@
 }
 
 sk_sp<sksg::PaintNode> AttachColorFill(const skjson::ObjectValue& jfill,
-                                       const AnimationBuilder* abuilder, AnimatorScope* ascope) {
-    return AttachPaint(jfill, abuilder, ascope, abuilder->attachColor(jfill, ascope, "c"));
+                                       const AnimationBuilder* abuilder) {
+    return AttachPaint(jfill, abuilder, abuilder->attachColor(jfill, "c"));
 }
 
 sk_sp<sksg::PaintNode> AttachGradientFill(const skjson::ObjectValue& jfill,
-                                          const AnimationBuilder* abuilder, AnimatorScope* ascope) {
-    return AttachPaint(jfill, abuilder, ascope, AttachGradient(jfill, abuilder, ascope));
+                                          const AnimationBuilder* abuilder) {
+    return AttachPaint(jfill, abuilder, AttachGradient(jfill, abuilder));
 }
 
 sk_sp<sksg::PaintNode> AttachColorStroke(const skjson::ObjectValue& jstroke,
-                                         const AnimationBuilder* abuilder,
-                                         AnimatorScope* ascope) {
-    return AttachStroke(jstroke, abuilder, ascope,
-                        AttachPaint(jstroke, abuilder, ascope,
-                                    abuilder->attachColor(jstroke, ascope, "c")));
+                                         const AnimationBuilder* abuilder) {
+    return AttachStroke(jstroke, abuilder, AttachPaint(jstroke, abuilder,
+                                                       abuilder->attachColor(jstroke, "c")));
 }
 
 sk_sp<sksg::PaintNode> AttachGradientStroke(const skjson::ObjectValue& jstroke,
-                                            const AnimationBuilder* abuilder,
-                                            AnimatorScope* ascope) {
-    return AttachStroke(jstroke, abuilder, ascope,
-                        AttachPaint(jstroke, abuilder, ascope,
-                                    AttachGradient(jstroke, abuilder, ascope)));
+                                            const AnimationBuilder* abuilder) {
+    return AttachStroke(jstroke, abuilder, AttachPaint(jstroke, abuilder,
+                                                       AttachGradient(jstroke, abuilder)));
 }
 
 sk_sp<sksg::Merge> Merge(std::vector<sk_sp<sksg::GeometryNode>>&& geos, sksg::Merge::Mode mode) {
@@ -276,7 +268,7 @@
 }
 
 std::vector<sk_sp<sksg::GeometryNode>> AttachMergeGeometryEffect(
-        const skjson::ObjectValue& jmerge, const AnimationBuilder*, AnimatorScope*,
+        const skjson::ObjectValue& jmerge, const AnimationBuilder*,
         std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
     static constexpr sksg::Merge::Mode gModes[] = {
         sksg::Merge::Mode::kMerge,      // "mm": 1
@@ -296,7 +288,7 @@
 }
 
 std::vector<sk_sp<sksg::GeometryNode>> AttachTrimGeometryEffect(
-        const skjson::ObjectValue& jtrim, const AnimationBuilder* abuilder, AnimatorScope* ascope,
+        const skjson::ObjectValue& jtrim, const AnimationBuilder* abuilder,
         std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
 
     enum class Mode {
@@ -321,15 +313,15 @@
         trimmed.push_back(trimEffect);
 
         const auto adapter = sk_make_sp<TrimEffectAdapter>(std::move(trimEffect));
-        abuilder->bindProperty<ScalarValue>(jtrim["s"], ascope,
+        abuilder->bindProperty<ScalarValue>(jtrim["s"],
             [adapter](const ScalarValue& s) {
                 adapter->setStart(s);
             });
-        abuilder->bindProperty<ScalarValue>(jtrim["e"], ascope,
+        abuilder->bindProperty<ScalarValue>(jtrim["e"],
             [adapter](const ScalarValue& e) {
                 adapter->setEnd(e);
             });
-        abuilder->bindProperty<ScalarValue>(jtrim["o"], ascope,
+        abuilder->bindProperty<ScalarValue>(jtrim["o"],
             [adapter](const ScalarValue& o) {
                 adapter->setOffset(o);
             });
@@ -339,7 +331,7 @@
 }
 
 std::vector<sk_sp<sksg::GeometryNode>> AttachRoundGeometryEffect(
-        const skjson::ObjectValue& jtrim, const AnimationBuilder* abuilder, AnimatorScope* ascope,
+        const skjson::ObjectValue& jtrim, const AnimationBuilder* abuilder,
         std::vector<sk_sp<sksg::GeometryNode>>&& geos) {
 
     std::vector<sk_sp<sksg::GeometryNode>> rounded;
@@ -349,7 +341,7 @@
         const auto roundEffect = sksg::RoundEffect::Make(std::move(g));
         rounded.push_back(roundEffect);
 
-        abuilder->bindProperty<ScalarValue>(jtrim["r"], ascope,
+        abuilder->bindProperty<ScalarValue>(jtrim["r"],
             [roundEffect](const ScalarValue& r) {
                 roundEffect->setRadius(r);
             });
@@ -361,7 +353,6 @@
 std::vector<sk_sp<sksg::RenderNode>> AttachRepeaterDrawEffect(
         const skjson::ObjectValue& jrepeater,
         const AnimationBuilder* abuilder,
-        AnimatorScope* ascope,
         std::vector<sk_sp<sksg::RenderNode>>&& draws) {
 
     std::vector<sk_sp<sksg::RenderNode>> repeater_draws;
@@ -381,35 +372,35 @@
         auto adapter = sk_make_sp<RepeaterAdapter>(std::move(repeater_node),
                                                    repeater_composite);
 
-        abuilder->bindProperty<ScalarValue>(jrepeater["c"], ascope,
+        abuilder->bindProperty<ScalarValue>(jrepeater["c"],
             [adapter](const ScalarValue& c) {
                 adapter->setCount(c);
             });
-        abuilder->bindProperty<ScalarValue>(jrepeater["o"], ascope,
+        abuilder->bindProperty<ScalarValue>(jrepeater["o"],
             [adapter](const ScalarValue& o) {
                 adapter->setOffset(o);
             });
-        abuilder->bindProperty<VectorValue>((*jtransform)["a"], ascope,
+        abuilder->bindProperty<VectorValue>((*jtransform)["a"],
             [adapter](const VectorValue& a) {
                 adapter->setAnchorPoint(ValueTraits<VectorValue>::As<SkPoint>(a));
             });
-        abuilder->bindProperty<VectorValue>((*jtransform)["p"], ascope,
+        abuilder->bindProperty<VectorValue>((*jtransform)["p"],
             [adapter](const VectorValue& p) {
                 adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
             });
-        abuilder->bindProperty<VectorValue>((*jtransform)["s"], ascope,
+        abuilder->bindProperty<VectorValue>((*jtransform)["s"],
             [adapter](const VectorValue& s) {
                 adapter->setScale(ValueTraits<VectorValue>::As<SkVector>(s));
             });
-        abuilder->bindProperty<ScalarValue>((*jtransform)["r"], ascope,
+        abuilder->bindProperty<ScalarValue>((*jtransform)["r"],
             [adapter](const ScalarValue& r) {
                 adapter->setRotation(r);
             });
-        abuilder->bindProperty<ScalarValue>((*jtransform)["so"], ascope,
+        abuilder->bindProperty<ScalarValue>((*jtransform)["so"],
             [adapter](const ScalarValue& so) {
                 adapter->setStartOpacity(so);
             });
-        abuilder->bindProperty<ScalarValue>((*jtransform)["eo"], ascope,
+        abuilder->bindProperty<ScalarValue>((*jtransform)["eo"],
             [adapter](const ScalarValue& eo) {
                 adapter->setEndOpacity(eo);
             });
@@ -424,7 +415,7 @@
 }
 
 using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const skjson::ObjectValue&,
-                                                        const AnimationBuilder*, AnimatorScope*);
+                                                        const AnimationBuilder*);
 static constexpr GeometryAttacherT gGeometryAttachers[] = {
     AttachPathGeometry,
     AttachRRectGeometry,
@@ -433,7 +424,7 @@
 };
 
 using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const skjson::ObjectValue&,
-                                                  const AnimationBuilder*, AnimatorScope*);
+                                                  const AnimationBuilder*);
 static constexpr PaintAttacherT gPaintAttachers[] = {
     AttachColorFill,
     AttachColorStroke,
@@ -443,7 +434,7 @@
 
 using GeometryEffectAttacherT =
     std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&,
-                                               const AnimationBuilder*, AnimatorScope*,
+                                               const AnimationBuilder*,
                                                std::vector<sk_sp<sksg::GeometryNode>>&&);
 static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
     AttachMergeGeometryEffect,
@@ -453,7 +444,7 @@
 
 using DrawEffectAttacherT =
     std::vector<sk_sp<sksg::RenderNode>> (*)(const skjson::ObjectValue&,
-                                             const AnimationBuilder*, AnimatorScope*,
+                                             const AnimationBuilder*,
                                              std::vector<sk_sp<sksg::RenderNode>>&&);
 
 static constexpr DrawEffectAttacherT gDrawEffectAttachers[] = {
@@ -518,16 +509,13 @@
 } // namespace
 
 struct AnimationBuilder::AttachShapeContext {
-    AttachShapeContext(AnimatorScope* ascope,
-                       std::vector<sk_sp<sksg::GeometryNode>>* geos,
+    AttachShapeContext(std::vector<sk_sp<sksg::GeometryNode>>* geos,
                        std::vector<GeometryEffectRec>* effects,
                        size_t committedAnimators)
-        : fScope(ascope)
-        , fGeometryStack(geos)
+        : fGeometryStack(geos)
         , fGeometryEffectStack(effects)
         , fCommittedAnimators(committedAnimators) {}
 
-    AnimatorScope*                          fScope;
     std::vector<sk_sp<sksg::GeometryNode>>* fGeometryStack;
     std::vector<GeometryEffectRec>*         fGeometryEffectStack;
     size_t                                  fCommittedAnimators;
@@ -605,9 +593,7 @@
         switch (rec->fInfo.fShapeType) {
         case ShapeType::kGeometry: {
             SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryAttachers));
-            if (auto geo = gGeometryAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
-                                                                         this,
-                                                                         ctx->fScope)) {
+            if (auto geo = gGeometryAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this)) {
                 geos.push_back(std::move(geo));
             }
         } break;
@@ -617,7 +603,6 @@
             if (!geos.empty()) {
                 geos = gGeometryEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
                                                                            this,
-                                                                           ctx->fScope,
                                                                            std::move(geos));
             }
 
@@ -627,8 +612,7 @@
             ctx->fGeometryEffectStack->pop_back();
         } break;
         case ShapeType::kGroup: {
-            AttachShapeContext groupShapeCtx(ctx->fScope,
-                                             &geos,
+            AttachShapeContext groupShapeCtx(&geos,
                                              ctx->fGeometryEffectStack,
                                              ctx->fCommittedAnimators);
             if (auto subgroup = this->attachShape(rec->fJson["it"], &groupShapeCtx)) {
@@ -639,9 +623,7 @@
         } break;
         case ShapeType::kPaint: {
             SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
-            auto paint = gPaintAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
-                                                                    this,
-                                                                    ctx->fScope);
+            auto paint = gPaintAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this);
             if (!paint || geos.empty())
                 break;
 
@@ -650,7 +632,7 @@
             // Apply all pending effects from the stack.
             for (auto it = ctx->fGeometryEffectStack->rbegin();
                  it != ctx->fGeometryEffectStack->rend(); ++it) {
-                drawGeos = it->fAttach(it->fJson, this, ctx->fScope, std::move(drawGeos));
+                drawGeos = it->fAttach(it->fJson, this, std::move(drawGeos));
             }
 
             // If we still have multiple geos, reduce using 'merge'.
@@ -660,16 +642,15 @@
 
             SkASSERT(geo);
             add_draw(sksg::Draw::Make(std::move(geo), std::move(paint)), *rec);
-            ctx->fCommittedAnimators = ctx->fScope->size();
+            ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
         } break;
         case ShapeType::kDrawEffect: {
             SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gDrawEffectAttachers));
             if (!draws.empty()) {
                 draws = gDrawEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
                                                                         this,
-                                                                        ctx->fScope,
                                                                         std::move(draws));
-                ctx->fCommittedAnimators = ctx->fScope->size();
+                ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
             }
         } break;
         default:
@@ -700,16 +681,17 @@
         // This is tricky due to the interaction with ctx->fCommittedAnimators: we want any
         // animators related to tranform/opacity to be committed => they must be inserted in front
         // of the dangling/uncommitted ones.
-        AnimatorScope local_scope;
+        AutoScope ascope(this);
 
-        if ((shape_transform = this->attachMatrix2D(*jtransform, &local_scope, nullptr))) {
+        if ((shape_transform = this->attachMatrix2D(*jtransform, nullptr))) {
             shape_wrapper = sksg::TransformEffect::Make(std::move(shape_wrapper), shape_transform);
         }
-        shape_wrapper = this->attachOpacity(*jtransform, &local_scope, std::move(shape_wrapper));
+        shape_wrapper = this->attachOpacity(*jtransform, std::move(shape_wrapper));
 
-        ctx->fScope->insert(ctx->fScope->begin() + ctx->fCommittedAnimators,
-                            std::make_move_iterator(local_scope.begin()),
-                            std::make_move_iterator(local_scope.end()));
+        auto local_scope = ascope.release();
+        fCurrentAnimatorScope->insert(fCurrentAnimatorScope->begin() + ctx->fCommittedAnimators,
+                                      std::make_move_iterator(local_scope.begin()),
+                                      std::make_move_iterator(local_scope.end()));
         ctx->fCommittedAnimators += local_scope.size();
     }
 
@@ -724,19 +706,19 @@
 }
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachShapeLayer(const skjson::ObjectValue& layer,
-                                                           LayerInfo*,
-                                                           AnimatorScope* ascope) const {
+                                                           LayerInfo*) const {
     std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
     std::vector<GeometryEffectRec> geometryEffectStack;
-    AttachShapeContext shapeCtx(ascope, &geometryStack, &geometryEffectStack, ascope->size());
+    AttachShapeContext shapeCtx(&geometryStack, &geometryEffectStack,
+                                fCurrentAnimatorScope->size());
     auto shapeNode = this->attachShape(layer["shapes"], &shapeCtx);
 
     // Trim uncommitted animators: AttachShape consumes effects on the fly, and greedily attaches
     // geometries => at the end, we can end up with unused geometries, which are nevertheless alive
     // due to attached animators.  To avoid this, we track committed animators and discard the
     // orphans here.
-    SkASSERT(shapeCtx.fCommittedAnimators <= ascope->size());
-    ascope->resize(shapeCtx.fCommittedAnimators);
+    SkASSERT(shapeCtx.fCommittedAnimators <= fCurrentAnimatorScope->size());
+    fCurrentAnimatorScope->resize(shapeCtx.fCommittedAnimators);
 
     return shapeNode;
 }
diff --git a/modules/skottie/src/layers/SolidLayer.cpp b/modules/skottie/src/layers/SolidLayer.cpp
index 0cad4be..ef68e99 100644
--- a/modules/skottie/src/layers/SolidLayer.cpp
+++ b/modules/skottie/src/layers/SolidLayer.cpp
@@ -18,8 +18,7 @@
 namespace internal {
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachSolidLayer(const skjson::ObjectValue& jlayer,
-                                                           LayerInfo* layer_info,
-                                                           AnimatorScope*) const {
+                                                           LayerInfo* layer_info) const {
     layer_info->fSize = SkSize::Make(ParseDefault<float>(jlayer["sw"], 0.0f),
                                      ParseDefault<float>(jlayer["sh"], 0.0f));
     const skjson::StringValue* hex_str = jlayer["sc"];
diff --git a/modules/skottie/src/layers/TextLayer.cpp b/modules/skottie/src/layers/TextLayer.cpp
index f722c75..a912777 100644
--- a/modules/skottie/src/layers/TextLayer.cpp
+++ b/modules/skottie/src/layers/TextLayer.cpp
@@ -248,8 +248,7 @@
 }
 
 sk_sp<sksg::RenderNode> AnimationBuilder::attachTextLayer(const skjson::ObjectValue& layer,
-                                                          LayerInfo*,
-                                                          AnimatorScope* ascope) const {
+                                                          LayerInfo*) const {
     // General text node format:
     // "t": {
     //    "a": [], // animators (see TextAnimator.cpp)
@@ -294,13 +293,14 @@
     auto text_root = sksg::Group::Make();
     auto adapter   = sk_make_sp<TextAdapter>(text_root, has_animators);
 
-    this->bindProperty<TextValue>(*jd, ascope, [adapter] (const TextValue& txt) {
-        adapter->setText(txt);
-    });
+    this->bindProperty<TextValue>(*jd,
+        [adapter] (const TextValue& txt) {
+            adapter->setText(txt);
+        });
 
     if (has_animators) {
         if (auto alist = TextAnimatorList::Make(*animated_props, this, adapter)) {
-            ascope->push_back(std::move(alist));
+            fCurrentAnimatorScope->push_back(std::move(alist));
         }
     }
 
diff --git a/modules/skottie/src/text/RangeSelector.cpp b/modules/skottie/src/text/RangeSelector.cpp
index 78e335e..0495916 100644
--- a/modules/skottie/src/text/RangeSelector.cpp
+++ b/modules/skottie/src/text/RangeSelector.cpp
@@ -173,8 +173,7 @@
 } // namespace
 
 sk_sp<RangeSelector> RangeSelector::Make(const skjson::ObjectValue* jrange,
-                                         const AnimationBuilder* abuilder,
-                                         AnimatorScope *ascope) {
+                                         const AnimationBuilder* abuilder) {
     if (!jrange) {
         return nullptr;
     }
@@ -210,19 +209,19 @@
                               ParseEnum<Mode>  (gModeMap  , (*jrange)["m" ], abuilder, "mode"  ),
                               ParseEnum<Shape> (gShapeMap , (*jrange)["sh"], abuilder, "shape" )));
 
-    abuilder->bindProperty<ScalarValue>((*jrange)["s"], ascope,
+    abuilder->bindProperty<ScalarValue>((*jrange)["s"],
         [selector](const ScalarValue& s) {
             selector->fStart = s;
         });
-    abuilder->bindProperty<ScalarValue>((*jrange)["e"], ascope,
+    abuilder->bindProperty<ScalarValue>((*jrange)["e"],
         [selector](const ScalarValue& e) {
             selector->fEnd = e;
         });
-    abuilder->bindProperty<ScalarValue>((*jrange)["o"], ascope,
+    abuilder->bindProperty<ScalarValue>((*jrange)["o"],
         [selector](const ScalarValue& o) {
             selector->fOffset = o;
         });
-    abuilder->bindProperty<ScalarValue>((*jrange)["a"], ascope,
+    abuilder->bindProperty<ScalarValue>((*jrange)["a"],
         [selector](const ScalarValue& a) {
             selector->fAmount = a;
         });
diff --git a/modules/skottie/src/text/RangeSelector.h b/modules/skottie/src/text/RangeSelector.h
index 6c0acbb..3425a2f 100644
--- a/modules/skottie/src/text/RangeSelector.h
+++ b/modules/skottie/src/text/RangeSelector.h
@@ -21,8 +21,7 @@
 class RangeSelector final : public SkNVRefCnt<RangeSelector> {
 public:
     static sk_sp<RangeSelector> Make(const skjson::ObjectValue*,
-                                     const AnimationBuilder*,
-                                     AnimatorScope* ascope);
+                                     const AnimationBuilder*);
 
     enum class Units : uint8_t {
         kPercentage,  // values are percentages of domain size
diff --git a/modules/skottie/src/text/TextAnimator.cpp b/modules/skottie/src/text/TextAnimator.cpp
index ed22318..8a1d2e0 100644
--- a/modules/skottie/src/text/TextAnimator.cpp
+++ b/modules/skottie/src/text/TextAnimator.cpp
@@ -58,8 +58,7 @@
  * }
  */
 sk_sp<TextAnimator> TextAnimator::Make(const skjson::ObjectValue* janimator,
-                                       const AnimationBuilder* abuilder,
-                                       AnimatorScope* ascope) {
+                                       const AnimationBuilder* abuilder) {
     if (!janimator) {
         return nullptr;
     }
@@ -67,7 +66,7 @@
     std::vector<sk_sp<RangeSelector>> selectors;
     if (const skjson::ObjectValue* jselector = (*janimator)["s"]) {
         // Single range selector for now.
-        if (auto sel = RangeSelector::Make(jselector, abuilder, ascope)) {
+        if (auto sel = RangeSelector::Make(jselector, abuilder)) {
             selectors.reserve(1);
             selectors.push_back(std::move(sel));
         }
@@ -76,7 +75,7 @@
     const skjson::ObjectValue* jprops = (*janimator)["a"];
 
     return jprops
-        ? sk_sp<TextAnimator>(new TextAnimator(std::move(selectors), *jprops, abuilder, ascope))
+        ? sk_sp<TextAnimator>(new TextAnimator(std::move(selectors), *jprops, abuilder))
         : nullptr;
 }
 
@@ -136,8 +135,7 @@
 
 TextAnimator::TextAnimator(std::vector<sk_sp<RangeSelector>>&& selectors,
                            const skjson::ObjectValue& jprops,
-                           const AnimationBuilder* abuilder,
-                           AnimatorScope* ascope)
+                           const AnimationBuilder* abuilder)
     : fSelectors(std::move(selectors)) {
 
     // It's *probably* OK to capture a raw pointer to this animator, because the lambda
@@ -145,33 +143,33 @@
     // owning us. But for peace of mind (and future-proofing) let's grab a ref.
     auto animator = sk_ref_sp(this);
 
-    abuilder->bindProperty<VectorValue>(jprops["p"], ascope,
+    abuilder->bindProperty<VectorValue>(jprops["p"],
         [animator](const VectorValue& p) {
             animator->fTextProps.position = ValueTraits<VectorValue>::As<SkPoint>(p);
         });
-    abuilder->bindProperty<ScalarValue>(jprops["s"], ascope,
+    abuilder->bindProperty<ScalarValue>(jprops["s"],
         [animator](const ScalarValue& s) {
             // Scale is 100-based.
             animator->fTextProps.scale = s * 0.01f;
         });
-    abuilder->bindProperty<ScalarValue>(jprops["r"], ascope,
+    abuilder->bindProperty<ScalarValue>(jprops["r"],
         [animator](const ScalarValue& r) {
             animator->fTextProps.rotation = r;
         });
-    fHasFillColor   = abuilder->bindProperty<VectorValue>(jprops["fc"], ascope,
+    fHasFillColor   = abuilder->bindProperty<VectorValue>(jprops["fc"],
         [animator](const VectorValue& fc) {
             animator->fTextProps.fill_color = ValueTraits<VectorValue>::As<SkColor>(fc);
         });
-    fHasStrokeColor = abuilder->bindProperty<VectorValue>(jprops["sc"], ascope,
+    fHasStrokeColor = abuilder->bindProperty<VectorValue>(jprops["sc"],
         [animator](const VectorValue& sc) {
             animator->fTextProps.stroke_color = ValueTraits<VectorValue>::As<SkColor>(sc);
-    });
-    abuilder->bindProperty<ScalarValue>(jprops["o"], ascope,
+        });
+    abuilder->bindProperty<ScalarValue>(jprops["o"],
         [animator](const ScalarValue& o) {
             // Opacity is 100-based.
             animator->fTextProps.opacity = SkTPin<float>(o * 0.01f, 0, 1);
         });
-    abuilder->bindProperty<ScalarValue>(jprops["t"], ascope,
+    abuilder->bindProperty<ScalarValue>(jprops["t"],
         [animator](const ScalarValue& t) {
             animator->fTextProps.tracking = t;
         });
@@ -180,16 +178,18 @@
 sk_sp<TextAnimatorList> TextAnimatorList::Make(const skjson::ArrayValue& janimators,
                                                const AnimationBuilder* abuilder,
                                                sk_sp<TextAdapter> adapter) {
-    AnimatorScope local_animator_scope;
+    AnimationBuilder::AutoScope ascope(abuilder);
     std::vector<sk_sp<TextAnimator>> animators;
     animators.reserve(janimators.size());
 
     for (const skjson::ObjectValue* janimator : janimators) {
-        if (auto animator = TextAnimator::Make(janimator, abuilder, &local_animator_scope)) {
+        if (auto animator = TextAnimator::Make(janimator, abuilder)) {
             animators.push_back(std::move(animator));
         }
     }
 
+    auto local_animator_scope = ascope.release();
+
     if (animators.empty()) {
         return nullptr;
     }
diff --git a/modules/skottie/src/text/TextAnimator.h b/modules/skottie/src/text/TextAnimator.h
index 503077a..69ad82a 100644
--- a/modules/skottie/src/text/TextAnimator.h
+++ b/modules/skottie/src/text/TextAnimator.h
@@ -26,8 +26,7 @@
 class TextAnimator final : public SkNVRefCnt<TextAnimator> {
 public:
     static sk_sp<TextAnimator> Make(const skjson::ObjectValue*,
-                                    const AnimationBuilder*,
-                                    AnimatorScope*);
+                                    const AnimationBuilder*);
 
     struct AnimatedProps {
         SkPoint   position = { 0, 0 };
@@ -66,8 +65,7 @@
 private:
     TextAnimator(std::vector<sk_sp<RangeSelector>>&& selectors,
                  const skjson::ObjectValue& jprops,
-                 const AnimationBuilder* abuilder,
-                 AnimatorScope* ascope);
+                 const AnimationBuilder* abuilder);
 
     AnimatedProps modulateProps(const AnimatedProps&, float amount) const;