[skottie] Cascading track matte support

Currently, we treat track matte source layers (tagged with td:1) as single-shot mask triggers:
we apply once to the following layer, then move on.

But track mattes can cascade: a layer with a matte can itself be applied as a track matte for the
following layer.

Also, for matte/masking purposes, only the layer content is being considered (ignoring blend mode
and any masks applied to the matte itself).

To support this, refactor the layer attachment code:

  - instead of tracking the presence of a single-shot matte source, always track
    previous layer content trees
  - instead of triggering matte attachment in the presence of a matte source, trigger based on
    the matte *target* property (tt: X)
  - log errors on unknown matte modes

Change-Id: I6c71d4007e1e27d3f3a139344bbf367d7bc6e29d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/259820
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Florin Malita 2019-12-12 15:43:53 -05:00 committed by Skia Commit-Bot
parent b0892888d1
commit 46a331b93f
5 changed files with 29 additions and 36 deletions

View File

@ -172,14 +172,6 @@ CompositionBuilder::CompositionBuilder(const AnimationBuilder& abuilder,
CompositionBuilder::~CompositionBuilder() = default; CompositionBuilder::~CompositionBuilder() = default;
void CompositionBuilder::pushMatte(sk_sp<sksg::RenderNode> matte) {
fCurrentMatte = std::move(matte);
}
sk_sp<sksg::RenderNode> CompositionBuilder::popMatte() {
return std::move(fCurrentMatte);
}
LayerBuilder* CompositionBuilder::layerBuilder(int layer_index) { LayerBuilder* CompositionBuilder::layerBuilder(int layer_index) {
if (layer_index < 0) { if (layer_index < 0) {
return nullptr; return nullptr;
@ -202,10 +194,12 @@ sk_sp<sksg::RenderNode> CompositionBuilder::build(const AnimationBuilder& abuild
std::vector<sk_sp<sksg::RenderNode>> layers; std::vector<sk_sp<sksg::RenderNode>> layers;
layers.reserve(fLayerBuilders.size()); layers.reserve(fLayerBuilders.size());
LayerBuilder* prev_layer = nullptr;
for (auto& lbuilder : fLayerBuilders) { for (auto& lbuilder : fLayerBuilders) {
if (auto layer = lbuilder.buildRenderTree(abuilder, this)) { if (auto layer = lbuilder.buildRenderTree(abuilder, this, prev_layer)) {
layers.push_back(std::move(layer)); layers.push_back(std::move(layer));
} }
prev_layer = &lbuilder;
} }
if (layers.empty()) { if (layers.empty()) {

View File

@ -29,9 +29,6 @@ private:
const sk_sp<sksg::Transform>& getCameraTransform() const { return fCameraTransform; } const sk_sp<sksg::Transform>& getCameraTransform() const { return fCameraTransform; }
void pushMatte(sk_sp<sksg::RenderNode>);
sk_sp<sksg::RenderNode> popMatte();
friend class LayerBuilder; friend class LayerBuilder;
const SkSize fSize; const SkSize fSize;
@ -40,7 +37,6 @@ private:
SkTHashMap<int, size_t> fLayerIndexMap; // Maps layer "ind" to layer builder index. SkTHashMap<int, size_t> fLayerIndexMap; // Maps layer "ind" to layer builder index.
sk_sp<sksg::Transform> fCameraTransform; sk_sp<sksg::Transform> fCameraTransform;
sk_sp<sksg::RenderNode> fCurrentMatte; // Tracks the current/active matte.
size_t fMotionBlurSamples = 1; size_t fMotionBlurSamples = 1;
float fMotionBlurAngle = 0, float fMotionBlurAngle = 0,

View File

@ -363,7 +363,8 @@ bool LayerBuilder::hasMotionBlur(const CompositionBuilder* cbuilder) const {
} }
sk_sp<sksg::RenderNode> LayerBuilder::buildRenderTree(const AnimationBuilder& abuilder, sk_sp<sksg::RenderNode> LayerBuilder::buildRenderTree(const AnimationBuilder& abuilder,
CompositionBuilder* cbuilder) { CompositionBuilder* cbuilder,
const LayerBuilder* prev_layer) {
AnimationBuilder::LayerInfo layer_info = { AnimationBuilder::LayerInfo layer_info = {
cbuilder->fSize, cbuilder->fSize,
ParseDefault<float>(fJlayer["ip"], 0.0f), ParseDefault<float>(fJlayer["ip"], 0.0f),
@ -475,40 +476,38 @@ sk_sp<sksg::RenderNode> LayerBuilder::buildRenderTree(const AnimationBuilder& ab
abuilder.fCurrentAnimatorScope->push_back(std::move(controller)); abuilder.fCurrentAnimatorScope->push_back(std::move(controller));
if (!layer) { // Stash the content tree in case it is needed for later mattes.
fContentTree = layer;
if (ParseDefault<bool>(fJlayer["td"], false)) {
// |layer| is a track matte. We apply it as a mask to the next layer.
return nullptr; return nullptr;
} }
if (auto matte = cbuilder->popMatte()) { // Optional matte.
// There is a pending matte (|layer| is a matte target). size_t matte_mode;
static constexpr sksg::MaskEffect::Mode gMaskModes[] = { if (prev_layer && Parse(fJlayer["tt"], &matte_mode)) {
static constexpr sksg::MaskEffect::Mode gMatteModes[] = {
sksg::MaskEffect::Mode::kAlphaNormal, // tt: 1 sksg::MaskEffect::Mode::kAlphaNormal, // tt: 1
sksg::MaskEffect::Mode::kAlphaInvert, // tt: 2 sksg::MaskEffect::Mode::kAlphaInvert, // tt: 2
sksg::MaskEffect::Mode::kLumaNormal, // tt: 3 sksg::MaskEffect::Mode::kLumaNormal, // tt: 3
sksg::MaskEffect::Mode::kLumaInvert, // tt: 4 sksg::MaskEffect::Mode::kLumaInvert, // tt: 4
}; };
const auto matteType = ParseDefault<size_t>(fJlayer["tt"], 1) - 1;
if (matteType < SK_ARRAY_COUNT(gMaskModes)) { if (matte_mode > 0 && matte_mode <= SK_ARRAY_COUNT(gMatteModes)) {
// The current layer is masked with the previous layer *content*.
layer = sksg::MaskEffect::Make(std::move(layer), layer = sksg::MaskEffect::Make(std::move(layer),
std::move(matte), prev_layer->fContentTree,
gMaskModes[matteType]); gMatteModes[matte_mode - 1]);
} else {
abuilder.log(Logger::Level::kError, nullptr,
"Unknown track matte mode: %zu\n", matte_mode);
} }
} }
// Optional blend mode. The attachment point is important for matte interactions: // Finally, attach an optional blend mode.
// - for mattes (mask layers), the blend mode is applied to the layer content // NB: blend modes are never applied to matte sources (layer content only).
// - for matte targets (masked layers), the blend mode is applied post-masking return abuilder.attachBlendMode(fJlayer, std::move(layer));
// (wrapping the MaskEffect above)
layer = abuilder.attachBlendMode(fJlayer, std::move(layer));
if (ParseDefault<bool>(fJlayer["td"], false)) {
// |layer| is a matte. We apply it as a mask to the next layer.
cbuilder->pushMatte(std::move(layer));
return nullptr;
}
return layer;
} }
} // namespace internal } // namespace internal

View File

@ -28,7 +28,8 @@ public:
sk_sp<sksg::Transform> buildTransform(const AnimationBuilder&, CompositionBuilder*); sk_sp<sksg::Transform> buildTransform(const AnimationBuilder&, CompositionBuilder*);
// Attaches the actual layer content and finalizes its render tree. Called once per layer. // Attaches the actual layer content and finalizes its render tree. Called once per layer.
sk_sp<sksg::RenderNode> buildRenderTree(const AnimationBuilder&, CompositionBuilder*); sk_sp<sksg::RenderNode> buildRenderTree(const AnimationBuilder&, CompositionBuilder*,
const LayerBuilder* prev_layer);
private: private:
enum TransformType : uint8_t { enum TransformType : uint8_t {
@ -65,6 +66,8 @@ private:
sk_sp<sksg::Transform> fLayerTransform; // this layer's transform node. sk_sp<sksg::Transform> fLayerTransform; // this layer's transform node.
sk_sp<sksg::Transform> fTransformCache[2]; // cached 2D/3D chain for the local node sk_sp<sksg::Transform> fTransformCache[2]; // cached 2D/3D chain for the local node
sk_sp<sksg::RenderNode> fContentTree; // render tree for layer content,
// excluding mask/matte and blending
AnimatorScope fLayerScope; // layer-scoped animators AnimatorScope fLayerScope; // layer-scoped animators
size_t fTransformAnimatorCount = 0; // transform-related animator count size_t fTransformAnimatorCount = 0; // transform-related animator count

View File

@ -0,0 +1 @@
{"v":"5.5.10","fr":60,"ip":0,"op":601,"w":500,"h":500,"nm":"chained mattes","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"matte 1","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":600,"s":[720]}],"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"sr","sy":1,"d":1,"pt":{"a":0,"k":5,"ix":3},"p":{"a":0,"k":[0,0],"ix":4},"r":{"a":0,"k":0,"ix":5},"ir":{"a":0,"k":50,"ix":6},"is":{"a":0,"k":0,"ix":8},"or":{"a":0,"k":140,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":601,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"matte2","tt":2,"sr":1,"ks":{"o":{"a":0,"k":70,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[150,150],"ix":2},"p":{"a":0,"k":[75,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.342968761921,0.597441792488,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":26,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":600,"s":[-360]}],"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[400,400],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.520817995071,0.698636651039,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":601,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":1,"nm":"Medium Purple Solid 1","tt":4,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":500,"sh":500,"sc":"#d14eff","ip":0,"op":601,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":1,"nm":"bg","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":500,"sh":500,"sc":"#000000","ip":0,"op":601,"st":0,"bm":0}],"markers":[]}