Bug 1458457 - Use the current frame time stamp instead of the previous frame's time stamp in the case where the animation was play pending state when the animation was sent to the compositor. r?birtles draft
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Tue, 08 May 2018 16:58:06 +0900
changeset 792312 03c0271bee30e252b6870382334a1cfdba84b28b
parent 792290 5c2110d892a495fdff71937a3eb32ac9fc309a05
push id109081
push userhikezoe@mozilla.com
push dateTue, 08 May 2018 07:58:27 +0000
reviewersbirtles
bugs1458457
milestone62.0a1
Bug 1458457 - Use the current frame time stamp instead of the previous frame's time stamp in the case where the animation was play pending state when the animation was sent to the compositor. r?birtles When we send play-pending animation to the compositor, we resolve its start time with the time at the very moment we send the animation to the compositor instead of refresh drivers' tick time. That means that it's possible that the start time indicates more future time stamp than the previous frame time stamp on the compositor. If we were to use the previous frame timestamp in this case, then we'd end up treating the animation as if it had not yet started so we would skip its initial paint. MozReview-Commit-ID: 8TdZ6m0gqMm
gfx/layers/AnimationHelper.cpp
gfx/layers/AnimationHelper.h
gfx/layers/composite/AsyncCompositionManager.cpp
--- a/gfx/layers/AnimationHelper.cpp
+++ b/gfx/layers/AnimationHelper.cpp
@@ -147,17 +147,18 @@ CompositorAnimationStorage::SetAnimation
 
 
 AnimationHelper::SampleResult
 AnimationHelper::SampleAnimationForEachNode(
   TimeStamp aPreviousFrameTime,
   TimeStamp aCurrentFrameTime,
   AnimationArray& aAnimations,
   InfallibleTArray<AnimData>& aAnimationData,
-  RefPtr<RawServoAnimationValue>& aAnimationValue)
+  RefPtr<RawServoAnimationValue>& aAnimationValue,
+  const AnimatedValue* aPreviousValue)
 {
   MOZ_ASSERT(!aAnimations.IsEmpty(), "Should be called with animations");
 
   bool hasInEffectAnimations = false;
 #ifdef DEBUG
   // In cases where this function returns a SampleResult::Skipped, we actually
   // do populate aAnimationValue in debug mode, so that we can MOZ_ASSERT at the
   // call site that the value that would have been computed matches the stored
@@ -172,24 +173,56 @@ AnimationHelper::SampleAnimationForEachN
 
     MOZ_ASSERT((!animation.originTime().IsNull() &&
                 animation.startTime().type() ==
                   MaybeTimeDuration::TTimeDuration) ||
                animation.isNotPlaying(),
                "If we are playing, we should have an origin time and a start"
                " time");
 
-    // Use a previous vsync time to make main thread animations and compositor
-    // more in sync with each other.
-    // On the initial frame we use the current frame time here so the timestamp
-    // on the second frame are the same as the initial frame, but it does not
-    // matter.
-    const TimeStamp& timeStamp = !aPreviousFrameTime.IsNull()
-      ? aPreviousFrameTime
-      : aCurrentFrameTime;
+    // Determine if the animation was play-pending and used a ready time later
+    // than the previous frame time.
+    //
+    // To determine this, _all_ of the following consitions need to hold:
+    //
+    // * There was no previous animation value (i.e. this is the first frame for
+    //   the animation since it was sent to the compositor), and
+    // * The animation is playing, and
+    // * There is a previous frame time, and
+    // * The ready time of the animation is ahead of the previous frame time.
+    //
+    bool hasFutureReadyTime = false;
+    if (!aPreviousValue &&
+        !animation.isNotPlaying() &&
+        !aPreviousFrameTime.IsNull()) {
+      // This is the inverse of the calculation performed in
+      // AnimationInfo::StartPendingAnimations to calculate the start time of
+      // play-pending animations.
+      const TimeStamp readyTime =
+        animation.originTime() +
+        animation.startTime().get_TimeDuration() +
+        animation.holdTime().MultDouble(1.0 / animation.playbackRate());
+      hasFutureReadyTime = readyTime > aPreviousFrameTime;
+    }
+    // Use the previous vsync time to make main thread animations and compositor
+    // more closely aligned.
+    //
+    // On the first frame where we have animations the previous timestamp will
+    // not be set so we simply use the current timestamp.  As a result we will
+    // end up painting the first frame twice.  That doesn't appear to be
+    // noticeable, however.
+    //
+    // Likewise, if the animation is play-pending, it may have a ready time that
+    // is *after* |aPreviousFrameTime| (but *before* |aCurrentFrameTime|).
+    // To avoid flicker we need to use |aCurrentFrameTime| to avoid temporarily
+    // jumping backwards into the range prior to when the animation starts.
+    const TimeStamp& timeStamp =
+      aPreviousFrameTime.IsNull() || hasFutureReadyTime
+      ? aCurrentFrameTime
+      : aPreviousFrameTime;
 
     // If the animation is not currently playing, e.g. paused or
     // finished, then use the hold time to stay at the same position.
     TimeDuration elapsedDuration =
       animation.isNotPlaying() ||
       animation.startTime().type() != MaybeTimeDuration::TTimeDuration
       ? animation.holdTime()
       : (timeStamp - animation.originTime() -
@@ -600,22 +633,24 @@ AnimationHelper::SampleAnimations(Compos
       continue;
     }
 
     RefPtr<RawServoAnimationValue> animationValue;
     InfallibleTArray<AnimData> animationData;
     AnimationHelper::SetAnimations(*animations,
                                    animationData,
                                    animationValue);
+    AnimatedValue* previousValue = aStorage->GetAnimatedValue(iter.Key());
     AnimationHelper::SampleResult sampleResult =
       AnimationHelper::SampleAnimationForEachNode(aPreviousFrameTime,
                                                   aCurrentFrameTime,
                                                   *animations,
                                                   animationData,
-                                                  animationValue);
+                                                  animationValue,
+                                                  previousValue);
 
     if (sampleResult != AnimationHelper::SampleResult::Sampled) {
       continue;
     }
 
     // Store the AnimatedValue
     Animation& animation = animations->LastElement();
     switch (animation.property()) {
--- a/gfx/layers/AnimationHelper.h
+++ b/gfx/layers/AnimationHelper.h
@@ -211,30 +211,33 @@ public:
     Sampled
   };
 
   /**
    * Sample animations based on a given time stamp for a element(layer) with
    * its animation data.
    * Generally |aPreviousFrameTimeStamp| is used for the sampling if it's
    * supplied to make the animation more in sync with other animations on the
-   * main-thread.
+   * main-thread.  But in the case where the animation just started at the time
+   * when the animation was sent to the compositor, |aCurrentTime| is used for
+   * the sampling instead to avoid flickering the animation.
    *
    * 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 SampleResult
   SampleAnimationForEachNode(TimeStamp aPreviousFrameTime,
                              TimeStamp aCurrentFrameTime,
                              AnimationArray& aAnimations,
                              InfallibleTArray<AnimData>& aAnimationData,
-                             RefPtr<RawServoAnimationValue>& aAnimationValue);
+                             RefPtr<RawServoAnimationValue>& aAnimationValue,
+                             const AnimatedValue* aPreviousValue);
   /**
    * 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
@@ -682,24 +682,27 @@ SampleAnimations(Layer* aLayer,
       aLayer,
       [&] (Layer* layer)
       {
         AnimationArray& animations = layer->GetAnimations();
         if (animations.IsEmpty()) {
           return;
         }
         isAnimating = true;
+        AnimatedValue* previousValue =
+          aStorage->GetAnimatedValue(layer->GetCompositorAnimationsId());
         RefPtr<RawServoAnimationValue> animationValue =
           layer->GetBaseAnimationStyle();
         AnimationHelper::SampleResult sampleResult =
           AnimationHelper::SampleAnimationForEachNode(aPreviousFrameTime,
                                                       aCurrentFrameTime,
                                                       animations,
                                                       layer->GetAnimationData(),
-                                                      animationValue);
+                                                      animationValue,
+                                                      previousValue);
         switch (sampleResult) {
           case AnimationHelper::SampleResult::Sampled: {
             Animation& animation = animations.LastElement();
             ApplyAnimatedValue(layer,
                                aStorage,
                                animation.property(),
                                animation.data(),
                                animationValue);
@@ -717,29 +720,28 @@ SampleAnimations(Layer* aLayer,
                 MOZ_ASSERT(FuzzyEqualsMultiplicative(
                   Servo_AnimationValue_GetOpacity(animationValue),
                   *(aStorage->GetAnimationOpacity(layer->GetCompositorAnimationsId()))));
                 break;
               case eCSSProperty_transform: {
                 MOZ_ASSERT(
                   layer->AsHostLayer()->GetShadowTransformSetByAnimation());
 
-                AnimatedValue* transform =
-                  aStorage->GetAnimatedValue(layer->GetCompositorAnimationsId());
+                MOZ_ASSERT(previousValue);
 
                 const TransformData& transformData =
                   animations[0].data().get_TransformData();
                 Matrix4x4 frameTransform =
                   ServoAnimationValueToMatrix4x4(animationValue, transformData);
                 Matrix4x4 transformInDevice =
                   FrameTransformToTransformInDevice(frameTransform,
                                                     layer,
                                                     transformData);
                 MOZ_ASSERT(
-                  transform->mTransform.mTransformInDevSpace.FuzzyEqualsMultiplicative(
+                  previousValue->mTransform.mTransformInDevSpace.FuzzyEqualsMultiplicative(
                   transformInDevice));
                 break;
               }
               default:
                 MOZ_ASSERT_UNREACHABLE("Unsupported properties");
                 break;
             }
 #endif