[skottie] VectorValue specialization
Currently we're handling VectorValues via a generic animator => we use vector<vector<float>> for storage. That's kinda clunky, especially for small-size vectors (3d values, colors). Introduce a more efficient VectorKeyframeAnimator: - stores vector values in a contiguous/consolidated float array - keyframes reference value offsets in storage - fast/sk4f lerp impl Change-Id: Ia9538068f2c722c2d2209f87e26564f0fe28ac31 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/277578 Commit-Queue: Florin Malita <fmalita@chromium.org> Reviewed-by: Mike Klein <mtklein@google.com>
This commit is contained in:
parent
4765da43c5
commit
3a76975a7c
@ -36,6 +36,7 @@ skia_skottie_sources = [
|
||||
"$_src/animator/Keyframe.h",
|
||||
"$_src/animator/Scalar.cpp",
|
||||
"$_src/animator/Vec2.cpp",
|
||||
"$_src/animator/Vector.cpp",
|
||||
"$_src/effects/DropShadowEffect.cpp",
|
||||
"$_src/effects/Effects.cpp",
|
||||
"$_src/effects/Effects.h",
|
||||
|
@ -15,6 +15,8 @@
|
||||
#include "modules/skottie/src/SkottieJson.h"
|
||||
#include "modules/skottie/src/SkottiePriv.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace skottie {
|
||||
|
||||
template <>
|
||||
@ -46,29 +48,6 @@ SkScalar ValueTraits<ScalarValue>::As<SkScalar>(const ScalarValue& v) {
|
||||
return v;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool ValueTraits<VectorValue>::FromJSON(const skjson::Value& jv, const internal::AnimationBuilder*,
|
||||
VectorValue* v) {
|
||||
return Parse(jv, v);
|
||||
}
|
||||
|
||||
template <>
|
||||
bool ValueTraits<VectorValue>::CanLerp(const VectorValue& v1, const VectorValue& v2) {
|
||||
return v1.size() == v2.size();
|
||||
}
|
||||
|
||||
template <>
|
||||
void ValueTraits<VectorValue>::Lerp(const VectorValue& v0, const VectorValue& v1, float t,
|
||||
VectorValue* result) {
|
||||
SkASSERT(v0.size() == v1.size());
|
||||
|
||||
result->resize(v0.size());
|
||||
|
||||
for (size_t i = 0; i < v0.size(); ++i) {
|
||||
ValueTraits<ScalarValue>::Lerp(v0[i], v1[i], t, &(*result)[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// DEPRECATED: remove after converting everything to SkColor4f
|
||||
template <>
|
||||
template <>
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "include/core/SkPath.h"
|
||||
#include "include/core/SkScalar.h"
|
||||
#include "include/core/SkString.h"
|
||||
#include "include/private/SkNoncopyable.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
@ -37,7 +38,7 @@ struct ValueTraits {
|
||||
|
||||
using ScalarValue = SkScalar;
|
||||
using Vec2Value = SkV2;
|
||||
using VectorValue = std::vector<ScalarValue>;
|
||||
using VectorValue = std::vector<float>;
|
||||
|
||||
struct BezierVertex {
|
||||
SkPoint fInPoint, // "in" control point, relative to the vertex
|
||||
|
@ -306,27 +306,6 @@ private:
|
||||
|
||||
} // namespace
|
||||
|
||||
template <>
|
||||
bool AnimatablePropertyContainer::bind<VectorValue>(const AnimationBuilder& abuilder,
|
||||
const skjson::ObjectValue* jprop,
|
||||
VectorValue* v) {
|
||||
if (!jprop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseDefault<bool>((*jprop)["s"], false)) {
|
||||
// Regular (static or keyframed) vector value.
|
||||
KeyframeAnimator<VectorValue>::Builder builder;
|
||||
return this->bindImpl(abuilder, jprop, builder, v);
|
||||
}
|
||||
|
||||
// Separate-dimensions vector value: each component is animated independently.
|
||||
v->resize(3ul, 0);
|
||||
return this->bind(abuilder, (*jprop)["x"], v->data() + 0)
|
||||
| this->bind(abuilder, (*jprop)["y"], v->data() + 1)
|
||||
| this->bind(abuilder, (*jprop)["z"], v->data() + 2);
|
||||
}
|
||||
|
||||
template <>
|
||||
bool AnimatablePropertyContainer::bind<ShapeValue>(const AnimationBuilder& abuilder,
|
||||
const skjson::ObjectValue* jprop,
|
||||
|
@ -134,7 +134,7 @@ private:
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
T LERP(const T& a, const T& b, float t) { return a + (b - a) * t; }
|
||||
T Lerp(const T& a, const T& b, float t) { return a + (b - a) * t; }
|
||||
|
||||
} // namespace skottie::internal
|
||||
|
||||
|
@ -23,7 +23,7 @@ public:
|
||||
sk_sp<KeyframeAnimatorBase> make(const AnimationBuilder& abuilder,
|
||||
const skjson::ArrayValue& jkfs,
|
||||
void* target_value) override {
|
||||
SkASSERT(jkfs.size() >= 1);
|
||||
SkASSERT(jkfs.size() > 0);
|
||||
if (!this->parseKeyframes(abuilder, jkfs)) {
|
||||
return nullptr;
|
||||
}
|
||||
@ -48,16 +48,16 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
explicit ScalarKeyframeAnimator(std::vector<Keyframe> kfs,
|
||||
std::vector<SkCubicMap> cms,
|
||||
ScalarValue* target_value)
|
||||
ScalarKeyframeAnimator(std::vector<Keyframe> kfs,
|
||||
std::vector<SkCubicMap> cms,
|
||||
ScalarValue* target_value)
|
||||
: INHERITED(std::move(kfs), std::move(cms))
|
||||
, fTarget(target_value) {}
|
||||
|
||||
void onTick(float t) override {
|
||||
const auto& lerp_info = this->getLERPInfo(t);
|
||||
|
||||
*fTarget = LERP(lerp_info.vrec0.flt, lerp_info.vrec1.flt, lerp_info.weight);
|
||||
*fTarget = Lerp(lerp_info.vrec0.flt, lerp_info.vrec1.flt, lerp_info.weight);
|
||||
}
|
||||
|
||||
ScalarValue* fTarget;
|
||||
|
@ -147,7 +147,7 @@ private:
|
||||
}
|
||||
|
||||
const auto& v1 = fValues[lerp_info.vrec1.idx];
|
||||
*fTarget = LERP(v0.v2, v1.v2, lerp_info.weight);
|
||||
*fTarget = Lerp(v0.v2, v1.v2, lerp_info.weight);
|
||||
}
|
||||
|
||||
const std::vector<SpatialValue> fValues;
|
||||
|
229
modules/skottie/src/animator/Vector.cpp
Normal file
229
modules/skottie/src/animator/Vector.cpp
Normal file
@ -0,0 +1,229 @@
|
||||
/*
|
||||
* Copyright 2020 Google Inc.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license that can be
|
||||
* found in the LICENSE file.
|
||||
*/
|
||||
|
||||
#include "modules/skottie/src/animator/Keyframe.h"
|
||||
|
||||
#include "include/private/SkNx.h"
|
||||
#include "modules/skottie/src/SkottieJson.h"
|
||||
#include "modules/skottie/src/SkottieValue.h"
|
||||
#include "modules/skottie/src/animator/Animator.h"
|
||||
#include "src/core/SkSafeMath.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace skottie {
|
||||
|
||||
// Parses an array of exact size.
|
||||
static bool parse_array(const skjson::ArrayValue* ja, float* a, size_t count) {
|
||||
if (!ja || ja->size() != count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (!Parse((*ja)[i], a + i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool ValueTraits<VectorValue>::FromJSON(const skjson::Value& jv, const internal::AnimationBuilder*,
|
||||
VectorValue* v) {
|
||||
if (const skjson::ArrayValue* ja = jv) {
|
||||
const auto size = ja->size();
|
||||
v->resize(size);
|
||||
return parse_array(ja, v->data(), size);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
namespace {
|
||||
|
||||
// Vector specialization - stores float vector values (of same length) in consolidated/contiguous
|
||||
// storage. Keyframe records hold the storage offset for each value:
|
||||
//
|
||||
// fStorage: [ vec0 ][ vec1 ] ... [ vecN ]
|
||||
// <- vec_len -> <- vec_len -> <- vec_len ->
|
||||
//
|
||||
// ^ ^ ^
|
||||
// fKFs[]: .idx .idx ... .idx
|
||||
//
|
||||
class VectorKeyframeAnimator final : public KeyframeAnimatorBase {
|
||||
public:
|
||||
class Builder final : public KeyframeAnimatorBuilder {
|
||||
public:
|
||||
sk_sp<KeyframeAnimatorBase> make(const AnimationBuilder& abuilder,
|
||||
const skjson::ArrayValue& jkfs,
|
||||
void* target_value) override {
|
||||
SkASSERT(jkfs.size() > 0);
|
||||
|
||||
// peek at the first keyframe array value to find our vector length
|
||||
const skjson::ObjectValue* jkf0 = jkfs[0];
|
||||
const skjson::ArrayValue* ja = jkf0
|
||||
? static_cast<const skjson::ArrayValue*>((*jkf0)["s"])
|
||||
: nullptr;
|
||||
if (!ja) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
fVecLen = ja->size();
|
||||
|
||||
SkSafeMath safe;
|
||||
// total elements: vector length x number vectors
|
||||
const auto total_size = safe.mul(fVecLen, jkfs.size());
|
||||
|
||||
// we must be able to store all offsets in Keyframe::Value::idx (uint32_t)
|
||||
if (!safe || !SkTFitsIn<uint32_t>(total_size)) {
|
||||
return nullptr;
|
||||
}
|
||||
fStorage.resize(total_size);
|
||||
|
||||
if (!this->parseKeyframes(abuilder, jkfs)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// parseKFValue() might have stored fewer vectors thanks to tail-deduping.
|
||||
SkASSERT(fCurrentVec <= jkfs.size());
|
||||
fStorage.resize(fCurrentVec * fVecLen);
|
||||
fStorage.shrink_to_fit();
|
||||
|
||||
return sk_sp<VectorKeyframeAnimator>(
|
||||
new VectorKeyframeAnimator(std::move(fKFs),
|
||||
std::move(fCMs),
|
||||
std::move(fStorage),
|
||||
fVecLen,
|
||||
static_cast<VectorValue*>(target_value)));
|
||||
}
|
||||
|
||||
bool parseValue(const AnimationBuilder& abuilder,
|
||||
const skjson::Value& jv,
|
||||
void* v) const override {
|
||||
return ValueTraits<VectorValue>::FromJSON(jv, &abuilder, static_cast<VectorValue*>(v));
|
||||
}
|
||||
|
||||
private:
|
||||
bool parseKFValue(const AnimationBuilder&,
|
||||
const skjson::ObjectValue&,
|
||||
const skjson::Value& jv,
|
||||
Keyframe::Value* kfv) override {
|
||||
auto offset = fCurrentVec * fVecLen;
|
||||
SkASSERT(offset + fVecLen <= fStorage.size());
|
||||
|
||||
if (!parse_array(jv, fStorage.data() + offset, fVecLen)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SkASSERT(!fCurrentVec || offset >= fVecLen);
|
||||
// compare with previous vector value
|
||||
if (fCurrentVec > 0 && !memcmp(fStorage.data() + offset,
|
||||
fStorage.data() + offset - fVecLen,
|
||||
fVecLen * sizeof(float))) {
|
||||
// repeating value -> use prev offset (dedupe)
|
||||
offset -= fVecLen;
|
||||
} else {
|
||||
// new value -> advance the current index
|
||||
fCurrentVec += 1;
|
||||
}
|
||||
|
||||
// Keyframes record the storage-offset for a given vector value.
|
||||
kfv->idx = SkToU32(offset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<float> fStorage;
|
||||
size_t fVecLen, // size of individual vector values we store
|
||||
fCurrentVec = 0; // vector value index being parsed (corresponding
|
||||
// storage offset is fCurrentVec * fVecLen)
|
||||
};
|
||||
|
||||
private:
|
||||
VectorKeyframeAnimator(std::vector<Keyframe> kfs,
|
||||
std::vector<SkCubicMap> cms,
|
||||
std::vector<float> storage,
|
||||
size_t vec_len,
|
||||
VectorValue* target_value)
|
||||
: INHERITED(std::move(kfs), std::move(cms))
|
||||
, fStorage(std::move(storage))
|
||||
, fVecLen(vec_len)
|
||||
, fTarget(target_value) {
|
||||
|
||||
// Resize the target value appropriately.
|
||||
fTarget->resize(fVecLen);
|
||||
}
|
||||
|
||||
void onTick(float t) override {
|
||||
const auto& lerp_info = this->getLERPInfo(t);
|
||||
|
||||
SkASSERT(lerp_info.vrec0.idx + fVecLen <= fStorage.size());
|
||||
SkASSERT(lerp_info.vrec1.idx + fVecLen <= fStorage.size());
|
||||
SkASSERT(fTarget->size() == fVecLen);
|
||||
|
||||
const auto* v0 = fStorage.data() + lerp_info.vrec0.idx;
|
||||
const auto* v1 = fStorage.data() + lerp_info.vrec1.idx;
|
||||
auto* dst = fTarget->data();
|
||||
|
||||
if (lerp_info.isConstant()) {
|
||||
std::copy(v0, v0 + fVecLen, dst);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t count = fVecLen;
|
||||
|
||||
while (count >= 4) {
|
||||
Lerp(Sk4f::Load(v0), Sk4f::Load(v1), lerp_info.weight)
|
||||
.store(dst);
|
||||
|
||||
v0 += 4;
|
||||
v1 += 4;
|
||||
dst += 4;
|
||||
count -= 4;
|
||||
}
|
||||
|
||||
while (count-- > 0) {
|
||||
*dst++ = Lerp(*v0++, *v1++, lerp_info.weight);
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<float> fStorage;
|
||||
const size_t fVecLen;
|
||||
|
||||
VectorValue* fTarget;
|
||||
|
||||
using INHERITED = KeyframeAnimatorBase;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
template <>
|
||||
bool AnimatablePropertyContainer::bind<VectorValue>(const AnimationBuilder& abuilder,
|
||||
const skjson::ObjectValue* jprop,
|
||||
VectorValue* v) {
|
||||
if (!jprop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseDefault<bool>((*jprop)["s"], false)) {
|
||||
// Regular (static or keyframed) vector value.
|
||||
VectorKeyframeAnimator::Builder builder;
|
||||
return this->bindImpl(abuilder, jprop, builder, v);
|
||||
}
|
||||
|
||||
// Separate-dimensions vector value: each component is animated independently.
|
||||
*v = { 0, 0, 0 };
|
||||
return this->bind(abuilder, (*jprop)["x"], v->data() + 0)
|
||||
| this->bind(abuilder, (*jprop)["y"], v->data() + 1)
|
||||
| this->bind(abuilder, (*jprop)["z"], v->data() + 2);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace skottie
|
Loading…
Reference in New Issue
Block a user