Bug 1454324 - Skip calculating animation value if animation's progress value hasn't been changed since the last composition and if there are no other animations on the same element. r=birtles,kats

Note that we have to calculate animation values if there is another animation
since the other animation might be 'accumulate' or 'add', or might have a
missing keyframe (i.e. the calculated animation values will be used in the
missing keyframe).

MozReview-Commit-ID: rQyS9nuVJi

--HG--
extra : rebase_source : 6ddc70308e223a709eba9c4c2f05e42bbc0f3160
This commit is contained in:
Hiroyuki Ikezoe 2018-04-24 09:27:54 +09:00
Родитель 1695970073
Коммит df02c10b40
5 изменённых файлов: 150 добавлений и 48 удалений

Просмотреть файл

@ -1586,19 +1586,30 @@ KeyframeEffectReadOnly::MarkCascadeNeedsUpdate()
effectSet->MarkCascadeNeedsUpdate();
}
bool
KeyframeEffectReadOnly::HasComputedTimingChanged() const
/* static */ bool
KeyframeEffectReadOnly::HasComputedTimingChanged(
const ComputedTiming& aComputedTiming,
IterationCompositeOperation aIterationComposite,
const Nullable<double>& aProgressOnLastCompose,
uint64_t aCurrentIterationOnLastCompose)
{
// Typically we don't need to request a restyle if the progress hasn't
// changed since the last call to ComposeStyle. The one exception is if the
// iteration composite mode is 'accumulate' and the current iteration has
// changed, since that will often produce a different result.
return aComputedTiming.mProgress != aProgressOnLastCompose ||
(aIterationComposite == IterationCompositeOperation::Accumulate &&
aComputedTiming.mCurrentIteration != aCurrentIterationOnLastCompose);
}
bool
KeyframeEffectReadOnly::HasComputedTimingChanged() const
{
ComputedTiming computedTiming = GetComputedTiming();
return computedTiming.mProgress != mProgressOnLastCompose ||
(mEffectOptions.mIterationComposite ==
IterationCompositeOperation::Accumulate &&
computedTiming.mCurrentIteration !=
mCurrentIterationOnLastCompose);
return HasComputedTimingChanged(computedTiming,
mEffectOptions.mIterationComposite,
mProgressOnLastCompose,
mCurrentIterationOnLastCompose);
}
bool

Просмотреть файл

