[skottie] Fix start-time/stretch-time handling

We currently apply start-time/stretch-time adjustments to the referenced
composition AND to the referencing layer local properties.  That last
part is incorrect: the adjustment should only apply to the referenced
composition.

Introduce a specialized composition time mapper to handle t adjustments,
and push the logic down to AttachCompLayer (and out of the generic
AttachLayer).

TBR=
Change-Id: I0ddb86232010a8e7cdac6524aef2eea5823e306d
Reviewed-on: https://skia-review.googlesource.com/136166
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index 71521c6..a5b4bc8 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -777,22 +777,49 @@
     return asset;
 }
 
-sk_sp<sksg::RenderNode> AttachCompLayer(const skjson::ObjectValue& jlayer, AttachContext* ctx,
-                                        float* time_bias, float* time_scale) {
+sk_sp<sksg::RenderNode> AttachCompLayer(const skjson::ObjectValue& jlayer, AttachContext* ctx) {
     const auto start_time = ParseDefault<float>(jlayer["st"], 0.0f),
              stretch_time = ParseDefault<float>(jlayer["sr"], 1.0f);
+    const auto requires_time_mapping = !SkScalarNearlyEqual(start_time  , 0) ||
+                                       !SkScalarNearlyEqual(stretch_time, 1);
 
-    *time_bias = -start_time;
-    *time_scale = sk_ieee_float_divide(1, stretch_time);
-    if (SkScalarIsNaN(*time_scale)) {
-        *time_scale = 1;
+    sksg::AnimatorList local_animators;
+    AttachContext local_ctx = { ctx->fResources,
+                                ctx->fAssets,
+                                ctx->fDuration,
+                                requires_time_mapping ? local_animators : ctx->fAnimators };
+
+    auto comp_layer = AttachAssetRef(jlayer, &local_ctx, AttachComposition);
+
+    // Applies a bias/scale t-adjustment to child animators.
+    class CompTimeMapper final : public sksg::GroupAnimator {
+    public:
+        CompTimeMapper(sksg::AnimatorList&& layer_animators, float time_bias, float time_scale)
+            : INHERITED(std::move(layer_animators))
+            , fTimeBias(time_bias)
+            , fTimeScale(time_scale) {}
+
+        void onTick(float t) override {
+            this->INHERITED::onTick((t + fTimeBias) * fTimeScale);
+        }
+    private:
+        const float fTimeBias,
+                    fTimeScale;
+
+        using INHERITED = sksg::GroupAnimator;
+    };
+
+    if (requires_time_mapping) {
+        const auto t_bias  = -start_time,
+                   t_scale = sk_ieee_float_divide(1, stretch_time);
+        ctx->fAnimators.push_back(skstd::make_unique<CompTimeMapper>(std::move(local_animators),
+                                                                     t_bias, t_scale));
     }
 
-    return AttachAssetRef(jlayer, ctx, AttachComposition);
+    return comp_layer;
 }
 
-sk_sp<sksg::RenderNode> AttachSolidLayer(const skjson::ObjectValue& jlayer, AttachContext*,
-                                         float*, float*) {
+sk_sp<sksg::RenderNode> AttachSolidLayer(const skjson::ObjectValue& jlayer, AttachContext*) {
     const auto size = SkSize::Make(ParseDefault<float>(jlayer["sw"], 0.0f),
                                    ParseDefault<float>(jlayer["sh"], 0.0f));
     const auto hex = ParseDefault<SkString>(jlayer["sc"], SkString());
@@ -829,20 +856,17 @@
         SkImage::MakeFromEncoded(SkData::MakeFromStream(resStream.get(), resStream->getLength())));
 }
 
-sk_sp<sksg::RenderNode> AttachImageLayer(const skjson::ObjectValue& jlayer, AttachContext* ctx,
-                                         float*, float*) {
+sk_sp<sksg::RenderNode> AttachImageLayer(const skjson::ObjectValue& jlayer, AttachContext* ctx) {
     return AttachAssetRef(jlayer, ctx, AttachImageAsset);
 }
 
-sk_sp<sksg::RenderNode> AttachNullLayer(const skjson::ObjectValue& layer, AttachContext*,
-                                        float*, float*) {
+sk_sp<sksg::RenderNode> AttachNullLayer(const skjson::ObjectValue& layer, AttachContext*) {
     // Null layers are used solely to drive dependent transforms,
     // but we use free-floating sksg::Matrices for that purpose.
     return nullptr;
 }
 
-sk_sp<sksg::RenderNode> AttachShapeLayer(const skjson::ObjectValue& layer, AttachContext* ctx,
-                                         float*, float*) {
+sk_sp<sksg::RenderNode> AttachShapeLayer(const skjson::ObjectValue& layer, AttachContext* ctx) {
     std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
     std::vector<GeometryEffectRec> geometryEffectStack;
     AttachShapeContext shapeCtx(ctx, &geometryStack, &geometryEffectStack, ctx->fAnimators.size());
@@ -858,8 +882,7 @@
     return shapeNode;
 }
 
-sk_sp<sksg::RenderNode> AttachTextLayer(const skjson::ObjectValue& layer, AttachContext*,
-                                        float*, float*) {
+sk_sp<sksg::RenderNode> AttachTextLayer(const skjson::ObjectValue& layer, AttachContext*) {
     LOG("?? Text layer stub\n");
     return nullptr;
 }
@@ -1007,8 +1030,7 @@
                                     AttachLayerContext* layerCtx) {
     if (!jlayer) return nullptr;
 
-    using LayerAttacher = sk_sp<sksg::RenderNode> (*)(const skjson::ObjectValue&, AttachContext*,
-                                                      float* time_bias, float* time_scale);
+    using LayerAttacher = sk_sp<sksg::RenderNode> (*)(const skjson::ObjectValue&, AttachContext*);
     static constexpr LayerAttacher gLayerAttachers[] = {
         AttachCompLayer,  // 'ty': 0
         AttachSolidLayer, // 'ty': 1
@@ -1029,12 +1051,8 @@
                                 layerCtx->fCtx->fDuration,
                                 layer_animators};
 
-    // Layer attachers may adjust these.
-    float time_bias  = 0,
-          time_scale = 1;
-
     // Layer content.
-    auto layer = gLayerAttachers[type](*jlayer, &local_ctx, &time_bias, &time_scale);
+    auto layer = gLayerAttachers[type](*jlayer, &local_ctx);
 
     // Clip layers with explicit dimensions.
     float w = 0, h = 0;
@@ -1062,14 +1080,11 @@
     public:
         LayerController(sksg::AnimatorList&& layer_animators,
                         sk_sp<sksg::OpacityEffect> controlNode,
-                        float in, float out,
-                        float time_bias, float time_scale)
+                        float in, float out)
             : INHERITED(std::move(layer_animators))
             , fControlNode(std::move(controlNode))
             , fIn(in)
-            , fOut(out)
-            , fTimeBias(time_bias)
-            , fTimeScale(time_scale) {}
+            , fOut(out) {}
 
         void onTick(float t) override {
             const auto active = (t >= fIn && t <= fOut);
@@ -1079,16 +1094,13 @@
             fControlNode->setOpacity(active ? 1 : 0);
 
             // Dispatch ticks only while active.
-            if (active)
-                this->INHERITED::onTick((t + fTimeBias) * fTimeScale);
+            if (active) this->INHERITED::onTick(t);
         }
 
     private:
         const sk_sp<sksg::OpacityEffect> fControlNode;
         const float                      fIn,
-                                         fOut,
-                                         fTimeBias,
-                                         fTimeScale;
+                                         fOut;
 
         using INHERITED = sksg::GroupAnimator;
     };
@@ -1105,12 +1117,7 @@
         return nullptr;
 
     layerCtx->fCtx->fAnimators.push_back(
-        skstd::make_unique<LayerController>(std::move(layer_animators),
-                                            controller_node,
-                                            in,
-                                            out,
-                                            time_bias,
-                                            time_scale));
+        skstd::make_unique<LayerController>(std::move(layer_animators), controller_node, in, out));
 
     if (ParseDefault<bool>((*jlayer)["td"], false)) {
         // This layer is a matte.  We apply it as a mask to the next layer.