[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