1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "AnimationHelper.h"
8 #include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction
9 #include "mozilla/dom/AnimationEffectBinding.h" // for dom::FillMode
10 #include "mozilla/dom/KeyframeEffectBinding.h" // for dom::IterationComposite
11 #include "mozilla/dom/KeyframeEffect.h" // for dom::KeyFrameEffectReadOnly
12 #include "mozilla/dom/Nullable.h" // for dom::Nullable
13 #include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder
14 #include "mozilla/layers/LayerAnimationUtils.h" // for TimingFunctionToComputedTimingFunction
15 #include "mozilla/LayerAnimationInfo.h" // for GetCSSPropertiesFor()
16 #include "mozilla/ServoBindings.h" // for Servo_ComposeAnimationSegment, etc
17 #include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc
18 #include "nsDeviceContext.h" // for AppUnitsPerCSSPixel
19 #include "nsDisplayList.h" // for nsDisplayTransform, etc
24 void CompositorAnimationStorage::Clear() {
25 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
27 mAnimatedValues
.Clear();
29 mAnimationRenderRoots
.Clear();
32 void CompositorAnimationStorage::ClearById(const uint64_t& aId
) {
33 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
35 mAnimatedValues
.Remove(aId
);
36 mAnimations
.Remove(aId
);
37 mAnimationRenderRoots
.Remove(aId
);
40 AnimatedValue
* CompositorAnimationStorage::GetAnimatedValue(
41 const uint64_t& aId
) const {
42 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
43 return mAnimatedValues
.Get(aId
);
46 OMTAValue
CompositorAnimationStorage::GetOMTAValue(const uint64_t& aId
) const {
47 OMTAValue omtaValue
= mozilla::null_t();
48 auto animatedValue
= GetAnimatedValue(aId
);
53 animatedValue
->Value().match(
54 [&](const AnimationTransform
& aTransform
) {
55 gfx::Matrix4x4 transform
= aTransform
.mFrameTransform
;
56 const TransformData
& data
= aTransform
.mData
;
57 float scale
= data
.appUnitsPerDevPixel();
58 gfx::Point3D transformOrigin
= data
.transformOrigin();
60 // Undo the rebasing applied by
61 // nsDisplayTransform::GetResultingTransformMatrixInternal
62 transform
.ChangeBasis(-transformOrigin
);
64 // Convert to CSS pixels (this undoes the operations performed by
65 // nsStyleTransformMatrix::ProcessTranslatePart which is called from
66 // nsDisplayTransform::GetResultingTransformMatrix)
67 double devPerCss
= double(scale
) / double(AppUnitsPerCSSPixel());
68 transform
._41
*= devPerCss
;
69 transform
._42
*= devPerCss
;
70 transform
._43
*= devPerCss
;
71 omtaValue
= transform
;
73 [&](const float& aOpacity
) { omtaValue
= aOpacity
; },
74 [&](const nscolor
& aColor
) { omtaValue
= aColor
; });
78 void CompositorAnimationStorage::SetAnimatedValue(
79 uint64_t aId
, gfx::Matrix4x4
&& aTransformInDevSpace
,
80 gfx::Matrix4x4
&& aFrameTransform
, const TransformData
& aData
) {
81 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
82 auto count
= mAnimatedValues
.Count();
83 AnimatedValue
* value
= mAnimatedValues
.LookupOrAdd(
84 aId
, std::move(aTransformInDevSpace
), std::move(aFrameTransform
), aData
);
85 if (count
== mAnimatedValues
.Count()) {
86 MOZ_ASSERT(value
->Is
<AnimationTransform
>());
87 *value
= AnimatedValue(std::move(aTransformInDevSpace
),
88 std::move(aFrameTransform
), aData
);
92 void CompositorAnimationStorage::SetAnimatedValue(
93 uint64_t aId
, gfx::Matrix4x4
&& aTransformInDevSpace
) {
94 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
95 const TransformData dontCare
= {};
96 SetAnimatedValue(aId
, std::move(aTransformInDevSpace
), gfx::Matrix4x4(),
100 void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId
,
102 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
103 auto count
= mAnimatedValues
.Count();
104 AnimatedValue
* value
= mAnimatedValues
.LookupOrAdd(aId
, aColor
);
105 if (count
== mAnimatedValues
.Count()) {
106 MOZ_ASSERT(value
->Is
<nscolor
>());
107 *value
= AnimatedValue(aColor
);
111 void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId
,
112 const float& aOpacity
) {
113 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
114 auto count
= mAnimatedValues
.Count();
115 AnimatedValue
* value
= mAnimatedValues
.LookupOrAdd(aId
, aOpacity
);
116 if (count
== mAnimatedValues
.Count()) {
117 MOZ_ASSERT(value
->Is
<float>());
118 *value
= AnimatedValue(aOpacity
);
122 nsTArray
<PropertyAnimationGroup
>* CompositorAnimationStorage::GetAnimations(
123 const uint64_t& aId
) const {
124 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
125 return mAnimations
.Get(aId
);
128 void CompositorAnimationStorage::SetAnimations(uint64_t aId
,
129 const AnimationArray
& aValue
,
130 wr::RenderRoot aRenderRoot
) {
131 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
132 mAnimations
.Put(aId
, new nsTArray
<PropertyAnimationGroup
>(
133 AnimationHelper::ExtractAnimations(aValue
)));
134 mAnimationRenderRoots
.Put(aId
, aRenderRoot
);
137 enum class CanSkipCompose
{
141 static AnimationHelper::SampleResult
SampleAnimationForProperty(
142 TimeStamp aPreviousFrameTime
, TimeStamp aCurrentFrameTime
,
143 const AnimatedValue
* aPreviousValue
, CanSkipCompose aCanSkipCompose
,
144 nsTArray
<PropertyAnimation
>& aPropertyAnimations
,
145 RefPtr
<RawServoAnimationValue
>& aAnimationValue
) {
146 MOZ_ASSERT(!aPropertyAnimations
.IsEmpty(), "Should have animations");
147 bool hasInEffectAnimations
= false;
149 // In cases where this function returns a SampleResult::Skipped, we actually
150 // do populate aAnimationValue in debug mode, so that we can MOZ_ASSERT at the
151 // call site that the value that would have been computed matches the stored
152 // value that we end up using. This flag is used to ensure we populate
153 // aAnimationValue in this scenario.
154 bool shouldBeSkipped
= false;
156 // Process in order, since later animations override earlier ones.
157 for (PropertyAnimation
& animation
: aPropertyAnimations
) {
159 (!animation
.mOriginTime
.IsNull() && animation
.mStartTime
.isSome()) ||
160 animation
.mIsNotPlaying
,
161 "If we are playing, we should have an origin time and a start time");
163 // Determine if the animation was play-pending and used a ready time later
164 // than the previous frame time.
166 // To determine this, _all_ of the following conditions need to hold:
168 // * There was no previous animation value (i.e. this is the first frame for
169 // the animation since it was sent to the compositor), and
170 // * The animation is playing, and
171 // * There is a previous frame time, and
172 // * The ready time of the animation is ahead of the previous frame time.
174 bool hasFutureReadyTime
= false;
175 if (!aPreviousValue
&& !animation
.mIsNotPlaying
&&
176 !aPreviousFrameTime
.IsNull()) {
177 // This is the inverse of the calculation performed in
178 // AnimationInfo::StartPendingAnimations to calculate the start time of
179 // play-pending animations.
180 // Note that we have to calculate (TimeStamp + TimeDuration) last to avoid
181 // underflow in the middle of the calulation.
182 const TimeStamp readyTime
=
183 animation
.mOriginTime
+
184 (animation
.mStartTime
.ref() +
185 animation
.mHoldTime
.MultDouble(1.0 / animation
.mPlaybackRate
));
187 !readyTime
.IsNull() && readyTime
> aPreviousFrameTime
;
189 // Use the previous vsync time to make main thread animations and compositor
190 // more closely aligned.
192 // On the first frame where we have animations the previous timestamp will
193 // not be set so we simply use the current timestamp. As a result we will
194 // end up painting the first frame twice. That doesn't appear to be
195 // noticeable, however.
197 // Likewise, if the animation is play-pending, it may have a ready time that
198 // is *after* |aPreviousFrameTime| (but *before* |aCurrentFrameTime|).
199 // To avoid flicker we need to use |aCurrentFrameTime| to avoid temporarily
200 // jumping backwards into the range prior to when the animation starts.
201 const TimeStamp
& timeStamp
=
202 aPreviousFrameTime
.IsNull() || hasFutureReadyTime
? aCurrentFrameTime
203 : aPreviousFrameTime
;
205 // If the animation is not currently playing, e.g. paused or
206 // finished, then use the hold time to stay at the same position.
207 TimeDuration elapsedDuration
=
208 animation
.mIsNotPlaying
|| animation
.mStartTime
.isNothing()
209 ? animation
.mHoldTime
210 : (timeStamp
- animation
.mOriginTime
- animation
.mStartTime
.ref())
211 .MultDouble(animation
.mPlaybackRate
);
213 ComputedTiming computedTiming
= dom::AnimationEffect::GetComputedTimingAt(
214 dom::Nullable
<TimeDuration
>(elapsedDuration
), animation
.mTiming
,
215 animation
.mPlaybackRate
);
217 if (computedTiming
.mProgress
.IsNull()) {
221 dom::IterationCompositeOperation iterCompositeOperation
=
222 animation
.mIterationComposite
;
224 // Skip calculation if the progress hasn't changed since the last
226 // Note that we don't skip calculate this animation if there is another
227 // animation since the other animation might be 'accumulate' or 'add', or
228 // might have a missing keyframe (i.e. this animation value will be used in
229 // the missing keyframe).
230 // FIXME Bug 1455476: We should do this optimizations for the case where
231 // the layer has multiple animations and multiple properties.
232 if (aCanSkipCompose
== CanSkipCompose::IfPossible
&&
233 !dom::KeyframeEffect::HasComputedTimingChanged(
234 computedTiming
, iterCompositeOperation
,
235 animation
.mProgressOnLastCompose
,
236 animation
.mCurrentIterationOnLastCompose
)) {
238 shouldBeSkipped
= true;
240 return AnimationHelper::SampleResult::Skipped
;
244 uint32_t segmentIndex
= 0;
245 size_t segmentSize
= animation
.mSegments
.Length();
246 PropertyAnimation::SegmentData
* segment
= animation
.mSegments
.Elements();
247 while (segment
->mEndPortion
< computedTiming
.mProgress
.Value() &&
248 segmentIndex
< segmentSize
- 1) {
253 double positionInSegment
=
254 (computedTiming
.mProgress
.Value() - segment
->mStartPortion
) /
255 (segment
->mEndPortion
- segment
->mStartPortion
);
257 double portion
= ComputedTimingFunction::GetPortion(
258 segment
->mFunction
, positionInSegment
, computedTiming
.mBeforeFlag
);
260 // Like above optimization, skip calculation if the target segment isn't
261 // changed and if the portion in the segment isn't changed.
262 // This optimization is needed for CSS animations/transitions with step
263 // timing functions (e.g. the throbber animation on tabs or frame based
265 // FIXME Bug 1455476: Like the above optimization, we should apply this
266 // optimizations for multiple animation cases and multiple properties as
268 if (aCanSkipCompose
== CanSkipCompose::IfPossible
&&
269 animation
.mSegmentIndexOnLastCompose
== segmentIndex
&&
270 !animation
.mPortionInSegmentOnLastCompose
.IsNull() &&
271 animation
.mPortionInSegmentOnLastCompose
.Value() == portion
) {
273 shouldBeSkipped
= true;
275 return AnimationHelper::SampleResult::Skipped
;
279 AnimationPropertySegment animSegment
;
280 animSegment
.mFromKey
= 0.0;
281 animSegment
.mToKey
= 1.0;
282 animSegment
.mFromValue
= AnimationValue(segment
->mStartValue
);
283 animSegment
.mToValue
= AnimationValue(segment
->mEndValue
);
284 animSegment
.mFromComposite
= segment
->mStartComposite
;
285 animSegment
.mToComposite
= segment
->mEndComposite
;
287 // interpolate the property
289 Servo_ComposeAnimationSegment(
290 &animSegment
, aAnimationValue
,
291 animation
.mSegments
.LastElement().mEndValue
, iterCompositeOperation
,
292 portion
, computedTiming
.mCurrentIteration
)
296 if (shouldBeSkipped
) {
297 return AnimationHelper::SampleResult::Skipped
;
301 hasInEffectAnimations
= true;
302 animation
.mProgressOnLastCompose
= computedTiming
.mProgress
;
303 animation
.mCurrentIterationOnLastCompose
= computedTiming
.mCurrentIteration
;
304 animation
.mSegmentIndexOnLastCompose
= segmentIndex
;
305 animation
.mPortionInSegmentOnLastCompose
.SetValue(portion
);
308 return hasInEffectAnimations
? AnimationHelper::SampleResult::Sampled
309 : AnimationHelper::SampleResult::None
;
312 AnimationHelper::SampleResult
AnimationHelper::SampleAnimationForEachNode(
313 TimeStamp aPreviousFrameTime
, TimeStamp aCurrentFrameTime
,
314 const AnimatedValue
* aPreviousValue
,
315 nsTArray
<PropertyAnimationGroup
>& aPropertyAnimationGroups
,
316 nsTArray
<RefPtr
<RawServoAnimationValue
>>& aAnimationValues
/* out */) {
317 MOZ_ASSERT(!aPropertyAnimationGroups
.IsEmpty(),
318 "Should be called with animation data");
319 MOZ_ASSERT(aAnimationValues
.IsEmpty(),
320 "Should be called with empty aAnimationValues");
322 nsTArray
<RefPtr
<RawServoAnimationValue
>> nonAnimatingValues
;
323 for (PropertyAnimationGroup
& group
: aPropertyAnimationGroups
) {
324 // Initialize animation value with base style.
325 RefPtr
<RawServoAnimationValue
> currValue
= group
.mBaseStyle
;
327 CanSkipCompose canSkipCompose
= aPropertyAnimationGroups
.Length() == 1 &&
328 group
.mAnimations
.Length() == 1
329 ? CanSkipCompose::IfPossible
330 : CanSkipCompose::No
;
333 !group
.mAnimations
.IsEmpty() ||
334 nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(
336 "Only transform-like properties can have empty PropertyAnimation list");
338 // For properties which are not animating (i.e. their values are always the
339 // same), we store them in a different array, and then merge them into the
340 // final result (a.k.a. aAnimationValues) because we shouldn't take them
341 // into account for SampleResult. (In other words, these properties
342 // shouldn't affect the optimization.)
343 if (group
.mAnimations
.IsEmpty()) {
344 nonAnimatingValues
.AppendElement(std::move(currValue
));
348 SampleResult result
= SampleAnimationForProperty(
349 aPreviousFrameTime
, aCurrentFrameTime
, aPreviousValue
, canSkipCompose
,
350 group
.mAnimations
, currValue
);
352 // FIXME: Bug 1455476: Do optimization for multiple properties. For now,
353 // the result is skipped only if the property count == 1.
354 if (result
== SampleResult::Skipped
) {
356 aAnimationValues
.AppendElement(std::move(currValue
));
358 return SampleResult::Skipped
;
361 if (result
!= SampleResult::Sampled
) {
365 // Insert the interpolation result into the output array.
366 MOZ_ASSERT(currValue
);
367 aAnimationValues
.AppendElement(std::move(currValue
));
371 // Sanity check that all of animation data are the same.
372 const Maybe
<TransformData
>& lastData
=
373 aPropertyAnimationGroups
.LastElement().mAnimationData
;
374 for (const PropertyAnimationGroup
& group
: aPropertyAnimationGroups
) {
375 const Maybe
<TransformData
>& data
= group
.mAnimationData
;
376 MOZ_ASSERT(data
.isSome() == lastData
.isSome(),
377 "The type of AnimationData should be the same");
378 if (data
.isNothing()) {
382 MOZ_ASSERT(data
.isSome());
383 const TransformData
& transformData
= data
.ref();
384 const TransformData
& lastTransformData
= lastData
.ref();
385 MOZ_ASSERT(transformData
.origin() == lastTransformData
.origin() &&
386 transformData
.transformOrigin() ==
387 lastTransformData
.transformOrigin() &&
388 transformData
.bounds() == lastTransformData
.bounds() &&
389 transformData
.appUnitsPerDevPixel() ==
390 lastTransformData
.appUnitsPerDevPixel(),
391 "All of members of TransformData should be the same");
396 aAnimationValues
.IsEmpty() ? SampleResult::None
: SampleResult::Sampled
;
397 if (rv
== SampleResult::Sampled
) {
398 aAnimationValues
.AppendElements(nonAnimatingValues
);
403 static dom::FillMode
GetAdjustedFillMode(const Animation
& aAnimation
) {
404 // Adjust fill mode so that if the main thread is delayed in clearing
405 // this animation we don't introduce flicker by jumping back to the old
407 auto fillMode
= static_cast<dom::FillMode
>(aAnimation
.fillMode());
408 float playbackRate
= aAnimation
.playbackRate();
410 case dom::FillMode::None
:
411 if (playbackRate
> 0) {
412 fillMode
= dom::FillMode::Forwards
;
413 } else if (playbackRate
< 0) {
414 fillMode
= dom::FillMode::Backwards
;
417 case dom::FillMode::Backwards
:
418 if (playbackRate
> 0) {
419 fillMode
= dom::FillMode::Both
;
422 case dom::FillMode::Forwards
:
423 if (playbackRate
< 0) {
424 fillMode
= dom::FillMode::Both
;
433 nsTArray
<PropertyAnimationGroup
> AnimationHelper::ExtractAnimations(
434 const AnimationArray
& aAnimations
) {
435 nsTArray
<PropertyAnimationGroup
> propertyAnimationGroupArray
;
437 nsCSSPropertyID prevID
= eCSSProperty_UNKNOWN
;
438 PropertyAnimationGroup
* currData
= nullptr;
439 DebugOnly
<const layers::Animatable
*> currBaseStyle
= nullptr;
441 for (const Animation
& animation
: aAnimations
) {
442 // Animations with same property are grouped together, so we can just
443 // check if the current property is the same as the previous one for
444 // knowing this is a new group.
445 if (prevID
!= animation
.property()) {
446 // Got a different group, we should create a different array.
447 currData
= propertyAnimationGroupArray
.AppendElement();
448 currData
->mProperty
= animation
.property();
449 currData
->mAnimationData
= animation
.data();
450 prevID
= animation
.property();
452 // Reset the debug pointer.
453 currBaseStyle
= nullptr;
456 MOZ_ASSERT(currData
);
457 if (animation
.baseStyle().type() != Animatable::Tnull_t
) {
458 MOZ_ASSERT(!currBaseStyle
|| *currBaseStyle
== animation
.baseStyle(),
459 "Should be the same base style");
461 currData
->mBaseStyle
= AnimationValue::FromAnimatable(
462 animation
.property(), animation
.baseStyle());
463 currBaseStyle
= &animation
.baseStyle();
466 // If this layers::Animation sets isNotAnimating to true, it only has
467 // base style and doesn't have any animation information, so we can skip
468 // the rest steps. (And so its PropertyAnimationGroup::mAnimation will be
470 if (animation
.isNotAnimating()) {
471 MOZ_ASSERT(nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(
472 animation
.property()),
473 "Only transform-like properties could set this true");
477 PropertyAnimation
* propertyAnimation
=
478 currData
->mAnimations
.AppendElement();
480 propertyAnimation
->mOriginTime
= animation
.originTime();
481 propertyAnimation
->mStartTime
= animation
.startTime();
482 propertyAnimation
->mHoldTime
= animation
.holdTime();
483 propertyAnimation
->mPlaybackRate
= animation
.playbackRate();
484 propertyAnimation
->mIterationComposite
=
485 static_cast<dom::IterationCompositeOperation
>(
486 animation
.iterationComposite());
487 propertyAnimation
->mIsNotPlaying
= animation
.isNotPlaying();
488 propertyAnimation
->mTiming
=
489 TimingParams
{animation
.duration(),
491 animation
.endDelay(),
492 animation
.iterations(),
493 animation
.iterationStart(),
494 static_cast<dom::PlaybackDirection
>(animation
.direction()),
495 GetAdjustedFillMode(animation
),
496 AnimationUtils::TimingFunctionToComputedTimingFunction(
497 animation
.easingFunction())};
499 nsTArray
<PropertyAnimation::SegmentData
>& segmentData
=
500 propertyAnimation
->mSegments
;
501 for (const AnimationSegment
& segment
: animation
.segments()) {
502 segmentData
.AppendElement(PropertyAnimation::SegmentData
{
503 AnimationValue::FromAnimatable(animation
.property(),
504 segment
.startState()),
505 AnimationValue::FromAnimatable(animation
.property(),
507 AnimationUtils::TimingFunctionToComputedTimingFunction(
509 segment
.startPortion(), segment
.endPortion(),
510 static_cast<dom::CompositeOperation
>(segment
.startComposite()),
511 static_cast<dom::CompositeOperation
>(segment
.endComposite())});
516 // Sanity check that the grouped animation data is correct by looking at the
518 if (!propertyAnimationGroupArray
.IsEmpty()) {
519 nsCSSPropertyIDSet seenProperties
;
520 for (const auto& group
: propertyAnimationGroupArray
) {
521 nsCSSPropertyID id
= group
.mProperty
;
523 MOZ_ASSERT(!seenProperties
.HasProperty(id
), "Should be a new property");
524 seenProperties
.AddProperty(id
);
528 seenProperties
.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
529 DisplayItemType::TYPE_TRANSFORM
)) ||
530 seenProperties
.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
531 DisplayItemType::TYPE_OPACITY
)) ||
532 seenProperties
.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
533 DisplayItemType::TYPE_BACKGROUND_COLOR
)),
534 "The property set of output should be the subset of transform-like "
535 "properties, opacity, or background_color.");
539 return propertyAnimationGroupArray
;
542 uint64_t AnimationHelper::GetNextCompositorAnimationsId() {
543 static uint32_t sNextId
= 0;
546 uint32_t procId
= static_cast<uint32_t>(base::GetCurrentProcId());
547 uint64_t nextId
= procId
;
548 nextId
= nextId
<< 32 | sNextId
;
552 bool AnimationHelper::SampleAnimations(CompositorAnimationStorage
* aStorage
,
553 TimeStamp aPreviousFrameTime
,
554 TimeStamp aCurrentFrameTime
) {
555 MOZ_ASSERT(aStorage
);
556 bool isAnimating
= false;
558 // Do nothing if there are no compositor animations
559 if (!aStorage
->AnimationsCount()) {
563 // Sample the animations in CompositorAnimationStorage
564 for (auto iter
= aStorage
->ConstAnimationsTableIter(); !iter
.Done();
566 auto& propertyAnimationGroups
= *iter
.UserData();
567 if (propertyAnimationGroups
.IsEmpty()) {
572 nsTArray
<RefPtr
<RawServoAnimationValue
>> animationValues
;
573 AnimatedValue
* previousValue
= aStorage
->GetAnimatedValue(iter
.Key());
574 AnimationHelper::SampleResult sampleResult
=
575 AnimationHelper::SampleAnimationForEachNode(
576 aPreviousFrameTime
, aCurrentFrameTime
, previousValue
,
577 propertyAnimationGroups
, animationValues
);
579 if (sampleResult
!= AnimationHelper::SampleResult::Sampled
) {
583 const PropertyAnimationGroup
& lastPropertyAnimationGroup
=
584 propertyAnimationGroups
.LastElement();
586 // Store the AnimatedValue
587 switch (lastPropertyAnimationGroup
.mProperty
) {
588 case eCSSProperty_opacity
: {
589 MOZ_ASSERT(animationValues
.Length() == 1);
590 aStorage
->SetAnimatedValue(
591 iter
.Key(), Servo_AnimationValue_GetOpacity(animationValues
[0]));
594 case eCSSProperty_rotate
:
595 case eCSSProperty_scale
:
596 case eCSSProperty_translate
:
597 case eCSSProperty_transform
: {
598 const TransformData
& transformData
=
599 lastPropertyAnimationGroup
.mAnimationData
.ref();
601 gfx::Matrix4x4 transform
=
602 ServoAnimationValueToMatrix4x4(animationValues
, transformData
);
603 gfx::Matrix4x4 frameTransform
= transform
;
604 // If the parent has perspective transform, then the offset into
605 // reference frame coordinates is already on this transform. If not,
606 // then we need to ask for it to be added here.
607 if (!transformData
.hasPerspectiveParent()) {
608 nsLayoutUtils::PostTranslate(transform
, transformData
.origin(),
609 transformData
.appUnitsPerDevPixel(),
613 transform
.PostScale(transformData
.inheritedXScale(),
614 transformData
.inheritedYScale(), 1);
616 aStorage
->SetAnimatedValue(iter
.Key(), std::move(transform
),
617 std::move(frameTransform
), transformData
);
621 MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
628 gfx::Matrix4x4
AnimationHelper::ServoAnimationValueToMatrix4x4(
629 const nsTArray
<RefPtr
<RawServoAnimationValue
>>& aValues
,
630 const TransformData
& aTransformData
) {
631 // This is a bit silly just to avoid the transform list copy from the
632 // animation transform list.
633 auto noneTranslate
= StyleTranslate::None();
634 auto noneRotate
= StyleRotate::None();
635 auto noneScale
= StyleScale::None();
636 const StyleTransform noneTransform
;
638 const StyleTranslate
* translate
= nullptr;
639 const StyleRotate
* rotate
= nullptr;
640 const StyleScale
* scale
= nullptr;
641 const StyleTransform
* transform
= nullptr;
643 // TODO: Bug 1429305: Support compositor animations for motion-path.
644 for (const auto& value
: aValues
) {
646 nsCSSPropertyID id
= Servo_AnimationValue_GetPropertyId(value
);
648 case eCSSProperty_transform
:
649 MOZ_ASSERT(!transform
);
650 transform
= Servo_AnimationValue_GetTransform(value
);
652 case eCSSProperty_translate
:
653 MOZ_ASSERT(!translate
);
654 translate
= Servo_AnimationValue_GetTranslate(value
);
656 case eCSSProperty_rotate
:
658 rotate
= Servo_AnimationValue_GetRotate(value
);
660 case eCSSProperty_scale
:
662 scale
= Servo_AnimationValue_GetScale(value
);
665 MOZ_ASSERT_UNREACHABLE("Unsupported transform-like property");
668 // We expect all our transform data to arrive in device pixels
669 gfx::Point3D transformOrigin
= aTransformData
.transformOrigin();
670 nsDisplayTransform::FrameTransformProperties
props(
671 translate
? *translate
: noneTranslate
, rotate
? *rotate
: noneRotate
,
672 scale
? *scale
: noneScale
, transform
? *transform
: noneTransform
,
675 return nsDisplayTransform::GetResultingTransformMatrix(
676 props
, aTransformData
.origin(), aTransformData
.appUnitsPerDevPixel(), 0,
677 &aTransformData
.bounds());
680 } // namespace layers
681 } // namespace mozilla