Bug 1568876 - Make copyRule in the ChangesView fission compatible. r=rcaliman
[gecko.git] / gfx / layers / AnimationHelper.cpp
bloba1ea59c15ea4a28e09d6a5c7d839ad1f9ab8e82a
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
21 namespace mozilla {
22 namespace layers {
24 void CompositorAnimationStorage::Clear() {
25 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
27 mAnimatedValues.Clear();
28 mAnimations.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);
49 if (!animatedValue) {
50 return omtaValue;
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; });
75 return omtaValue;
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(),
97 dontCare);
100 void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
101 nscolor aColor) {
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 {
138 IfPossible,
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;
148 #ifdef DEBUG
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;
155 #endif
156 // Process in order, since later animations override earlier ones.
157 for (PropertyAnimation& animation : aPropertyAnimations) {
158 MOZ_ASSERT(
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));
186 hasFutureReadyTime =
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()) {
218 continue;
221 dom::IterationCompositeOperation iterCompositeOperation =
222 animation.mIterationComposite;
224 // Skip calculation if the progress hasn't changed since the last
225 // calculation.
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)) {
237 #ifdef DEBUG
238 shouldBeSkipped = true;
239 #else
240 return AnimationHelper::SampleResult::Skipped;
241 #endif
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) {
249 ++segment;
250 ++segmentIndex;
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
264 // animations).
265 // FIXME Bug 1455476: Like the above optimization, we should apply this
266 // optimizations for multiple animation cases and multiple properties as
267 // well.
268 if (aCanSkipCompose == CanSkipCompose::IfPossible &&
269 animation.mSegmentIndexOnLastCompose == segmentIndex &&
270 !animation.mPortionInSegmentOnLastCompose.IsNull() &&
271 animation.mPortionInSegmentOnLastCompose.Value() == portion) {
272 #ifdef DEBUG
273 shouldBeSkipped = true;
274 #else
275 return AnimationHelper::SampleResult::Skipped;
276 #endif
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
288 aAnimationValue =
289 Servo_ComposeAnimationSegment(
290 &animSegment, aAnimationValue,
291 animation.mSegments.LastElement().mEndValue, iterCompositeOperation,
292 portion, computedTiming.mCurrentIteration)
293 .Consume();
295 #ifdef DEBUG
296 if (shouldBeSkipped) {
297 return AnimationHelper::SampleResult::Skipped;
299 #endif
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;
332 MOZ_ASSERT(
333 !group.mAnimations.IsEmpty() ||
334 nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(
335 group.mProperty),
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));
345 continue;
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) {
355 #ifdef DEBUG
356 aAnimationValues.AppendElement(std::move(currValue));
357 #endif
358 return SampleResult::Skipped;
361 if (result != SampleResult::Sampled) {
362 continue;
365 // Insert the interpolation result into the output array.
366 MOZ_ASSERT(currValue);
367 aAnimationValues.AppendElement(std::move(currValue));
370 #ifdef DEBUG
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()) {
379 continue;
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");
393 #endif
395 SampleResult rv =
396 aAnimationValues.IsEmpty() ? SampleResult::None : SampleResult::Sampled;
397 if (rv == SampleResult::Sampled) {
398 aAnimationValues.AppendElements(nonAnimatingValues);
400 return rv;
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
406 // underlying value.
407 auto fillMode = static_cast<dom::FillMode>(aAnimation.fillMode());
408 float playbackRate = aAnimation.playbackRate();
409 switch (fillMode) {
410 case dom::FillMode::None:
411 if (playbackRate > 0) {
412 fillMode = dom::FillMode::Forwards;
413 } else if (playbackRate < 0) {
414 fillMode = dom::FillMode::Backwards;
416 break;
417 case dom::FillMode::Backwards:
418 if (playbackRate > 0) {
419 fillMode = dom::FillMode::Both;
421 break;
422 case dom::FillMode::Forwards:
423 if (playbackRate < 0) {
424 fillMode = dom::FillMode::Both;
426 break;
427 default:
428 break;
430 return fillMode;
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
469 // an empty array.)
470 if (animation.isNotAnimating()) {
471 MOZ_ASSERT(nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(
472 animation.property()),
473 "Only transform-like properties could set this true");
474 continue;
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(),
490 animation.delay(),
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(),
506 segment.endState()),
507 AnimationUtils::TimingFunctionToComputedTimingFunction(
508 segment.sampleFn()),
509 segment.startPortion(), segment.endPortion(),
510 static_cast<dom::CompositeOperation>(segment.startComposite()),
511 static_cast<dom::CompositeOperation>(segment.endComposite())});
515 #ifdef DEBUG
516 // Sanity check that the grouped animation data is correct by looking at the
517 // property set.
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);
527 MOZ_ASSERT(
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.");
537 #endif
539 return propertyAnimationGroupArray;
542 uint64_t AnimationHelper::GetNextCompositorAnimationsId() {
543 static uint32_t sNextId = 0;
544 ++sNextId;
546 uint32_t procId = static_cast<uint32_t>(base::GetCurrentProcId());
547 uint64_t nextId = procId;
548 nextId = nextId << 32 | sNextId;
549 return nextId;
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()) {
560 return isAnimating;
563 // Sample the animations in CompositorAnimationStorage
564 for (auto iter = aStorage->ConstAnimationsTableIter(); !iter.Done();
565 iter.Next()) {
566 auto& propertyAnimationGroups = *iter.UserData();
567 if (propertyAnimationGroups.IsEmpty()) {
568 continue;
571 isAnimating = true;
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) {
580 continue;
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]));
592 break;
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(),
610 true);
613 transform.PostScale(transformData.inheritedXScale(),
614 transformData.inheritedYScale(), 1);
616 aStorage->SetAnimatedValue(iter.Key(), std::move(transform),
617 std::move(frameTransform), transformData);
618 break;
620 default:
621 MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
625 return isAnimating;
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) {
645 MOZ_ASSERT(value);
646 nsCSSPropertyID id = Servo_AnimationValue_GetPropertyId(value);
647 switch (id) {
648 case eCSSProperty_transform:
649 MOZ_ASSERT(!transform);
650 transform = Servo_AnimationValue_GetTransform(value);
651 break;
652 case eCSSProperty_translate:
653 MOZ_ASSERT(!translate);
654 translate = Servo_AnimationValue_GetTranslate(value);
655 break;
656 case eCSSProperty_rotate:
657 MOZ_ASSERT(!rotate);
658 rotate = Servo_AnimationValue_GetRotate(value);
659 break;
660 case eCSSProperty_scale:
661 MOZ_ASSERT(!scale);
662 scale = Servo_AnimationValue_GetScale(value);
663 break;
664 default:
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,
673 transformOrigin);
675 return nsDisplayTransform::GetResultingTransformMatrix(
676 props, aTransformData.origin(), aTransformData.appUnitsPerDevPixel(), 0,
677 &aTransformData.bounds());
680 } // namespace layers
681 } // namespace mozilla