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
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Tue, 24 Apr 2018 09:27:54 +0900
changeset 468768 b63e4bff951e97ce23e5673cb55a990a1bbb1a3c
parent 468767 6ba049b00c55f30638773a36e702e4144f23c8e5
child 468769 7c7be4064df10bfade7511a9b25fb4b21511029c
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbirtles, kats
bugs1454324
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
dom/animation/KeyframeEffectReadOnly.cpp
dom/animation/KeyframeEffectReadOnly.h
gfx/layers/AnimationHelper.cpp
gfx/layers/AnimationHelper.h
gfx/layers/composite/AsyncCompositionManager.cpp
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -1581,29 +1581,40 @@ KeyframeEffectReadOnly::MarkCascadeNeeds
   EffectSet* effectSet = EffectSet::GetEffectSet(mTarget->mElement,
                                                  mTarget->mPseudoType);
   if (!effectSet) {
     return;
   }
   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
 KeyframeEffectReadOnly::ContainsAnimatedScale(const nsIFrame* aFrame) const
 {
   if (!IsCurrent()) {
     return false;
   }
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -261,16 +261,22 @@ public:
     // We cannot use getters_AddRefs on RawServoAnimationValue because it is
     // an incomplete type, so Get() doesn't work. Instead, use GetWeak, and
     // then assign the raw pointer to a RefPtr.
     result.mServo = mBaseStyleValuesForServo.GetWeak(aProperty, &hasProperty);
     MOZ_ASSERT(hasProperty || result.IsNull());
     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,
                          AnimationEffectTimingReadOnly* aTiming,
                          const KeyframeEffectParams& aOptions);
 
   ~KeyframeEffectReadOnly() override = default;
 
--- a/gfx/layers/AnimationHelper.cpp
+++ b/gfx/layers/AnimationHelper.cpp
@@ -130,26 +130,26 @@ void
 CompositorAnimationStorage::SetAnimations(uint64_t aId, const AnimationArray& aValue)
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   AnimationArray* value = new AnimationArray(aValue);
   mAnimations.Put(aId, value);
 }
 
 
-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];
     AnimData& animData = aAnimationData[i];
 
     MOZ_ASSERT((!animation.originTime().IsNull() &&
                 animation.startTime().type() ==
                   MaybeTimeDuration::TTimeDuration) ||
@@ -170,16 +170,37 @@ AnimationHelper::SampleAnimationForEachN
       dom::AnimationEffectReadOnly::GetComputedTimingAt(
         dom::Nullable<TimeDuration>(elapsedDuration), animData.mTiming,
         animation.playbackRate());
 
     if (computedTiming.mProgress.IsNull()) {
       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();
     while (segment->endPortion() < computedTiming.mProgress.Value() &&
            segmentIndex < segmentSize - 1) {
       ++segment;
       ++segmentIndex;
     }
@@ -188,42 +209,56 @@ AnimationHelper::SampleAnimationForEachN
       (computedTiming.mProgress.Value() - segment->startPortion()) /
       (segment->endPortion() - segment->startPortion());
 
     double portion =
       ComputedTimingFunction::GetPortion(animData.mFunctions[segmentIndex],
                                          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;
     animSegment.mFromValue =
       AnimationValue(animData.mStartValues[segmentIndex]);
     animSegment.mToValue =
       AnimationValue(animData.mEndValues[segmentIndex]);
     animSegment.mFromComposite =
       static_cast<dom::CompositeOperation>(segment->startComposite());
     animSegment.mToComposite =
       static_cast<dom::CompositeOperation>(segment->endComposite());
 
     // interpolate the property
-    dom::IterationCompositeOperation iterCompositeOperation =
-        static_cast<dom::IterationCompositeOperation>(
-          animation.iterationComposite());
-
     aAnimationValue =
       Servo_ComposeAnimationSegment(
         &animSegment,
         aAnimationValue,
         animData.mEndValues.LastElement(),
         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
   // Sanity check that all of animation data are the same.
   const AnimationData& lastData = aAnimations.LastElement().data();
   for (const Animation& animation : aAnimations) {
     const AnimationData& data = animation.data();
     MOZ_ASSERT(data.type() == lastData.type(),
@@ -239,16 +274,18 @@ AnimationHelper::SampleAnimationForEachN
                transformData.transformOrigin() ==
                  lastTransformData.transformOrigin() &&
                transformData.bounds() == lastTransformData.bounds() &&
                transformData.appUnitsPerDevPixel() ==
                  lastTransformData.appUnitsPerDevPixel(),
                "All of members of TransformData should be the same");
   }
 #endif
+
+  return hasInEffectAnimations ? SampleResult::Sampled : SampleResult::None;
 }
 
 struct BogusAnimation {};
 
 static inline Result<Ok, BogusAnimation>
 SetCSSAngle(const CSSAngle& aAngle, nsCSSValue& aValue)
 {
   aValue.SetFloatValue(aAngle.value(), nsCSSUnit(aAngle.unit()));
@@ -507,34 +544,33 @@ AnimationHelper::SampleAnimations(Compos
   // Do nothing if there are no compositor animations
   if (!aStorage->AnimationsCount()) {
     return;
   }
 
   //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;
     }
 
     RefPtr<RawServoAnimationValue> animationValue;
     InfallibleTArray<AnimData> animationData;
     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;
     }
 
     // Store the AnimatedValue
     Animation& animation = animations->LastElement();
     switch (animation.property()) {
       case eCSSProperty_opacity: {
         aStorage->SetAnimatedValue(
--- a/gfx/layers/AnimationHelper.h
+++ b/gfx/layers/AnimationHelper.h
@@ -2,34 +2,44 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #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;
 namespace layers {
 class Animation;
 
 typedef InfallibleTArray<layers::Animation> AnimationArray;
 
 struct AnimData {
   InfallibleTArray<RefPtr<RawServoAnimationValue>> mStartValues;
   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 {
   /*
    * This transform is calculated from sampleanimation in device pixel
    * and used by compositor.
    */
   gfx::Matrix4x4 mTransformInDevSpace;
@@ -190,26 +200,37 @@ private:
  * This utility class allows reusing code between the webrender and
  * non-webrender compositor-side implementations. It provides
  * utility functions for sampling animations at particular timestamps.
  */
 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|.
    */
   static void
   SetAnimations(AnimationArray& aAnimations,
                 InfallibleTArray<AnimData>& aAnimData,
                 RefPtr<RawServoAnimationValue>& aBaseAnimationStyle);
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -655,40 +655,68 @@ SampleAnimations(Layer* aLayer,
       aLayer,
       [&] (Layer* layer)
       {
         AnimationArray& animations = layer->GetAnimations();
         if (animations.IsEmpty()) {
           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;
         }
       });
 
   return isAnimating;
 }
 
 void
 AsyncCompositionManager::RecordShadowTransforms(Layer* aLayer)