@ -266,6 +266,12 @@ public:
return result;
}
static bool HasComputedTimingChanged(
const ComputedTiming& aComputedTiming,
IterationCompositeOperation aIterationComposite,
const Nullable<double>& aProgressOnLastCompose,
uint64_t aCurrentIterationOnLastCompose);
protected:
KeyframeEffectReadOnly(nsIDocument* aDocument,
const Maybe<OwningAnimationTarget>& aTarget,

Просмотреть файл

@ -135,16 +135,16 @@ CompositorAnimationStorage::SetAnimations(uint64_t aId, const AnimationArray& aV
}
void
AnimationHelper::SampleResult
AnimationHelper::SampleAnimationForEachNode(
TimeStamp aTime,
AnimationArray& aAnimations,
InfallibleTArray<AnimData>& aAnimationData,
RefPtr<RawServoAnimationValue>& aAnimationValue,
bool& aHasInEffectAnimations)
RefPtr<RawServoAnimationValue>& aAnimationValue)
{
MOZ_ASSERT(!aAnimations.IsEmpty(), "Should be called with animations");
bool hasInEffectAnimations = false;
// Process in order, since later aAnimations override earlier ones.
for (size_t i = 0, iEnd = aAnimations.Length(); i < iEnd; ++i) {
Animation& animation = aAnimations[i];
@ -175,6 +175,27 @@ AnimationHelper::SampleAnimationForEachNode(
continue;
}
dom::IterationCompositeOperation iterCompositeOperation =
static_cast<dom::IterationCompositeOperation>(
animation.iterationComposite());
// Skip caluculation if the progress hasn't changed since the last
// calculation.
// Note that we don't skip calculate this animation if there is another
// animation since the other animation might be 'accumulate' or 'add', or
// might have a missing keyframe (i.e. this animation value will be used in
// the missing keyframe).
// FIXME Bug 1455476: We should do this optimizations for the case where
// the layer has multiple animations.
if (iEnd == 1 &&
!dom::KeyframeEffectReadOnly::HasComputedTimingChanged(
computedTiming,
iterCompositeOperation,
animData.mProgressOnLastCompose,
animData.mCurrentIterationOnLastCompose)) {
return SampleResult::Skipped;
}
uint32_t segmentIndex = 0;
size_t segmentSize = animation.segments().Length();
AnimationSegment* segment = animation.segments().Elements();
@ -193,6 +214,20 @@ AnimationHelper::SampleAnimationForEachNode(
positionInSegment,
computedTiming.mBeforeFlag);
// Like above optimization, skip caluculation if the target segment isn't
// changed and if the portion in the segment isn't changed.
// This optimization is needed for CSS animations/transitions with step
// timing functions (e.g. the throbber animation on tab or frame based
// animations).
// FIXME Bug 1455476: Like the above optimization, we should apply this
// optimizations for multiple animation cases as well.
if (iEnd == 1 &&
animData.mSegmentIndexOnLastCompose == segmentIndex &&
!animData.mPortionInSegmentOnLastCompose.IsNull() &&
animData.mPortionInSegmentOnLastCompose.Value() == portion) {
return SampleResult::Skipped;
}
AnimationPropertySegment animSegment;
animSegment.mFromKey = 0.0;
animSegment.mToKey = 1.0;
@ -206,10 +241,6 @@ AnimationHelper::SampleAnimationForEachNode(
static_cast<dom::CompositeOperation>(segment->endComposite());
// interpolate the property
dom::IterationCompositeOperation iterCompositeOperation =
static_cast<dom::IterationCompositeOperation>(
animation.iterationComposite());
aAnimationValue =
Servo_ComposeAnimationSegment(
&animSegment,
@ -218,7 +249,11 @@ AnimationHelper::SampleAnimationForEachNode(
iterCompositeOperation,
portion,
computedTiming.mCurrentIteration).Consume();
aHasInEffectAnimations = true;
hasInEffectAnimations = true;
animData.mProgressOnLastCompose = computedTiming.mProgress;
animData.mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration;
animData.mSegmentIndexOnLastCompose = segmentIndex;
animData.mPortionInSegmentOnLastCompose.SetValue(portion);
}
#ifdef DEBUG
@ -244,6 +279,8 @@ AnimationHelper::SampleAnimationForEachNode(
"All of members of TransformData should be the same");
}
#endif
return hasInEffectAnimations ? SampleResult::Sampled : SampleResult::None;
}
struct BogusAnimation {};
@ -512,7 +549,6 @@ AnimationHelper::SampleAnimations(CompositorAnimationStorage* aStorage,
//Sample the animations in CompositorAnimationStorage
for (auto iter = aStorage->ConstAnimationsTableIter();
!iter.Done(); iter.Next()) {
bool hasInEffectAnimations = false;
AnimationArray* animations = iter.UserData();
if (animations->IsEmpty()) {
continue;
@ -523,13 +559,13 @@ AnimationHelper::SampleAnimations(CompositorAnimationStorage* aStorage,
AnimationHelper::SetAnimations(*animations,
animationData,
animationValue);
AnimationHelper::SampleAnimationForEachNode(aTime,
*animations,
animationData,
animationValue,
hasInEffectAnimations);
AnimationHelper::SampleResult sampleResult =
AnimationHelper::SampleAnimationForEachNode(aTime,
*animations,
animationData,
animationValue);
if (!hasInEffectAnimations) {
if (sampleResult != AnimationHelper::SampleResult::Sampled) {
continue;
}

Просмотреть файл

@ -7,11 +7,12 @@
#ifndef mozilla_layers_AnimationHelper_h
#define mozilla_layers_AnimationHelper_h
#include "mozilla/dom/Nullable.h"
#include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction
#include "mozilla/layers/LayersMessages.h" // for TransformData, etc
#include "mozilla/TimeStamp.h" // for TimeStamp
#include "mozilla/TimingParams.h"
#include "X11UndefineNone.h"
namespace mozilla {
struct AnimationValue;
@ -25,6 +26,15 @@ struct AnimData {
InfallibleTArray<RefPtr<RawServoAnimationValue>> mEndValues;
InfallibleTArray<Maybe<mozilla::ComputedTimingFunction>> mFunctions;
TimingParams mTiming;
// These two variables correspond to the variables of the same name in
// KeyframeEffectReadOnly and are used for the same purpose: to skip composing
// animations whose progress has not changed.
dom::Nullable<double> mProgressOnLastCompose;
uint64_t mCurrentIterationOnLastCompose = 0;
// These two variables are used for a similar optimization above but are
// applied to the timing function in each keyframe.
uint32_t mSegmentIndexOnLastCompose = 0;
dom::Nullable<double> mPortionInSegmentOnLastCompose;
};
struct AnimationTransform {
@ -195,16 +205,27 @@ class AnimationHelper
{
public:
enum class SampleResult {
None,
Skipped,
Sampled
};
/**
* Sample animations based on a given time stamp for a element(layer) with
* its animation data.
*
* Returns SampleResult::None if none of the animations are producing a result
* (e.g. they are in the delay phase with no backwards fill),
* SampleResult::Skipped if the animation output did not change since the last
* call of this function,
* SampleResult::Sampled if the animation output was updated.
*/
static void
static SampleResult
SampleAnimationForEachNode(TimeStamp aTime,
AnimationArray& aAnimations,
InfallibleTArray<AnimData>& aAnimationData,
RefPtr<RawServoAnimationValue>& aAnimationValue,
bool& aHasInEffectAnimations);
RefPtr<RawServoAnimationValue>& aAnimationValue);
/**
* Populates AnimData stuctures into |aAnimData| and |aBaseAnimationStyle|
* based on |aAnimations|.

Просмотреть файл

@ -660,30 +660,58 @@ SampleAnimations(Layer* aLayer,
return;
}
isAnimating = true;
bool hasInEffectAnimations = false;
RefPtr<RawServoAnimationValue> animationValue =
layer->GetBaseAnimationStyle();
AnimationHelper::SampleAnimationForEachNode(aTime,
animations,
layer->GetAnimationData(),
animationValue,
hasInEffectAnimations);
if (hasInEffectAnimations) {
Animation& animation = animations.LastElement();
ApplyAnimatedValue(layer,
aStorage,
animation.property(),
animation.data(),
animationValue);
} else {
// Set non-animation values in the case there are no in-effect
// animations (i.e. all animations are in delay state or already
// finished with non-forward fill modes).
HostLayer* layerCompositor = layer->AsHostLayer();
layerCompositor->SetShadowBaseTransform(layer->GetBaseTransform());
layerCompositor->SetShadowTransformSetByAnimation(false);
layerCompositor->SetShadowOpacity(layer->GetOpacity());
layerCompositor->SetShadowOpacitySetByAnimation(false);
AnimationHelper::SampleResult sampleResult =
AnimationHelper::SampleAnimationForEachNode(aTime,
animations,
layer->GetAnimationData(),
animationValue);
switch (sampleResult) {
case AnimationHelper::SampleResult::Sampled: {
Animation& animation = animations.LastElement();
ApplyAnimatedValue(layer,
aStorage,
animation.property(),
animation.data(),
animationValue);
break;
}
case AnimationHelper::SampleResult::Skipped:
// We don't need to update animation values for this layer since
// the values haven't changed.
#ifdef DEBUG
// Sanity check that the animation value is surely unchanged.
switch (animations[0].property()) {
case eCSSProperty_opacity:
MOZ_ASSERT(FuzzyEqualsMultiplicative(
layer->AsHostLayer()->GetShadowOpacity(),
*(aStorage->GetAnimationOpacity(layer->GetCompositorAnimationsId()))));
break;
case eCSSProperty_transform: {
AnimatedValue* transform =
aStorage->GetAnimatedValue(layer->GetCompositorAnimationsId());
MOZ_ASSERT(
transform->mTransform.mTransformInDevSpace.FuzzyEqualsMultiplicative(
(layer->AsHostLayer()->GetShadowBaseTransform())));
break;
}
default:
MOZ_ASSERT_UNREACHABLE("Unsupported properties");
break;
}
#endif
break;
case AnimationHelper::SampleResult::None: {
HostLayer* layerCompositor = layer->AsHostLayer();
layerCompositor->SetShadowBaseTransform(layer->GetBaseTransform());
layerCompositor->SetShadowTransformSetByAnimation(false);
layerCompositor->SetShadowOpacity(layer->GetOpacity());
layerCompositor->SetShadowOpacitySetByAnimation(false);
break;
}
default:
break;
}
});