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. Once this situation happens, we did skip composing the
initial animation value on the compositor because we consider the animation
hasn't started at the previous frame time stamp so that we failed to do the
initial paint for the animation. To prevent the failure, we do explicitly use
the current frame time stamp for the initial painting.
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,43 @@ 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");
+ // We consider the animation just started if all following conditions are
+ // true:
+ // * We have no previous animation value for this animation (it means that
+ // this is the first frame for the animation since the animation was
+ // sent to the compositor)
+ // * The animation has a resolved start time (i.e. not in delay phase)
+ // * The start time of the animation at the previous timestamp is behind
+ // of the current time of the animation
+ const bool wasPlayPending =
+ !aPreviousValue &&
+ animation.startTime().type() != MaybeTimeDuration::Tnull_t &&
+ !aPreviousFrameTime.IsNull() &&
+ (aPreviousFrameTime - animation.originTime() -
+ animation.startTime().get_TimeDuration()) < animation.holdTime();
// 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;
+ const TimeStamp& timeStamp =
+ // If the animation was play-pending state when the animation was sent to
+ // the compositor, we should use the current frame time stamp to avoid
+ // using the past timestamp (aPreviousFrameTime) for the animation that
+ // has just started.
+ aPreviousFrameTime.IsNull() || wasPlayPending
+ ? 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 +620,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