[skottie] Fix camera/3D layer transform interactions

Currently, the camera view transform is attached to the render tree as a
TransformEffect.  This means it gets "flattened" to an SkMatrix in isolation,
and does not compose with other layer transforms in 4x4 format.

Refactor the implementation to

  - build the camera transform upfront
  - compose (chain) all layer transforms from this camera transform

This ensures that transform composition happens in SkMatrix44.

E.g. render tree topology change (TE == TransformEffect)

Before:

                      [root]
                        |
                      [TE]<---[CameraT]
                        |
       ------------------------------------
      |                                    |
      |                                    |
    [TE]<--[Layer1T]          [Layer2T]-->[TE]
      |                                    |
  [Layer1]                              [Layer2]
      |                                    |



After:
                      [root]
                        |
                        |
       ------------------------------------
      |                                    |
      |             [CameraT]              |
      |                / \                 |
    [TE]<--[Layer1T]<--   --->[Layer2T]-->[TE]
      |                                    |
  [Layer1]                              [Layer2]
      |                                    |


TBR=
Bug: skia:8914
Change-Id: Idd407712f75c48623b5299a4284ddb17b98c155f
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/249217
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/src/Composition.cpp b/modules/skottie/src/Composition.cpp
index 3efad42..50ce0a3 100644
--- a/modules/skottie/src/Composition.cpp
+++ b/modules/skottie/src/Composition.cpp
@@ -134,9 +134,40 @@
         layerCtx.fMotionBlurPhase = SkTPin(ParseDefault((*jmb)["sp"], 0.0f), -360.0f, 360.0f);
     }
 
-    layers.reserve(jlayers->size());
-    for (const auto& l : *jlayers) {
-        if (auto layer = this->attachLayer(l, &layerCtx)) {
+
+    // First pass: parse layer types and attach camera layers.
+    struct LayerRec {
+        const skjson::ObjectValue& jlayer;
+        size_t                     layer_type;
+    };
+    SkSTArray<64, LayerRec, true> layer_recs(jlayers->size());
+    static constexpr int kCameraLayerType = 13;
+    for (const skjson::ObjectValue* jlayer : *jlayers) {
+        if (!jlayer) {
+            continue;
+        }
+
+        const auto type = ParseDefault<int>((*jlayer)["ty"], -1);
+        if (type < 0) {
+            continue;
+        }
+
+        if (type == kCameraLayerType) {
+            // Cameras must be attached upfront because layer transforms
+            // are rooted in the camera transform.
+            this->attachLayer(*jlayer, kCameraLayerType, &layerCtx);
+            continue;
+        }
+
+        layer_recs.push_back({*jlayer, SkToSizeT(type)});
+    }
+
+    // Second pass: attach all other layers.
+    layers.reserve(layer_recs.size());
+    for (const auto& rec : layer_recs) {
+        SkASSERT(rec.layer_type != kCameraLayerType);
+
+        if (auto layer = this->attachLayer(rec.jlayer, rec.layer_type, &layerCtx)) {
             layers.push_back(std::move(layer));
         }
     }
@@ -155,11 +186,6 @@
         comp = sksg::Group::Make(std::move(layers));
     }
 
-    // Optional camera.
-    if (layerCtx.fCameraTransform) {
-        comp = sksg::TransformEffect::Make(std::move(comp), std::move(layerCtx.fCameraTransform));
-    }
-
     return comp;
 }
 
diff --git a/modules/skottie/src/Layer.cpp b/modules/skottie/src/Layer.cpp
index 4fdcf68..c46a90f 100644
--- a/modules/skottie/src/Layer.cpp
+++ b/modules/skottie/src/Layer.cpp
@@ -262,7 +262,7 @@
     if (layer_index >= 0) {
         auto* rec = fLayerTransformMap.find(layer_index);
         if (!rec) {
-            rec = this->attachLayerTransformImpl(jlayer, abuilder, type, layer_index);
+            rec = this->attachLayerTransformImpl(jlayer, abuilder, type, layer_index, false);
         }
         SkASSERT(rec);
 
@@ -282,10 +282,14 @@
 sk_sp<sksg::Transform>
 AnimationBuilder::AttachLayerContext::attachParentLayerTransform(const skjson::ObjectValue& jlayer,
                                                                  const AnimationBuilder* abuilder,
-                                                                 int layer_index) {
+                                                                 int layer_index,
+                                                                 bool is_camera_ancestor) {
     const auto parent_index = ParseDefault<int>(jlayer["parent"], -1);
-    if (parent_index < 0 || parent_index == layer_index)
-        return nullptr;
+    if (parent_index < 0 || parent_index == layer_index) {
+        // Layer transform chains are implicitly rooted in the camera transform
+        // (except for camera parent layers).
+        return is_camera_ancestor ? nullptr : fCameraTransform;
+    }
 
     if (const auto* rec = fLayerTransformMap.find(parent_index))
         return rec->fTransformNode;
@@ -300,7 +304,8 @@
             return this->attachLayerTransformImpl(*l,
                                                   abuilder,
                                                   parent_type,
-                                                  parent_index)->fTransformNode;
+                                                  parent_index,
+                                                  is_camera_ancestor)->fTransformNode;
         }
     }
 
