Source code

Revision control

Other Tools

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/. */
6
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/MotionPathUtils.h" // for ResolveMotionPath()
17
#include "mozilla/ServoBindings.h" // for Servo_ComposeAnimationSegment, etc
18
#include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc
19
#include "nsDeviceContext.h" // for AppUnitsPerCSSPixel
20
#include "nsDisplayList.h" // for nsDisplayTransform, etc
21
22
namespace mozilla {
23
namespace layers {
24
25
void CompositorAnimationStorage::Clear() {
26
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
27
28
mAnimatedValues.Clear();
29
mAnimations.Clear();
30
mAnimationRenderRoots.Clear();
31
}
32
33
void CompositorAnimationStorage::ClearById(const uint64_t& aId) {
34
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
35
36
mAnimatedValues.Remove(aId);
37
mAnimations.Remove(aId);
38
mAnimationRenderRoots.Remove(aId);
39
}
40
41
AnimatedValue* CompositorAnimationStorage::GetAnimatedValue(
42
const uint64_t& aId) const {
43
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
44
return mAnimatedValues.Get(aId);
45
}
46
47
OMTAValue CompositorAnimationStorage::GetOMTAValue(const uint64_t& aId) const {
48
OMTAValue omtaValue = mozilla::null_t();
49
auto animatedValue = GetAnimatedValue(aId);
50
if (!animatedValue) {
51
return omtaValue;
52
}
53
54
animatedValue->Value().match(
55
[&](const AnimationTransform& aTransform) {
56
gfx::Matrix4x4 transform = aTransform.mFrameTransform;
57
const TransformData& data = aTransform.mData;
58
float scale = data.appUnitsPerDevPixel();
59
gfx::Point3D transformOrigin = data.transformOrigin();
60
61
// Undo the rebasing applied by
62
// nsDisplayTransform::GetResultingTransformMatrixInternal
63
transform.ChangeBasis(-transformOrigin);
64
65
// Convert to CSS pixels (this undoes the operations performed by
66
// nsStyleTransformMatrix::ProcessTranslatePart which is called from
67
// nsDisplayTransform::GetResultingTransformMatrix)
68
double devPerCss = double(scale) / double(AppUnitsPerCSSPixel());
69
transform._41 *= devPerCss;
70
transform._42 *= devPerCss;
71
transform._43 *= devPerCss;
72
omtaValue = transform;
73
},
74
[&](const float& aOpacity) { omtaValue = aOpacity; },
75
[&](const nscolor& aColor) { omtaValue = aColor; });
76
return omtaValue;
77
}
78
79
void CompositorAnimationStorage::SetAnimatedValue(
80
uint64_t aId, gfx::Matrix4x4&& aTransformInDevSpace,
81
gfx::Matrix4x4&& aFrameTransform, const TransformData& aData) {
82
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
83
auto count = mAnimatedValues.Count();
84
AnimatedValue* value = mAnimatedValues.LookupOrAdd(
85
aId, std::move(aTransformInDevSpace), std::move(aFrameTransform), aData);
86
if (count == mAnimatedValues.Count()) {
87
MOZ_ASSERT(value->Is<AnimationTransform>());
88
*value = AnimatedValue(std::move(aTransformInDevSpace),
89
std::move(aFrameTransform), aData);
90
}
91
}
92
93
void CompositorAnimationStorage::SetAnimatedValue(
94
uint64_t aId, gfx::Matrix4x4&& aTransformInDevSpace) {
95
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
96
const TransformData dontCare = {};
97
SetAnimatedValue(aId, std::move(aTransformInDevSpace), gfx::Matrix4x4(),
98
dontCare);
99
}
100
101
void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
102
nscolor aColor) {
103
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
104
auto count = mAnimatedValues.Count();
105
AnimatedValue* value = mAnimatedValues.LookupOrAdd(aId, aColor);
106
if (count == mAnimatedValues.Count()) {
107
MOZ_ASSERT(value->Is<nscolor>());
108
*value = AnimatedValue(aColor);
109
}
110
}
111
112
void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
113
const float& aOpacity) {
114
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
115
auto count = mAnimatedValues.Count();
116
AnimatedValue* value = mAnimatedValues.LookupOrAdd(aId, aOpacity);
117
if (count == mAnimatedValues.Count()) {
118
MOZ_ASSERT(value->Is<float>());
119
*value = AnimatedValue(aOpacity);
120
}
121
}
122
123
void CompositorAnimationStorage::SetAnimations(uint64_t aId,
124
const AnimationArray& aValue,
125
wr::RenderRoot aRenderRoot) {
126
MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
127
mAnimations.Put(aId, AnimationHelper::ExtractAnimations(aValue));
128
mAnimationRenderRoots.Put(aId, aRenderRoot);
129
}
130
131
enum class CanSkipCompose {
132
IfPossible,
133
No,
134
};
135
static AnimationHelper::SampleResult SampleAnimationForProperty(
136
TimeStamp aPreviousFrameTime, TimeStamp aCurrentFrameTime,
137
const AnimatedValue* aPreviousValue, CanSkipCompose aCanSkipCompose,
138
nsTArray<PropertyAnimation>& aPropertyAnimations,
139
RefPtr<RawServoAnimationValue>& aAnimationValue) {
140
MOZ_ASSERT(!aPropertyAnimations.IsEmpty(), "Should have animations");
141
bool hasInEffectAnimations = false;
142
#ifdef DEBUG
143
// In cases where this function returns a SampleResult::Skipped, we actually
144
// do populate aAnimationValue in debug mode, so that we can MOZ_ASSERT at the
145
// call site that the value that would have been computed matches the stored
146
// value that we end up using. This flag is used to ensure we populate
147
// aAnimationValue in this scenario.
148
bool shouldBeSkipped = false;
149
#endif
150
// Process in order, since later animations override earlier ones.
151
for (PropertyAnimation& animation : aPropertyAnimations) {
152
MOZ_ASSERT(
153
(!animation.mOriginTime.IsNull() && animation.mStartTime.isSome()) ||
154
animation.mIsNotPlaying,
155
"If we are playing, we should have an origin time and a start time");
156
157
// Determine if the animation was play-pending and used a ready time later
158
// than the previous frame time.
159
//
160
// To determine this, _all_ of the following conditions need to hold:
161
//
162
// * There was no previous animation value (i.e. this is the first frame for
163
// the animation since it was sent to the compositor), and
164
// * The animation is playing, and
165
// * There is a previous frame time, and
166
// * The ready time of the animation is ahead of the previous frame time.
167
//
168
bool hasFutureReadyTime = false;
169
if (!aPreviousValue && !animation.mIsNotPlaying &&
170
!aPreviousFrameTime.IsNull()) {
171
// This is the inverse of the calculation performed in
172
// AnimationInfo::StartPendingAnimations to calculate the start time of
173
// play-pending animations.
174
// Note that we have to calculate (TimeStamp + TimeDuration) last to avoid
175
// underflow in the middle of the calulation.
176
const TimeStamp readyTime =
177
animation.mOriginTime +
178
(animation.mStartTime.ref() +
179
animation.mHoldTime.MultDouble(1.0 / animation.mPlaybackRate));
180
hasFutureReadyTime =
181
!readyTime.IsNull() && readyTime > aPreviousFrameTime;
182
}
183
// Use the previous vsync time to make main thread animations and compositor
184
// more closely aligned.
185
//
186
// On the first frame where we have animations the previous timestamp will
187
// not be set so we simply use the current timestamp. As a result we will
188
// end up painting the first frame twice. That doesn't appear to be
189
// noticeable, however.
190
//
191
// Likewise, if the animation is play-pending, it may have a ready time that
192
// is *after* |aPreviousFrameTime| (but *before* |aCurrentFrameTime|).
193
// To avoid flicker we need to use |aCurrentFrameTime| to avoid temporarily
194
// jumping backwards into the range prior to when the animation starts.
195
const TimeStamp& timeStamp =
196
aPreviousFrameTime.IsNull() || hasFutureReadyTime ? aCurrentFrameTime
197
: aPreviousFrameTime;
198
199
// If the animation is not currently playing, e.g. paused or
200
// finished, then use the hold time to stay at the same position.
201
TimeDuration elapsedDuration =
202
animation.mIsNotPlaying || animation.mStartTime.isNothing()
203
? animation.mHoldTime
204
: (timeStamp - animation.mOriginTime - animation.mStartTime.ref())
205
.MultDouble(animation.mPlaybackRate);
206
207
ComputedTiming computedTiming = dom::AnimationEffect::GetComputedTimingAt(
208
dom::Nullable<TimeDuration>(elapsedDuration), animation.mTiming,
209
animation.mPlaybackRate);
210
211
if (computedTiming.mProgress.IsNull()) {
212
continue;
213
}
214
215
dom::IterationCompositeOperation iterCompositeOperation =
216
animation.mIterationComposite;
217
218
// Skip calculation if the progress hasn't changed since the last
219
// calculation.
220
// Note that we don't skip calculate this animation if there is another
221
// animation since the other animation might be 'accumulate' or 'add', or
222
// might have a missing keyframe (i.e. this animation value will be used in
223
// the missing keyframe).
224
// FIXME Bug 1455476: We should do this optimizations for the case where
225
// the layer has multiple animations and multiple properties.
226
if (aCanSkipCompose == CanSkipCompose::IfPossible &&
227
!dom::KeyframeEffect::HasComputedTimingChanged(
228
computedTiming, iterCompositeOperation,
229
animation.mProgressOnLastCompose,
230
animation.mCurrentIterationOnLastCompose)) {
231
#ifdef DEBUG
232
shouldBeSkipped = true;
233
#else
234
return AnimationHelper::SampleResult::Skipped;
235
#endif
236
}
237
238
uint32_t segmentIndex = 0;
239
size_t segmentSize = animation.mSegments.Length();
240
PropertyAnimation::SegmentData* segment = animation.mSegments.Elements();
241
while (segment->mEndPortion < computedTiming.mProgress.Value() &&
242
segmentIndex < segmentSize - 1) {
243
++segment;
244
++segmentIndex;
245
}
246
247
double positionInSegment =
248
(computedTiming.mProgress.Value() - segment->mStartPortion) /
249
(segment->mEndPortion - segment->mStartPortion);
250
251
double portion = ComputedTimingFunction::GetPortion(
252
segment->mFunction, positionInSegment, computedTiming.mBeforeFlag);
253
254
// Like above optimization, skip calculation if the target segment isn't
255
// changed and if the portion in the segment isn't changed.
256
// This optimization is needed for CSS animations/transitions with step
257
// timing functions (e.g. the throbber animation on tabs or frame based
258
// animations).
259
// FIXME Bug 1455476: Like the above optimization, we should apply this
260
// optimizations for multiple animation cases and multiple properties as
261
// well.
262
if (aCanSkipCompose == CanSkipCompose::IfPossible &&
263
animation.mSegmentIndexOnLastCompose == segmentIndex &&
264
!animation.mPortionInSegmentOnLastCompose.IsNull() &&
265
animation.mPortionInSegmentOnLastCompose.Value() == portion) {
266
#ifdef DEBUG
267
shouldBeSkipped = true;
268
#else
269
return AnimationHelper::SampleResult::Skipped;
270
#endif
271
}
272
273
AnimationPropertySegment animSegment;
274
animSegment.mFromKey = 0.0;
275
animSegment.mToKey = 1.0;
276
animSegment.mFromValue = AnimationValue(segment->mStartValue);
277
animSegment.mToValue = AnimationValue(segment->mEndValue);
278
animSegment.mFromComposite = segment->mStartComposite;
279
animSegment.mToComposite = segment->mEndComposite;
280
281
// interpolate the property
282
aAnimationValue =
283
Servo_ComposeAnimationSegment(
284
&animSegment, aAnimationValue,
285
animation.mSegments.LastElement().mEndValue, iterCompositeOperation,
286
portion, computedTiming.mCurrentIteration)
287
.Consume();
288
289
#ifdef DEBUG
290
if (shouldBeSkipped) {
291
return AnimationHelper::SampleResult::Skipped;
292
}
293
#endif
294
295
hasInEffectAnimations = true;
296
animation.mProgressOnLastCompose = computedTiming.mProgress;
297
animation.mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration;
298
animation.mSegmentIndexOnLastCompose = segmentIndex;
299
animation.mPortionInSegmentOnLastCompose.SetValue(portion);
300
}
301
302
return hasInEffectAnimations ? AnimationHelper::SampleResult::Sampled
303
: AnimationHelper::SampleResult::None;
304
}
305
306
AnimationHelper::SampleResult AnimationHelper::SampleAnimationForEachNode(
307
TimeStamp aPreviousFrameTime, TimeStamp aCurrentFrameTime,
308
const AnimatedValue* aPreviousValue,
309
nsTArray<PropertyAnimationGroup>& aPropertyAnimationGroups,
310
nsTArray<RefPtr<RawServoAnimationValue>>& aAnimationValues /* out */) {
311
MOZ_ASSERT(!aPropertyAnimationGroups.IsEmpty(),
312
"Should be called with animation data");
313
MOZ_ASSERT(aAnimationValues.IsEmpty(),
314
"Should be called with empty aAnimationValues");
315
316
nsTArray<RefPtr<RawServoAnimationValue>> nonAnimatingValues;
317
for (PropertyAnimationGroup& group : aPropertyAnimationGroups) {
318
// Initialize animation value with base style.
319
RefPtr<RawServoAnimationValue> currValue = group.mBaseStyle;
320
321
CanSkipCompose canSkipCompose = aPropertyAnimationGroups.Length() == 1 &&
322
group.mAnimations.Length() == 1
323
? CanSkipCompose::IfPossible
324
: CanSkipCompose::No;
325
326
MOZ_ASSERT(
327
!group.mAnimations.IsEmpty() ||
328
nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(
329
group.mProperty),
330
"Only transform-like properties can have empty PropertyAnimation list");
331
332
// For properties which are not animating (i.e. their values are always the
333
// same), we store them in a different array, and then merge them into the
334
// final result (a.k.a. aAnimationValues) because we shouldn't take them
335
// into account for SampleResult. (In other words, these properties
336
// shouldn't affect the optimization.)
337
if (group.mAnimations.IsEmpty()) {
338
nonAnimatingValues.AppendElement(std::move(currValue));
339
continue;
340
}
341
342
SampleResult result = SampleAnimationForProperty(
343
aPreviousFrameTime, aCurrentFrameTime, aPreviousValue, canSkipCompose,
344
group.mAnimations, currValue);
345
346
// FIXME: Bug 1455476: Do optimization for multiple properties. For now,
347
// the result is skipped only if the property count == 1.
348
if (result == SampleResult::Skipped) {
349
#ifdef DEBUG
350
aAnimationValues.AppendElement(std::move(currValue));
351
#endif
352
return SampleResult::Skipped;
353
}
354
355
if (result != SampleResult::Sampled) {
356
continue;
357
}
358
359
// Insert the interpolation result into the output array.
360
MOZ_ASSERT(currValue);
361
aAnimationValues.AppendElement(std::move(currValue));
362
}
363
364
SampleResult rv =
365
aAnimationValues.IsEmpty() ? SampleResult::None : SampleResult::Sampled;
366
if (rv == SampleResult::Sampled) {
367
aAnimationValues.AppendElements(nonAnimatingValues);
368
}
369
return rv;
370
}
371
372
static dom::FillMode GetAdjustedFillMode(const Animation& aAnimation) {
373
// Adjust fill mode so that if the main thread is delayed in clearing
374
// this animation we don't introduce flicker by jumping back to the old
375
// underlying value.
376
auto fillMode = static_cast<dom::FillMode>(aAnimation.fillMode());
377
float playbackRate = aAnimation.playbackRate();
378
switch (fillMode) {
379
case dom::FillMode::None:
380
if (playbackRate > 0) {
381
fillMode = dom::FillMode::Forwards;
382
} else if (playbackRate < 0) {
383
fillMode = dom::FillMode::Backwards;
384
}
385
break;
386
case dom::FillMode::Backwards:
387
if (playbackRate > 0) {
388
fillMode = dom::FillMode::Both;
389
}
390
break;
391
case dom::FillMode::Forwards:
392
if (playbackRate < 0) {
393
fillMode = dom::FillMode::Both;
394
}
395
break;
396
default:
397
break;
398
}
399
return fillMode;
400
}
401
402
#ifdef DEBUG
403
static bool HasTransformLikeAnimations(const AnimationArray& aAnimations) {
404
nsCSSPropertyIDSet transformSet =
405
nsCSSPropertyIDSet::TransformLikeProperties();
406
407
for (const Animation& animation : aAnimations) {
408
if (animation.isNotAnimating()) {
409
continue;
410
}
411
412
if (transformSet.HasProperty(animation.property())) {
413
return true;
414
}
415
}
416
417
return false;
418
}
419
#endif
420
421
AnimationStorageData AnimationHelper::ExtractAnimations(
422
const AnimationArray& aAnimations) {
423
AnimationStorageData storageData;
424
425
nsCSSPropertyID prevID = eCSSProperty_UNKNOWN;
426
PropertyAnimationGroup* currData = nullptr;
427
DebugOnly<const layers::Animatable*> currBaseStyle = nullptr;
428
429
for (const Animation& animation : aAnimations) {
430
// Animations with same property are grouped together, so we can just
431
// check if the current property is the same as the previous one for
432
// knowing this is a new group.
433
if (prevID != animation.property()) {
434
// Got a different group, we should create a different array.
435
currData = storageData.mAnimation.AppendElement();
436
currData->mProperty = animation.property();
437
if (animation.transformData()) {
438
MOZ_ASSERT(!storageData.mTransformData,
439
"Only one entry has TransformData");
440
storageData.mTransformData = animation.transformData();
441
}
442
443
prevID = animation.property();
444
445
// Reset the debug pointer.
446
currBaseStyle = nullptr;
447
}
448
449
MOZ_ASSERT(currData);
450
if (animation.baseStyle().type() != Animatable::Tnull_t) {
451
MOZ_ASSERT(!currBaseStyle || *currBaseStyle == animation.baseStyle(),
452
"Should be the same base style");
453
454
currData->mBaseStyle = AnimationValue::FromAnimatable(
455
animation.property(), animation.baseStyle());
456
currBaseStyle = &animation.baseStyle();
457
}
458
459
// If this layers::Animation sets isNotAnimating to true, it only has
460
// base style and doesn't have any animation information, so we can skip
461
// the rest steps. (And so its PropertyAnimationGroup::mAnimation will be
462
// an empty array.)
463
if (animation.isNotAnimating()) {
464
MOZ_ASSERT(nsCSSPropertyIDSet::TransformLikeProperties().HasProperty(
465
animation.property()),
466
"Only transform-like properties could set this true");
467
468
if (animation.property() == eCSSProperty_offset_path) {
469
MOZ_ASSERT(currData->mBaseStyle,
470
"Fixed offset-path should have base style");
471
MOZ_ASSERT(HasTransformLikeAnimations(aAnimations));
472
473
AnimationValue value{currData->mBaseStyle};
474
const StyleOffsetPath& offsetPath = value.GetOffsetPathProperty();
475
if (offsetPath.IsPath()) {
476
MOZ_ASSERT(!storageData.mCachedMotionPath,
477
"Only one offset-path: path() is set");
478
479
RefPtr<gfx::PathBuilder> builder =
480
MotionPathUtils::GetCompositorPathBuilder();
481
storageData.mCachedMotionPath =
482
MotionPathUtils::BuildPath(offsetPath.AsPath(), builder);
483
}
484
}
485
486
continue;
487
}
488
489
PropertyAnimation* propertyAnimation =
490
currData->mAnimations.AppendElement();
491
492
propertyAnimation->mOriginTime = animation.originTime();
493
propertyAnimation->mStartTime = animation.startTime();
494
propertyAnimation->mHoldTime = animation.holdTime();
495
propertyAnimation->mPlaybackRate = animation.playbackRate();
496
propertyAnimation->mIterationComposite =
497
static_cast<dom::IterationCompositeOperation>(
498
animation.iterationComposite());
499
propertyAnimation->mIsNotPlaying = animation.isNotPlaying();
500
propertyAnimation->mTiming =
501
TimingParams{animation.duration(),
502
animation.delay(),
503
animation.endDelay(),
504
animation.iterations(),
505
animation.iterationStart(),
506
static_cast<dom::PlaybackDirection>(animation.direction()),
507
GetAdjustedFillMode(animation),
508
AnimationUtils::TimingFunctionToComputedTimingFunction(
509
animation.easingFunction())};
510
511
nsTArray<PropertyAnimation::SegmentData>& segmentData =
512
propertyAnimation->mSegments;
513
for (const AnimationSegment& segment : animation.segments()) {
514
segmentData.AppendElement(PropertyAnimation::SegmentData{
515
AnimationValue::FromAnimatable(animation.property(),
516
segment.startState()),
517
AnimationValue::FromAnimatable(animation.property(),
518
segment.endState()),
519
AnimationUtils::TimingFunctionToComputedTimingFunction(
520
segment.sampleFn()),
521
segment.startPortion(), segment.endPortion(),
522
static_cast<dom::CompositeOperation>(segment.startComposite()),
523
static_cast<dom::CompositeOperation>(segment.endComposite())});
524
}
525
}
526
527
#ifdef DEBUG
528
// Sanity check that the grouped animation data is correct by looking at the
529
// property set.
530
if (!storageData.mAnimation.IsEmpty()) {
531
nsCSSPropertyIDSet seenProperties;
532
for (const auto& group : storageData.mAnimation) {
533
nsCSSPropertyID id = group.mProperty;
534
535
MOZ_ASSERT(!seenProperties.HasProperty(id), "Should be a new property");
536
seenProperties.AddProperty(id);
537
}
538
539
MOZ_ASSERT(
540
seenProperties.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
541
DisplayItemType::TYPE_TRANSFORM)) ||
542
seenProperties.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
543
DisplayItemType::TYPE_OPACITY)) ||
544
seenProperties.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
545
DisplayItemType::TYPE_BACKGROUND_COLOR)),
546
"The property set of output should be the subset of transform-like "
547
"properties, opacity, or background_color.");
548
549
if (seenProperties.IsSubsetOf(LayerAnimationInfo::GetCSSPropertiesFor(
550
DisplayItemType::TYPE_TRANSFORM))) {
551
MOZ_ASSERT(storageData.mTransformData, "Should have TransformData");
552
}
553
554
if (seenProperties.HasProperty(eCSSProperty_offset_path)) {
555
MOZ_ASSERT(storageData.mTransformData, "Should have TransformData");
556
MOZ_ASSERT(storageData.mTransformData->motionPathData(),
557
"Should have MotionPathData");
558
}
559
}
560
#endif
561
562
return storageData;
563
}
564
565
uint64_t AnimationHelper::GetNextCompositorAnimationsId() {
566
static uint32_t sNextId = 0;
567
++sNextId;
568
569
uint32_t procId = static_cast<uint32_t>(base::GetCurrentProcId());
570
uint64_t nextId = procId;
571
nextId = nextId << 32 | sNextId;
572
return nextId;
573
}
574
575
bool AnimationHelper::SampleAnimations(CompositorAnimationStorage* aStorage,
576
TimeStamp aPreviousFrameTime,
577
TimeStamp aCurrentFrameTime) {
578
MOZ_ASSERT(aStorage);
579
bool isAnimating = false;
580
581
// Do nothing if there are no compositor animations
582
if (!aStorage->AnimationsCount()) {
583
return isAnimating;
584
}
585
586
// Sample the animations in CompositorAnimationStorage
587
for (auto iter = aStorage->ConstAnimationsTableIter(); !iter.Done();
588
iter.Next()) {
589
auto& animationStorageData = iter.Data();
590
if (animationStorageData.mAnimation.IsEmpty()) {
591
continue;
592
}
593
594
isAnimating = true;
595
nsTArray<RefPtr<RawServoAnimationValue>> animationValues;
596
AnimatedValue* previousValue = aStorage->GetAnimatedValue(iter.Key());
597
AnimationHelper::SampleResult sampleResult =
598
AnimationHelper::SampleAnimationForEachNode(
599
aPreviousFrameTime, aCurrentFrameTime, previousValue,
600
animationStorageData.mAnimation, animationValues);
601
602
if (sampleResult != AnimationHelper::SampleResult::Sampled) {
603
continue;
604
}
605
606
const PropertyAnimationGroup& lastPropertyAnimationGroup =
607
animationStorageData.mAnimation.LastElement();
608
609
// Store the AnimatedValue
610
switch (lastPropertyAnimationGroup.mProperty) {
611
case eCSSProperty_background_color: {
612
aStorage->SetAnimatedValue(
613
iter.Key(), Servo_AnimationValue_GetColor(animationValues[0],
614
NS_RGBA(0, 0, 0, 0)));
615
break;
616
}
617
case eCSSProperty_opacity: {
618
MOZ_ASSERT(animationValues.Length() == 1);
619
aStorage->SetAnimatedValue(
620
iter.Key(), Servo_AnimationValue_GetOpacity(animationValues[0]));
621
break;
622
}
623
case eCSSProperty_rotate:
624
case eCSSProperty_scale:
625
case eCSSProperty_translate:
626
case eCSSProperty_transform:
627
case eCSSProperty_offset_path:
628
case eCSSProperty_offset_distance:
629
case eCSSProperty_offset_rotate:
630
case eCSSProperty_offset_anchor: {
631
MOZ_ASSERT(animationStorageData.mTransformData);
632
633
gfx::Matrix4x4 transform = ServoAnimationValueToMatrix4x4(
634
animationValues, *animationStorageData.mTransformData,
635
animationStorageData.mCachedMotionPath);
636
gfx::Matrix4x4 frameTransform = transform;
637
// If the parent has perspective transform, then the offset into
638
// reference frame coordinates is already on this transform. If not,
639
// then we need to ask for it to be added here.
640
const TransformData& transformData =
641
*animationStorageData.mTransformData;
642
if (!transformData.hasPerspectiveParent()) {
643
nsLayoutUtils::PostTranslate(transform, transformData.origin(),
644
transformData.appUnitsPerDevPixel(),
645
true);
646
}
647
648
transform.PostScale(transformData.inheritedXScale(),
649
transformData.inheritedYScale(), 1);
650
651
aStorage->SetAnimatedValue(iter.Key(), std::move(transform),
652
std::move(frameTransform), transformData);
653
break;
654
}
655
default:
656
MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
657
}
658
}
659
660
return isAnimating;
661
}
662
663
gfx::Matrix4x4 AnimationHelper::ServoAnimationValueToMatrix4x4(
664
const nsTArray<RefPtr<RawServoAnimationValue>>& aValues,
665
const TransformData& aTransformData, gfx::Path* aCachedMotionPath) {
666
using nsStyleTransformMatrix::TransformReferenceBox;
667
668
// This is a bit silly just to avoid the transform list copy from the
669
// animation transform list.
670
auto noneTranslate = StyleTranslate::None();
671
auto noneRotate = StyleRotate::None();
672
auto noneScale = StyleScale::None();
673
const StyleTransform noneTransform;
674
675
const StyleTranslate* translate = nullptr;
676
const StyleRotate* rotate = nullptr;
677
const StyleScale* scale = nullptr;
678
const StyleTransform* transform = nullptr;
679
const StyleOffsetPath* path = nullptr;
680
const StyleLengthPercentage* distance = nullptr;
681
const StyleOffsetRotate* offsetRotate = nullptr;
682
const StylePositionOrAuto* anchor = nullptr;
683
684
for (const auto& value : aValues) {
685
MOZ_ASSERT(value);
686
nsCSSPropertyID id = Servo_AnimationValue_GetPropertyId(value);
687
switch (id) {
688
case eCSSProperty_transform:
689
MOZ_ASSERT(!transform);
690
transform = Servo_AnimationValue_GetTransform(value);
691
break;
692
case eCSSProperty_translate:
693
MOZ_ASSERT(!translate);
694
translate = Servo_AnimationValue_GetTranslate(value);
695
break;
696
case eCSSProperty_rotate:
697
MOZ_ASSERT(!rotate);
698
rotate = Servo_AnimationValue_GetRotate(value);
699
break;
700
case eCSSProperty_scale:
701
MOZ_ASSERT(!scale);
702
scale = Servo_AnimationValue_GetScale(value);
703
break;
704
case eCSSProperty_offset_path:
705
MOZ_ASSERT(!path);
706
path = Servo_AnimationValue_GetOffsetPath(value);
707
break;
708
case eCSSProperty_offset_distance:
709
MOZ_ASSERT(!distance);
710
distance = Servo_AnimationValue_GetOffsetDistance(value);
711
break;
712
case eCSSProperty_offset_rotate:
713
MOZ_ASSERT(!offsetRotate);
714
offsetRotate = Servo_AnimationValue_GetOffsetRotate(value);
715
break;
716
case eCSSProperty_offset_anchor:
717
MOZ_ASSERT(!anchor);
718
anchor = Servo_AnimationValue_GetOffsetAnchor(value);
719
break;
720
default:
721
MOZ_ASSERT_UNREACHABLE("Unsupported transform-like property");
722
}
723
}
724
725
TransformReferenceBox refBox(nullptr, aTransformData.bounds());
726
Maybe<ResolvedMotionPathData> motion = MotionPathUtils::ResolveMotionPath(
727
path, distance, offsetRotate, anchor, aTransformData.motionPathData(),
728
refBox, aCachedMotionPath);
729
730
// We expect all our transform data to arrive in device pixels
731
gfx::Point3D transformOrigin = aTransformData.transformOrigin();
732
nsDisplayTransform::FrameTransformProperties props(
733
translate ? *translate : noneTranslate, rotate ? *rotate : noneRotate,
734
scale ? *scale : noneScale, transform ? *transform : noneTransform,
735
motion, transformOrigin);
736
737
return nsDisplayTransform::GetResultingTransformMatrix(
738
props, refBox, aTransformData.origin(),
739
aTransformData.appUnitsPerDevPixel(), 0);
740
}
741
742
} // namespace layers
743
} // namespace mozilla