[skottie] Lazy adapter sync
Expand the core animator logic to return whether the computed value is changing on each tick. Also rename tick/onTick -> seek/onSeek to better reflect Skottie semantics. This information allows us to skip adapter updates for static/hold animation segments. This effectively hoists some of the scene graph lazy-update logic to the Skottie model level, and culls unneeded conversions (e.g. we were converting ShapeValue -> SkPath on every tick, even when the shape was not changing). TBR= Change-Id: I1ea4e19ae8f993d659826269de6b0465fec70189 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/279816 Reviewed-by: Mike Reed <reed@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
parent
34ed73b038
commit
0147de41c4
@ -122,7 +122,7 @@ sk_sp<sksg::Transform> AnimationBuilder::attachCamera(const skjson::ObjectValue&
|
||||
auto adapter = sk_make_sp<CameraAdaper>(jlayer, jtransform, *this, viewport_size);
|
||||
|
||||
if (adapter->isStatic()) {
|
||||
adapter->tick(0);
|
||||
adapter->seek(0);
|
||||
} else {
|
||||
fCurrentAnimatorScope->push_back(adapter);
|
||||
}
|
||||
|
@ -55,9 +55,12 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name
|
||||
}
|
||||
|
||||
protected:
|
||||
void onTick(float t) {
|
||||
bool onSeek(float t) {
|
||||
// TODO: we prolly need more sophisticated timeline mapping for nested animations.
|
||||
fAnimation->seek(t * fTimeScale);
|
||||
|
||||
// TODO: bubble the real update status to clients?
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -223,11 +223,13 @@ public:
|
||||
, fOut(out) {}
|
||||
|
||||
protected:
|
||||
void onTick(float t) override {
|
||||
bool onSeek(float t) override {
|
||||
// in/out may be inverted for time-reversed layers
|
||||
const auto active = (t >= fIn && t < fOut) || (t > fOut && t <= fIn);
|
||||
|
||||
auto updated = false;
|
||||
if (fLayerNode) {
|
||||
updated |= (fLayerNode->isVisible() != active);
|
||||
fLayerNode->setVisible(active);
|
||||
}
|
||||
|
||||
@ -237,8 +239,10 @@ protected:
|
||||
const auto dispatch_count = active ? fLayerAnimators.size()
|
||||
: fTransformAnimatorsCount;
|
||||
for (size_t i = 0; i < dispatch_count; ++i) {
|
||||
fLayerAnimators[i]->tick(t);
|
||||
updated |= fLayerAnimators[i]->seek(t);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private:
|
||||
@ -258,8 +262,9 @@ protected:
|
||||
// When motion blur is present, time ticks are not passed to layer animators
|
||||
// but to the motion blur effect. The effect then drives the animators/scene-graph
|
||||
// during reval and render phases.
|
||||
void onTick(float t) override {
|
||||
bool onSeek(float t) override {
|
||||
fMotionBlurEffect->setT(t);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -132,7 +132,7 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachOpacity(const skjson::ObjectValu
|
||||
const auto dispatched = this->dispatchOpacityProperty(adapter->node());
|
||||
|
||||
if (adapter->isStatic()) {
|
||||
adapter->tick(0);
|
||||
adapter->seek(0);
|
||||
if (!dispatched && adapter->node()->getOpacity() >= 1) {
|
||||
// No obeservable effects - we can discard.
|
||||
return child_node;
|
||||
@ -488,7 +488,7 @@ void Animation::seekFrame(double t, sksg::InvalidationController* ic) {
|
||||
comp_time = SkTPin<float>(fInPoint + t, fInPoint, kLastValidFrame);
|
||||
|
||||
for (const auto& anim : fAnimators) {
|
||||
anim->tick(comp_time);
|
||||
anim->seek(comp_time);
|
||||
}
|
||||
|
||||
fScene->revalidate(ic);
|
||||
|
@ -119,7 +119,7 @@ public:
|
||||
void attachDiscardableAdapter(sk_sp<T> adapter) const {
|
||||
if (adapter->isStatic()) {
|
||||
// Fire off a synthetic tick to force a single SG sync before discarding.
|
||||
adapter->tick(0);
|
||||
adapter->seek(0);
|
||||
} else {
|
||||
fCurrentAnimatorScope->push_back(std::move(adapter));
|
||||
}
|
||||
|
@ -31,17 +31,6 @@ bool ValueTraits<Vec2Value>::FromJSON(const skjson::Value& jv, const internal::A
|
||||
return Parse(jv, v);
|
||||
}
|
||||
|
||||
template <>
|
||||
bool ValueTraits<ScalarValue>::CanLerp(const ScalarValue&, const ScalarValue&) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
void ValueTraits<ScalarValue>::Lerp(const ScalarValue& v0, const ScalarValue& v1, float t,
|
||||
ScalarValue* result) {
|
||||
*result = v0 + (v1 - v0) * t;
|
||||
}
|
||||
|
||||
template <>
|
||||
template <>
|
||||
SkScalar ValueTraits<ScalarValue>::As<SkScalar>(const ScalarValue& v) {
|
||||
@ -191,24 +180,31 @@ static SkPoint lerp_point(const SkPoint& v0, const SkPoint& v1, const Sk2f& t) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void ValueTraits<ShapeValue>::Lerp(const ShapeValue& v0, const ShapeValue& v1, float t,
|
||||
bool ValueTraits<ShapeValue>::Lerp(const ShapeValue& v0, const ShapeValue& v1, float t,
|
||||
ShapeValue* result) {
|
||||
SkASSERT(v0.fVertices.size() == v1.fVertices.size());
|
||||
SkASSERT(v0.fClosed == v1.fClosed);
|
||||
|
||||
result->fClosed = v0.fClosed;
|
||||
auto updated = (result->fClosed != v0.fClosed || !result->fVolatile);
|
||||
|
||||
result->fClosed = v0.fClosed;
|
||||
result->fVolatile = true; // interpolated values are volatile
|
||||
|
||||
const auto t2f = Sk2f(t);
|
||||
result->fVertices.resize(v0.fVertices.size());
|
||||
|
||||
for (size_t i = 0; i < v0.fVertices.size(); ++i) {
|
||||
result->fVertices[i] = BezierVertex({
|
||||
const auto v = BezierVertex({
|
||||
lerp_point(v0.fVertices[i].fInPoint , v1.fVertices[i].fInPoint , t2f),
|
||||
lerp_point(v0.fVertices[i].fOutPoint, v1.fVertices[i].fOutPoint, t2f),
|
||||
lerp_point(v0.fVertices[i].fVertex , v1.fVertices[i].fVertex , t2f)
|
||||
});
|
||||
|
||||
updated |= (v != result->fVertices[i]);
|
||||
result->fVertices[i] = v;
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
template <>
|
||||
|
@ -33,7 +33,7 @@ struct ValueTraits {
|
||||
static U As(const T&);
|
||||
|
||||
static bool CanLerp(const T&, const T&);
|
||||
static void Lerp(const T&, const T&, float, T*);
|
||||
static bool Lerp(const T&, const T&, float, T*);
|
||||
};
|
||||
|
||||
using ScalarValue = SkScalar;
|
||||
|
@ -114,7 +114,7 @@ sk_sp<sksg::Transform> AnimationBuilder::attachMatrix2D(const skjson::ObjectValu
|
||||
// The transform has no observable effects - we can discard.
|
||||
return parent;
|
||||
}
|
||||
adapter->tick(0);
|
||||
adapter->seek(0);
|
||||
} else {
|
||||
fCurrentAnimatorScope->push_back(adapter);
|
||||
}
|
||||
@ -182,7 +182,7 @@ sk_sp<sksg::Transform> AnimationBuilder::attachMatrix3D(const skjson::ObjectValu
|
||||
// The transform has no observable effects - we can discard.
|
||||
return parent;
|
||||
}
|
||||
adapter->tick(0);
|
||||
adapter->seek(0);
|
||||
} else {
|
||||
fCurrentAnimatorScope->push_back(adapter);
|
||||
}
|
||||
|
@ -13,11 +13,20 @@
|
||||
|
||||
namespace skottie::internal {
|
||||
|
||||
void AnimatablePropertyContainer::onTick(float t) {
|
||||
bool AnimatablePropertyContainer::onSeek(float t) {
|
||||
// The very first seek must trigger a sync, to ensure proper SG setup.
|
||||
bool dirty = !fHasSynced;
|
||||
|
||||
for (const auto& animator : fAnimators) {
|
||||
animator->tick(t);
|
||||
dirty |= animator->seek(t);
|
||||
}
|
||||
this->onSync();
|
||||
|
||||
if (dirty) {
|
||||
this->onSync();
|
||||
fHasSynced = true;
|
||||
}
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
void AnimatablePropertyContainer::attachDiscardableAdapter(
|
||||
@ -27,7 +36,7 @@ void AnimatablePropertyContainer::attachDiscardableAdapter(
|
||||
}
|
||||
|
||||
if (child->isStatic()) {
|
||||
child->tick(0);
|
||||
child->seek(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -83,7 +92,7 @@ bool AnimatablePropertyContainer::bindImpl(const AnimationBuilder& abuilder,
|
||||
if (animator->isConstant()) {
|
||||
// If all keyframes are constant, there is no reason to treat this
|
||||
// as an animated property - apply immediately and discard the animator.
|
||||
animator->tick(0);
|
||||
animator->seek(0);
|
||||
} else {
|
||||
fAnimators.push_back(std::move(animator));
|
||||
}
|
||||
|
@ -28,12 +28,13 @@ class Animator : public SkRefCnt {
|
||||
public:
|
||||
virtual ~Animator() = default;
|
||||
|
||||
void tick(float t) { this->onTick(t); }
|
||||
// Returns true if the state has changed.
|
||||
bool seek(float t) { return this->onSeek(t); }
|
||||
|
||||
protected:
|
||||
Animator() = default;
|
||||
|
||||
virtual void onTick(float t) = 0;
|
||||
virtual bool onSeek(float t) = 0;
|
||||
|
||||
private:
|
||||
Animator(const Animator&) = delete;
|
||||
@ -63,7 +64,7 @@ protected:
|
||||
void attachDiscardableAdapter(sk_sp<AnimatablePropertyContainer>);
|
||||
|
||||
private:
|
||||
void onTick(float) final;
|
||||
bool onSeek(float) final;
|
||||
|
||||
bool bindImpl(const AnimationBuilder&,
|
||||
const skjson::ObjectValue*,
|
||||
@ -71,6 +72,7 @@ private:
|
||||
void*);
|
||||
|
||||
std::vector<sk_sp<Animator>> fAnimators;
|
||||
bool fHasSynced = false;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
@ -285,17 +285,23 @@ private:
|
||||
, fValues(std::move(vs))
|
||||
, fTarget(target_value) {}
|
||||
|
||||
void onTick(float t) override {
|
||||
bool onSeek(float t) override {
|
||||
const auto& lerp_info = this->getLERPInfo(t);
|
||||
|
||||
bool updated;
|
||||
if (lerp_info.isConstant()) {
|
||||
*fTarget = fValues[SkToSizeT(lerp_info.vrec0.idx)];
|
||||
updated = (*fTarget != fValues[SkToSizeT(lerp_info.vrec0.idx)]);
|
||||
if (updated) {
|
||||
*fTarget = fValues[SkToSizeT(lerp_info.vrec0.idx)];
|
||||
}
|
||||
} else {
|
||||
ValueTraits<T>::Lerp(fValues[lerp_info.vrec0.idx],
|
||||
fValues[lerp_info.vrec1.idx],
|
||||
lerp_info.weight,
|
||||
fTarget);
|
||||
updated = ValueTraits<T>::Lerp(fValues[lerp_info.vrec0.idx],
|
||||
fValues[lerp_info.vrec1.idx],
|
||||
lerp_info.weight,
|
||||
fTarget);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
const std::vector<T> fValues;
|
||||
|
@ -54,10 +54,13 @@ private:
|
||||
: INHERITED(std::move(kfs), std::move(cms))
|
||||
, fTarget(target_value) {}
|
||||
|
||||
void onTick(float t) override {
|
||||
bool onSeek(float t) override {
|
||||
const auto& lerp_info = this->getLERPInfo(t);
|
||||
const auto old_value = *fTarget;
|
||||
|
||||
*fTarget = Lerp(lerp_info.vrec0.flt, lerp_info.vrec1.flt, lerp_info.weight);
|
||||
|
||||
return *fTarget != old_value;
|
||||
}
|
||||
|
||||
ScalarValue* fTarget;
|
||||
|
@ -132,7 +132,14 @@ private:
|
||||
, fValues(std::move(vs))
|
||||
, fTarget(target_value) {}
|
||||
|
||||
void onTick(float t) override {
|
||||
bool update(const Vec2Value& new_value) {
|
||||
const auto updated = (new_value != *fTarget);
|
||||
*fTarget = new_value;
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
bool onSeek(float t) override {
|
||||
const auto& lerp_info = this->getLERPInfo(t);
|
||||
|
||||
const auto& v0 = fValues[lerp_info.vrec0.idx];
|
||||
@ -141,13 +148,12 @@ private:
|
||||
// arc length.
|
||||
SkPoint pos;
|
||||
if (v0.cmeasure->getPosTan(lerp_info.weight * v0.cmeasure->length(), &pos, nullptr)) {
|
||||
*fTarget = { pos.fX, pos.fY };
|
||||
return;
|
||||
return this->update({ pos.fX, pos.fY });
|
||||
}
|
||||
}
|
||||
|
||||
const auto& v1 = fValues[lerp_info.vrec1.idx];
|
||||
*fTarget = Lerp(v0.v2, v1.v2, lerp_info.weight);
|
||||
return this->update(Lerp(v0.v2, v1.v2, lerp_info.weight));
|
||||
}
|
||||
|
||||
const std::vector<SpatialValue> fValues;
|
||||
|
@ -161,7 +161,7 @@ private:
|
||||
fTarget->resize(fVecLen);
|
||||
}
|
||||
|
||||
void onTick(float t) override {
|
||||
bool onSeek(float t) override {
|
||||
const auto& lerp_info = this->getLERPInfo(t);
|
||||
|
||||
SkASSERT(lerp_info.vrec0.idx + fVecLen <= fStorage.size());
|
||||
@ -173,15 +173,22 @@ private:
|
||||
auto* dst = fTarget->data();
|
||||
|
||||
if (lerp_info.isConstant()) {
|
||||
std::copy(v0, v0 + fVecLen, dst);
|
||||
return;
|
||||
if (std::memcmp(dst, v0, fVecLen * sizeof(float))) {
|
||||
std::copy(v0, v0 + fVecLen, dst);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t count = fVecLen;
|
||||
bool updated = false;
|
||||
|
||||
while (count >= 4) {
|
||||
Lerp(Sk4f::Load(v0), Sk4f::Load(v1), lerp_info.weight)
|
||||
.store(dst);
|
||||
const auto old_val = Sk4f::Load(dst),
|
||||
new_val = Lerp(Sk4f::Load(v0), Sk4f::Load(v1), lerp_info.weight);
|
||||
|
||||
updated |= (new_val != old_val).anyTrue();
|
||||
new_val.store(dst);
|
||||
|
||||
v0 += 4;
|
||||
v1 += 4;
|
||||
@ -190,8 +197,13 @@ private:
|
||||
}
|
||||
|
||||
while (count-- > 0) {
|
||||
*dst++ = Lerp(*v0++, *v1++, lerp_info.weight);
|
||||
const auto new_val = Lerp(*v0++, *v1++, lerp_info.weight);
|
||||
|
||||
updated |= (new_val != *dst);
|
||||
*dst++ = new_val;
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
const std::vector<float> fStorage;
|
||||
|
@ -69,7 +69,7 @@ const sksg::RenderNode* MotionBlurEffect::onNodeAt(const SkPoint&) const {
|
||||
|
||||
SkRect MotionBlurEffect::seekToSample(size_t sample_idx, const SkMatrix& ctm) const {
|
||||
SkASSERT(sample_idx < fSampleCount);
|
||||
fAnimator->tick(fT + fPhase + fDT * sample_idx);
|
||||
fAnimator->seek(fT + fPhase + fDT * sample_idx);
|
||||
|
||||
SkASSERT(this->children().size() == 1ul);
|
||||
return this->children()[0]->revalidate(nullptr, ctm);
|
||||
|
@ -39,15 +39,20 @@ public:
|
||||
, fTimeScale(time_scale)
|
||||
, fIsMultiframe(fAsset->isMultiFrame()) {}
|
||||
|
||||
void onTick(float t) override {
|
||||
bool onSeek(float t) override {
|
||||
if (!fIsMultiframe && fImageNode->getImage()) {
|
||||
// Single frame already resolved.
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto frame = fAsset->getFrame((t + fTimeBias) * fTimeScale);
|
||||
fImageTransformNode->setMatrix(image_matrix(frame, fAssetSize));
|
||||
fImageNode->setImage(std::move(frame));
|
||||
if (frame != fImageNode->getImage()) {
|
||||
fImageTransformNode->setMatrix(image_matrix(frame, fAssetSize));
|
||||
fImageNode->setImage(std::move(frame));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -52,18 +52,22 @@ public:
|
||||
, fTimeBias(time_bias)
|
||||
, fTimeScale(time_scale) {}
|
||||
|
||||
void onTick(float t) override {
|
||||
bool onSeek(float t) override {
|
||||
if (fRemapper) {
|
||||
// When time remapping is active, |t| is fully driven externally.
|
||||
fRemapper->tick(t);
|
||||
fRemapper->seek(t);
|
||||
t = fRemapper->t();
|
||||
} else {
|
||||
t = (t + fTimeBias) * fTimeScale;
|
||||
}
|
||||
|
||||
bool updated = false;
|
||||
|
||||
for (const auto& anim : fAnimators) {
|
||||
anim->tick(t);
|
||||
updated |= anim->seek(t);
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -149,9 +149,13 @@ bool ValueTraits<TextValue>::CanLerp(const TextValue&, const TextValue&) {
|
||||
}
|
||||
|
||||
template <>
|
||||
void ValueTraits<TextValue>::Lerp(const TextValue& v0, const TextValue&, float, TextValue* result) {
|
||||
bool ValueTraits<TextValue>::Lerp(const TextValue& v0, const TextValue&, float, TextValue* result) {
|
||||
// Text value keyframes are treated as selectors, not as interpolated values.
|
||||
*result = v0;
|
||||
if (v0 != *result) {
|
||||
*result = v0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace skottie
|
||||
|
@ -31,7 +31,7 @@ public:
|
||||
|
||||
operator bool() const { return fDidBind; }
|
||||
|
||||
const T& operator()(float t) { this->tick(t); return fValue; }
|
||||
const T& operator()(float t) { this->seek(t); return fValue; }
|
||||
|
||||
private:
|
||||
void onSync() override {}
|
||||
|
Loading…
Reference in New Issue
Block a user