@@ -348,14 +353,16 @@
 AnimationBuilder::AttachLayerContext::attachLayerTransformImpl(const skjson::ObjectValue& jlayer,
                                                                const AnimationBuilder* abuilder,
                                                                TransformType type,
-                                                               int layer_index) {
+                                                               int layer_index,
+                                                               bool is_camera_ancestor) {
     SkASSERT(!fLayerTransformMap.find(layer_index));
 
     // Add a stub entry to break recursion cycles.
     fLayerTransformMap.set(layer_index, { nullptr, {} });
 
-    auto parent_matrix = this->attachParentLayerTransform(jlayer, abuilder, layer_index);
-
+    is_camera_ancestor |= type == TransformType::kCamera;
+    auto parent_matrix = this->attachParentLayerTransform(jlayer, abuilder, layer_index,
+                                                          is_camera_ancestor);
     AutoScope ascope(abuilder);
     auto transform = this->attachTransformNode(jlayer,
                                                abuilder,
@@ -371,16 +378,13 @@
         && ParseDefault(jlayer["mb"], false);
 }
 
-sk_sp<sksg::RenderNode> AnimationBuilder::attachLayer(const skjson::ObjectValue* jlayer,
+sk_sp<sksg::RenderNode> AnimationBuilder::attachLayer(const skjson::ObjectValue& jlayer,
+                                                      size_t type,
                                                       AttachLayerContext* layerCtx) const {
-    if (!jlayer) {
-        return nullptr;
-    }
-
     LayerInfo layer_info = {
         fSize,
-        ParseDefault<float>((*jlayer)["ip"], 0.0f),
-        ParseDefault<float>((*jlayer)["op"], 0.0f),
+        ParseDefault<float>(jlayer["ip"], 0.0f),
+        ParseDefault<float>(jlayer["op"], 0.0f),
     };
     if (layer_info.fInPoint >= layer_info.fOutPoint) {
         this->log(Logger::Level::kError, nullptr,
@@ -388,7 +392,7 @@
         return nullptr;
     }
 
-    const AutoPropertyTracker apt(this, *jlayer);
+    const AutoPropertyTracker apt(this, jlayer);
 
     using LayerBuilder = sk_sp<sksg::RenderNode> (AnimationBuilder::*)(const skjson::ObjectValue&,
                                                                        LayerInfo*) const;
@@ -415,9 +419,7 @@
         { &AnimationBuilder::attachTextLayer   ,                 0 },  // 'ty': 5 -> text
     };
 
-    const auto type = ParseDefault<int>((*jlayer)["ty"], -1);
-    if ((type < 0) ||
-        (type >= SkTo<int>(SK_ARRAY_COUNT(gLayerBuildInfo)) && type != kCameraLayerType)) {
+    if (type >= SK_ARRAY_COUNT(gLayerBuildInfo) && type != kCameraLayerType) {
         return nullptr;
     }
 
@@ -425,13 +427,13 @@
     const auto transform_type = (type == kCameraLayerType)
             ? AttachLayerContext::TransformType::kCamera
             : AttachLayerContext::TransformType::kLayer;
-    auto layer_transform_rec = layerCtx->attachLayerTransform(*jlayer, this, transform_type);
+    auto layer_transform_rec = layerCtx->attachLayerTransform(jlayer, this, transform_type);
 
     if (type == kCameraLayerType) {
         // Camera layers are special: they don't build normal SG fragments, but drive a root-level
         // transform.
         if (layerCtx->fCameraTransform) {
-            this->log(Logger::Level::kWarning, jlayer, "Ignoring duplicate camera layer.");
+            this->log(Logger::Level::kWarning, &jlayer, "Ignoring duplicate camera layer.");
             return nullptr;
         }
 
@@ -441,22 +443,22 @@
     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 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);
+    auto layer = (this->*(build_info.fBuilder))(jlayer, &layer_info);
 
     // Clip layers with explicit dimensions.
     float w = 0, h = 0;
-    if (Parse<float>((*jlayer)["w"], &w) && Parse<float>((*jlayer)["h"], &h)) {
+    if (Parse<float>(jlayer["w"], &w) && Parse<float>(jlayer["h"], &h)) {
         layer = sksg::ClipEffect::Make(std::move(layer),
                                        sksg::Rect::Make(SkRect::MakeWH(w, h)),
                                        true);
     }
 
     // Optional layer mask.
-    layer = AttachMask((*jlayer)["masksProperties"], this, 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)
@@ -468,7 +470,7 @@
     }
 
     // Optional layer effects.
-    if (const skjson::ArrayValue* jeffects = (*jlayer)["ef"]) {
+    if (const skjson::ArrayValue* jeffects = jlayer["ef"]) {
         layer = EffectBuilder(this, layer_info.fSize).attachEffects(*jeffects, std::move(layer));
     }
 
@@ -480,12 +482,12 @@
 
     // Optional layer opacity.
     // TODO: de-dupe this "ks" lookup with matrix above.
-    if (const skjson::ObjectValue* jtransform = (*jlayer)["ks"]) {
+    if (const skjson::ObjectValue* jtransform = jlayer["ks"]) {
         layer = this->attachOpacity(*jtransform, std::move(layer));
     }
 
     // Optional blend mode.
-    layer = this->attachBlendMode(*jlayer, std::move(layer));
+    layer = this->attachBlendMode(jlayer, std::move(layer));
 
     const auto has_animators = !fCurrentAnimatorScope->empty();
 
@@ -496,7 +498,7 @@
                                                                    layer_info.fOutPoint);
 
     // Optional motion blur.
-    if (layer && has_animators && layerCtx->hasMotionBlur(*jlayer)) {
+    if (layer && has_animators && layerCtx->hasMotionBlur(jlayer)) {
         SkASSERT(layerCtx->fMotionBlurAngle >= 0);
 
         // Wrap both the layer node and the controller.
@@ -514,7 +516,7 @@
         return nullptr;
     }
 
-    if (ParseDefault<bool>((*jlayer)["td"], false)) {
+    if (ParseDefault<bool>(jlayer["td"], false)) {
         // This layer is a matte.  We apply it as a mask to the next layer.
         layerCtx->fCurrentMatte = std::move(layer);
         return nullptr;
@@ -528,7 +530,7 @@
             sksg::MaskEffect::Mode::kLumaNormal,  // tt: 3
             sksg::MaskEffect::Mode::kLumaInvert,  // tt: 4
         };
-        const auto matteType = ParseDefault<size_t>((*jlayer)["tt"], 1) - 1;
+        const auto matteType = ParseDefault<size_t>(jlayer["tt"], 1) - 1;
 
         if (matteType < SK_ARRAY_COUNT(gMaskModes)) {
             return sksg::MaskEffect::Make(std::move(layer),
diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h
index 97da465..b415690 100644
--- a/modules/skottie/src/SkottiePriv.h
+++ b/modules/skottie/src/SkottiePriv.h
@@ -178,7 +178,7 @@
     void dispatchMarkers(const skjson::ArrayValue*) const;
 
     sk_sp<sksg::RenderNode> attachComposition(const skjson::ObjectValue&) const;
-    sk_sp<sksg::RenderNode> attachLayer(const skjson::ObjectValue*,
+    sk_sp<sksg::RenderNode> attachLayer(const skjson::ObjectValue&, size_t type,
                                         AttachLayerContext*) const;
 
     sk_sp<sksg::RenderNode> attachBlendMode(const skjson::ObjectValue&,
@@ -282,7 +282,8 @@
 private:
     sk_sp<sksg::Transform> attachParentLayerTransform(const skjson::ObjectValue& jlayer,
                                                       const AnimationBuilder* abuilder,
-                                                      int layer_index);
+                                                      int layer_index,
+                                                      bool is_camera_ancestor);
 
     sk_sp<sksg::Transform> attachTransformNode(const skjson::ObjectValue& jlayer,
                                                const AnimationBuilder* abuilder,
@@ -291,7 +292,8 @@
 
     TransformRec* attachLayerTransformImpl(const skjson::ObjectValue& jlayer,
                                            const AnimationBuilder* abuilder,
-                                           TransformType type, int layer_index);
+                                           TransformType type, int layer_index,
+                                           bool is_camera_ancestor);
 };
 
 } // namespace internal