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
--- 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