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 "CompositorAnimationStorage.h"
8
9 #include "AnimationHelper.h"
10 #include "mozilla/gfx/MatrixFwd.h"
11 #include "mozilla/layers/APZSampler.h" // for APZSampler
12 #include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent
13 #include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder
14 #include "mozilla/layers/LayerManagerComposite.h" // for LayerComposite, etc
15 #include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper
16 #include "mozilla/layers/OMTAController.h" // for OMTAController
17 #include "mozilla/ScopeExit.h"
18 #include "mozilla/ServoStyleConsts.h"
19 #include "mozilla/webrender/WebRenderTypes.h" // for ToWrTransformProperty, etc
20 #include "nsDeviceContext.h" // for AppUnitsPerCSSPixel
21 #include "nsDisplayList.h" // for nsDisplayTransform, etc
22 #include "nsLayoutUtils.h"
23 #include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch
24
25 namespace mozilla {
26 namespace layers {
27
28 using gfx::Matrix4x4;
29
Clear()30 void CompositorAnimationStorage::Clear() {
31 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
32 // This function should only be called via the non Webrender version of
33 // SampleAnimations.
34 mLock.AssertCurrentThreadOwns();
35
36 mAnimatedValues.Clear();
37 mAnimations.clear();
38 }
39
ClearById(const uint64_t & aId)40 void CompositorAnimationStorage::ClearById(const uint64_t& aId) {
41 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
42 MutexAutoLock lock(mLock);
43
44 mAnimatedValues.Remove(aId);
45 mAnimations.erase(aId);
46 }
47
HasAnimations() const48 bool CompositorAnimationStorage::HasAnimations() const {
49 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
50 MutexAutoLock lock(mLock);
51
52 return !mAnimations.empty();
53 }
54
GetAnimatedValue(const uint64_t & aId) const55 AnimatedValue* CompositorAnimationStorage::GetAnimatedValue(
56 const uint64_t& aId) const {
57 mLock.AssertCurrentThreadOwns();
58
59 return mAnimatedValues.Get(aId);
60 }
61
GetOMTAValue(const uint64_t & aId) const62 OMTAValue CompositorAnimationStorage::GetOMTAValue(const uint64_t& aId) const {
63 MutexAutoLock lock(mLock);
64
65 OMTAValue omtaValue = mozilla::null_t();
66 auto animatedValue = GetAnimatedValue(aId);
67 if (!animatedValue) {
68 return omtaValue;
69 }
70
71 animatedValue->Value().match(
72 [&](const AnimationTransform& aTransform) {
73 gfx::Matrix4x4 transform = aTransform.mFrameTransform;
74 const TransformData& data = aTransform.mData;
75 float scale = data.appUnitsPerDevPixel();
76 gfx::Point3D transformOrigin = data.transformOrigin();
77
78 // Undo the rebasing applied by
79 // nsDisplayTransform::GetResultingTransformMatrixInternal
80 transform.ChangeBasis(-transformOrigin);
81
82 // Convert to CSS pixels (this undoes the operations performed by
83 // nsStyleTransformMatrix::ProcessTranslatePart which is called from
84 // nsDisplayTransform::GetResultingTransformMatrix)
85 double devPerCss = double(scale) / double(AppUnitsPerCSSPixel());
86 transform._41 *= devPerCss;
87 transform._42 *= devPerCss;
88 transform._43 *= devPerCss;
89 omtaValue = transform;
90 },
91 [&](const float& aOpacity) { omtaValue = aOpacity; },
92 [&](const nscolor& aColor) { omtaValue = aColor; });
93 return omtaValue;
94 }
95
SetAnimatedValueForWebRender(uint64_t aId,AnimatedValue * aPreviousValue,const gfx::Matrix4x4 & aFrameTransform,const TransformData & aData)96 void CompositorAnimationStorage::SetAnimatedValueForWebRender(
97 uint64_t aId, AnimatedValue* aPreviousValue,
98 const gfx::Matrix4x4& aFrameTransform, const TransformData& aData) {
99 mLock.AssertCurrentThreadOwns();
100
101 if (!aPreviousValue) {
102 MOZ_ASSERT(!mAnimatedValues.Contains(aId));
103 mAnimatedValues.InsertOrUpdate(
104 aId,
105 MakeUnique<AnimatedValue>(gfx::Matrix4x4(), aFrameTransform, aData));
106 return;
107 }
108 MOZ_ASSERT(aPreviousValue->Is<AnimationTransform>());
109 MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId));
110
111 aPreviousValue->SetTransformForWebRender(aFrameTransform, aData);
112 }
113
SetAnimatedValue(uint64_t aId,AnimatedValue * aPreviousValue,const gfx::Matrix4x4 & aTransformInDevSpace,const gfx::Matrix4x4 & aFrameTransform,const TransformData & aData)114 void CompositorAnimationStorage::SetAnimatedValue(
115 uint64_t aId, AnimatedValue* aPreviousValue,
116 const gfx::Matrix4x4& aTransformInDevSpace,
117 const gfx::Matrix4x4& aFrameTransform, const TransformData& aData) {
118 mLock.AssertCurrentThreadOwns();
119
120 if (!aPreviousValue) {
121 MOZ_ASSERT(!mAnimatedValues.Contains(aId));
122 mAnimatedValues.InsertOrUpdate(
123 aId, MakeUnique<AnimatedValue>(aTransformInDevSpace, aFrameTransform,
124 aData));
125 return;
126 }
127 MOZ_ASSERT(aPreviousValue->Is<AnimationTransform>());
128 MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId));
129
130 aPreviousValue->SetTransform(aTransformInDevSpace, aFrameTransform, aData);
131 }
132
SetAnimatedValue(uint64_t aId,AnimatedValue * aPreviousValue,nscolor aColor)133 void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
134 AnimatedValue* aPreviousValue,
135 nscolor aColor) {
136 mLock.AssertCurrentThreadOwns();
137
138 if (!aPreviousValue) {
139 MOZ_ASSERT(!mAnimatedValues.Contains(aId));
140 mAnimatedValues.InsertOrUpdate(aId, MakeUnique<AnimatedValue>(aColor));
141 return;
142 }
143
144 MOZ_ASSERT(aPreviousValue->Is<nscolor>());
145 MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId));
146 aPreviousValue->SetColor(aColor);
147 }
148
SetAnimatedValue(uint64_t aId,AnimatedValue * aPreviousValue,float aOpacity)149 void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
150 AnimatedValue* aPreviousValue,
151 float aOpacity) {
152 mLock.AssertCurrentThreadOwns();
153
154 if (!aPreviousValue) {
155 MOZ_ASSERT(!mAnimatedValues.Contains(aId));
156 mAnimatedValues.InsertOrUpdate(aId, MakeUnique<AnimatedValue>(aOpacity));
157 return;
158 }
159
160 MOZ_ASSERT(aPreviousValue->Is<float>());
161 MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId));
162 aPreviousValue->SetOpacity(aOpacity);
163 }
164
SetAnimations(uint64_t aId,const LayersId & aLayersId,const AnimationArray & aValue)165 void CompositorAnimationStorage::SetAnimations(uint64_t aId,
166 const LayersId& aLayersId,
167 const AnimationArray& aValue) {
168 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
169 MutexAutoLock lock(mLock);
170
171 mAnimations[aId] = std::make_unique<AnimationStorageData>(
172 AnimationHelper::ExtractAnimations(aLayersId, aValue));
173
174 // If there is the last animated value, then we need to store the id to remove
175 // the value if the new animation doesn't produce any animated data (i.e. in
176 // the delay phase) when we sample this new animation.
177 if (mAnimatedValues.Contains(aId)) {
178 mNewAnimations.insert(aId);
179 }
180 }
181
182 // Returns clip rect in the scroll frame's coordinate space.
GetClipRectForPartialPrerender(const LayersId aLayersId,const PartialPrerenderData & aPartialPrerenderData,const RefPtr<APZSampler> & aSampler)183 static ParentLayerRect GetClipRectForPartialPrerender(
184 const LayersId aLayersId, const PartialPrerenderData& aPartialPrerenderData,
185 const RefPtr<APZSampler>& aSampler) {
186 if (aSampler &&
187 aPartialPrerenderData.scrollId() != ScrollableLayerGuid::NULL_SCROLL_ID) {
188 return aSampler->GetCompositionBounds(aLayersId,
189 aPartialPrerenderData.scrollId());
190 }
191
192 return aPartialPrerenderData.clipRect();
193 }
194
SampleAnimations(const OMTAController * aOMTAController,TimeStamp aPreviousFrameTime,TimeStamp aCurrentFrameTime)195 bool CompositorAnimationStorage::SampleAnimations(
196 const OMTAController* aOMTAController, TimeStamp aPreviousFrameTime,
197 TimeStamp aCurrentFrameTime) {
198 MutexAutoLock lock(mLock);
199
200 bool isAnimating = false;
201 auto cleanup = MakeScopeExit([&] { mNewAnimations.clear(); });
202
203 // Do nothing if there are no compositor animations
204 if (mAnimations.empty()) {
205 return isAnimating;
206 }
207
208 std::unordered_map<LayersId, nsTArray<uint64_t>, LayersId::HashFn> janked;
209
210 RefPtr<APZSampler> apzSampler = mCompositorBridge->GetAPZSampler();
211
212 for (const auto& iter : mAnimations) {
213 const auto& animationStorageData = iter.second;
214 if (animationStorageData->mAnimation.IsEmpty()) {
215 continue;
216 }
217
218 isAnimating = true;
219 AutoTArray<RefPtr<RawServoAnimationValue>, 1> animationValues;
220 AnimatedValue* previousValue = GetAnimatedValue(iter.first);
221 AnimationHelper::SampleResult sampleResult =
222 AnimationHelper::SampleAnimationForEachNode(
223 aPreviousFrameTime, aCurrentFrameTime, previousValue,
224 animationStorageData->mAnimation, animationValues);
225
226 if (sampleResult != AnimationHelper::SampleResult::Sampled) {
227 if (mNewAnimations.find(iter.first) != mNewAnimations.end()) {
228 mAnimatedValues.Remove(iter.first);
229 }
230 continue;
231 }
232
233 const PropertyAnimationGroup& lastPropertyAnimationGroup =
234 animationStorageData->mAnimation.LastElement();
235
236 // Store the AnimatedValue
237 switch (lastPropertyAnimationGroup.mProperty) {
238 case eCSSProperty_background_color: {
239 SetAnimatedValue(iter.first, previousValue,
240 Servo_AnimationValue_GetColor(animationValues[0],
241 NS_RGBA(0, 0, 0, 0)));
242 break;
243 }
244 case eCSSProperty_opacity: {
245 MOZ_ASSERT(animationValues.Length() == 1);
246 SetAnimatedValue(iter.first, previousValue,
247 Servo_AnimationValue_GetOpacity(animationValues[0]));
248 break;
249 }
250 case eCSSProperty_rotate:
251 case eCSSProperty_scale:
252 case eCSSProperty_translate:
253 case eCSSProperty_transform:
254 case eCSSProperty_offset_path:
255 case eCSSProperty_offset_distance:
256 case eCSSProperty_offset_rotate:
257 case eCSSProperty_offset_anchor: {
258 MOZ_ASSERT(animationStorageData->mTransformData);
259
260 const TransformData& transformData =
261 *animationStorageData->mTransformData;
262 MOZ_ASSERT(transformData.origin() == nsPoint());
263
264 gfx::Matrix4x4 frameTransform =
265 AnimationHelper::ServoAnimationValueToMatrix4x4(
266 animationValues, transformData,
267 animationStorageData->mCachedMotionPath);
268
269 if (const Maybe<PartialPrerenderData>& partialPrerenderData =
270 transformData.partialPrerenderData()) {
271 gfx::Matrix4x4 transform = frameTransform;
272 transform.PostTranslate(
273 partialPrerenderData->position().ToUnknownPoint());
274
275 gfx::Matrix4x4 transformInClip =
276 partialPrerenderData->transformInClip();
277 if (apzSampler && partialPrerenderData->scrollId() !=
278 ScrollableLayerGuid::NULL_SCROLL_ID) {
279 AsyncTransform asyncTransform =
280 apzSampler->GetCurrentAsyncTransform(
281 animationStorageData->mLayersId,
282 partialPrerenderData->scrollId(), LayoutAndVisual);
283 transformInClip.PostTranslate(
284 asyncTransform.mTranslation.ToUnknownPoint());
285 }
286 transformInClip = transform * transformInClip;
287
288 ParentLayerRect clipRect =
289 GetClipRectForPartialPrerender(animationStorageData->mLayersId,
290 *partialPrerenderData, apzSampler);
291 if (AnimationHelper::ShouldBeJank(
292 partialPrerenderData->rect(),
293 partialPrerenderData->overflowedSides(), transformInClip,
294 clipRect)) {
295 if (previousValue) {
296 frameTransform = previousValue->Transform().mFrameTransform;
297 }
298 janked[animationStorageData->mLayersId].AppendElement(iter.first);
299 }
300 }
301
302 SetAnimatedValueForWebRender(iter.first, previousValue, frameTransform,
303 transformData);
304 break;
305 }
306 default:
307 MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
308 }
309 }
310
311 if (!janked.empty() && aOMTAController) {
312 aOMTAController->NotifyJankedAnimations(std::move(janked));
313 }
314
315 return isAnimating;
316 }
317
CollectWebRenderAnimations() const318 WrAnimations CompositorAnimationStorage::CollectWebRenderAnimations() const {
319 MutexAutoLock lock(mLock);
320
321 WrAnimations animations;
322
323 for (const auto& animatedValueEntry : mAnimatedValues) {
324 AnimatedValue* value = animatedValueEntry.GetWeak();
325 value->Value().match(
326 [&](const AnimationTransform& aTransform) {
327 animations.mTransformArrays.AppendElement(wr::ToWrTransformProperty(
328 animatedValueEntry.GetKey(), aTransform.mFrameTransform));
329 },
330 [&](const float& aOpacity) {
331 animations.mOpacityArrays.AppendElement(
332 wr::ToWrOpacityProperty(animatedValueEntry.GetKey(), aOpacity));
333 },
334 [&](const nscolor& aColor) {
335 animations.mColorArrays.AppendElement(wr::ToWrColorProperty(
336 animatedValueEntry.GetKey(),
337 ToDeviceColor(gfx::sRGBColor::FromABGR(aColor))));
338 });
339 }
340
341 return animations;
342 }
343
FrameTransformToTransformInDevice(const gfx::Matrix4x4 & aFrameTransform,Layer * aLayer,const TransformData & aTransformData)344 static gfx::Matrix4x4 FrameTransformToTransformInDevice(
345 const gfx::Matrix4x4& aFrameTransform, Layer* aLayer,
346 const TransformData& aTransformData) {
347 gfx::Matrix4x4 transformInDevice = aFrameTransform;
348 // If our parent layer is a perspective layer, then the offset into reference
349 // frame coordinates is already on that layer. If not, then we need to ask
350 // for it to be added here.
351 if (!aLayer->GetParent() ||
352 !aLayer->GetParent()->GetTransformIsPerspective()) {
353 nsLayoutUtils::PostTranslate(
354 transformInDevice, aTransformData.origin(),
355 aTransformData.appUnitsPerDevPixel(),
356 aLayer->GetContentFlags() & Layer::CONTENT_SNAP_TO_GRID);
357 }
358
359 if (ContainerLayer* c = aLayer->AsContainerLayer()) {
360 transformInDevice.PostScale(c->GetInheritedXScale(),
361 c->GetInheritedYScale(), 1);
362 }
363
364 return transformInDevice;
365 }
366
GetTransformForPartialPrerender(Layer * aLayer,const LayersId aLayersId,const ScrollableLayerGuid::ViewID & aScrollId,const RefPtr<APZSampler> & aSampler)367 static Matrix4x4 GetTransformForPartialPrerender(
368 Layer* aLayer, const LayersId aLayersId,
369 const ScrollableLayerGuid::ViewID& aScrollId,
370 const RefPtr<APZSampler>& aSampler) {
371 MOZ_ASSERT(aLayer);
372
373 ParentLayerPoint translationByApz;
374 Matrix4x4 transform;
375
376 for (Layer* layer = aLayer; layer; layer = layer->GetParent()) {
377 if (layer->AsRefLayer()) {
378 MOZ_ASSERT(layer->AsRefLayer()->GetReferentId() == aLayersId);
379 break;
380 }
381
382 // Accumulate static transforms.
383 if (layer != aLayer) {
384 Matrix4x4 baseTransform = layer->GetBaseTransform();
385 if (ContainerLayer* container = layer->AsContainerLayer()) {
386 baseTransform.PostScale(container->GetPreXScale(),
387 container->GetPreYScale(), 1);
388 }
389 transform *= baseTransform;
390 }
391
392 if (!layer->GetIsStickyPosition() && !layer->GetIsFixedPosition()) {
393 bool hasSameScrollId = false;
394 for (uint32_t i = 0; i < layer->GetScrollMetadataCount(); i++) {
395 // Factor APZ translation if there exists.
396 if (aSampler) {
397 LayerMetricsWrapper wrapper = LayerMetricsWrapper(layer, i);
398 AsyncTransform asyncTransform =
399 aSampler->GetCurrentAsyncTransform(wrapper, LayoutAndVisual);
400 translationByApz += asyncTransform.mTranslation;
401 }
402 if (layer->GetFrameMetrics(i).GetScrollId() == aScrollId) {
403 hasSameScrollId = true;
404 }
405 }
406
407 if (hasSameScrollId) {
408 break;
409 }
410 } else {
411 // Bug 1642547: Fix for position:sticky layers.
412 }
413 }
414
415 transform.PostTranslate(translationByApz.ToUnknownPoint());
416
417 return transform;
418 }
419
ApplyAnimatedValue(CompositorBridgeParent * aCompositorBridge,Layer * aLayer,nsCSSPropertyID aProperty,AnimatedValue * aPreviousValue,const nsTArray<RefPtr<RawServoAnimationValue>> & aValues)420 bool CompositorAnimationStorage::ApplyAnimatedValue(
421 CompositorBridgeParent* aCompositorBridge, Layer* aLayer,
422 nsCSSPropertyID aProperty, AnimatedValue* aPreviousValue,
423 const nsTArray<RefPtr<RawServoAnimationValue>>& aValues) {
424 mLock.AssertCurrentThreadOwns();
425
426 MOZ_ASSERT(!aValues.IsEmpty());
427
428 bool janked = false;
429 HostLayer* layerCompositor = aLayer->AsHostLayer();
430 switch (aProperty) {
431 case eCSSProperty_background_color: {
432 MOZ_ASSERT(aValues.Length() == 1);
433 // We don't support 'color' animations on the compositor yet so we never
434 // meet currentColor on the compositor.
435 nscolor color =
436 Servo_AnimationValue_GetColor(aValues[0], NS_RGBA(0, 0, 0, 0));
437 aLayer->AsColorLayer()->SetColor(gfx::ToDeviceColor(color));
438 SetAnimatedValue(aLayer->GetCompositorAnimationsId(), aPreviousValue,
439 color);
440
441 layerCompositor->SetShadowOpacity(aLayer->GetOpacity());
442 layerCompositor->SetShadowOpacitySetByAnimation(false);
443 layerCompositor->SetShadowBaseTransform(aLayer->GetBaseTransform());
444 layerCompositor->SetShadowTransformSetByAnimation(false);
445 break;
446 }
447 case eCSSProperty_opacity: {
448 MOZ_ASSERT(aValues.Length() == 1);
449 float opacity = Servo_AnimationValue_GetOpacity(aValues[0]);
450 layerCompositor->SetShadowOpacity(opacity);
451 layerCompositor->SetShadowOpacitySetByAnimation(true);
452 SetAnimatedValue(aLayer->GetCompositorAnimationsId(), aPreviousValue,
453 opacity);
454
455 layerCompositor->SetShadowBaseTransform(aLayer->GetBaseTransform());
456 layerCompositor->SetShadowTransformSetByAnimation(false);
457 break;
458 }
459 case eCSSProperty_rotate:
460 case eCSSProperty_scale:
461 case eCSSProperty_translate:
462 case eCSSProperty_transform:
463 case eCSSProperty_offset_path:
464 case eCSSProperty_offset_distance:
465 case eCSSProperty_offset_rotate:
466 case eCSSProperty_offset_anchor: {
467 MOZ_ASSERT(aLayer->GetTransformData());
468 const TransformData& transformData = *aLayer->GetTransformData();
469 gfx::Matrix4x4 frameTransform =
470 AnimationHelper::ServoAnimationValueToMatrix4x4(
471 aValues, transformData, aLayer->CachedMotionPath());
472
473 gfx::Matrix4x4 transform = FrameTransformToTransformInDevice(
474 frameTransform, aLayer, transformData);
475 if (const Maybe<PartialPrerenderData>& partialPrerenderData =
476 transformData.partialPrerenderData()) {
477 Matrix4x4 transformInClip = GetTransformForPartialPrerender(
478 aLayer, aLayer->GetAnimationLayersId(),
479 partialPrerenderData->scrollId(),
480 aCompositorBridge->GetAPZSampler());
481 transformInClip = transform * transformInClip;
482 ParentLayerRect clipRect = GetClipRectForPartialPrerender(
483 aLayer->GetAnimationLayersId(), *partialPrerenderData,
484 aCompositorBridge->GetAPZSampler());
485 if (AnimationHelper::ShouldBeJank(
486 partialPrerenderData->rect(),
487 partialPrerenderData->overflowedSides(), transformInClip,
488 clipRect)) {
489 // It's possible that we don't have the previous value and we don't
490 // either have enough area to composite in the first composition,
491 // e.g. a translate animation with a step timing function. In such
492 // cases we use the base transform value which was calculated on the
493 // main-thread as a fallback value.
494 transform = aPreviousValue
495 ? aPreviousValue->Transform().mTransformInDevSpace
496 : aLayer->GetBaseTransform();
497 janked = true;
498 }
499 }
500
501 layerCompositor->SetShadowBaseTransform(transform);
502 layerCompositor->SetShadowTransformSetByAnimation(true);
503 SetAnimatedValue(aLayer->GetCompositorAnimationsId(), aPreviousValue,
504 transform, frameTransform, transformData);
505
506 layerCompositor->SetShadowOpacity(aLayer->GetOpacity());
507 layerCompositor->SetShadowOpacitySetByAnimation(false);
508 break;
509 }
510 default:
511 MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
512 }
513 return !janked;
514 }
515
SampleAnimations(Layer * aRoot,CompositorBridgeParent * aCompositorBridge,TimeStamp aPreviousFrameTime,TimeStamp aCurrentFrameTime)516 bool CompositorAnimationStorage::SampleAnimations(
517 Layer* aRoot, CompositorBridgeParent* aCompositorBridge,
518 TimeStamp aPreviousFrameTime, TimeStamp aCurrentFrameTime) {
519 MutexAutoLock lock(mLock);
520
521 bool isAnimating = false;
522
523 auto autoClearAnimationStorage = MakeScopeExit([&] {
524 if (!isAnimating) {
525 // Clean up the CompositorAnimationStorage because
526 // there are no active animations running
527 Clear();
528 }
529 });
530
531 std::unordered_map<LayersId, nsTArray<uint64_t>, LayersId::HashFn> janked;
532
533 ForEachNode<ForwardIterator>(aRoot, [&](Layer* layer) {
534 auto& propertyAnimationGroups = layer->GetPropertyAnimationGroups();
535 if (propertyAnimationGroups.IsEmpty()) {
536 return;
537 }
538
539 isAnimating = true;
540 AnimatedValue* previousValue =
541 GetAnimatedValue(layer->GetCompositorAnimationsId());
542
543 AutoTArray<RefPtr<RawServoAnimationValue>, 1> animationValues;
544 AnimationHelper::SampleResult sampleResult =
545 AnimationHelper::SampleAnimationForEachNode(
546 aPreviousFrameTime, aCurrentFrameTime, previousValue,
547 propertyAnimationGroups, animationValues);
548
549 const PropertyAnimationGroup& lastPropertyAnimationGroup =
550 propertyAnimationGroups.LastElement();
551
552 switch (sampleResult) {
553 case AnimationHelper::SampleResult::Sampled:
554 // We assume all transform like properties (on the same frame) live in
555 // a single same layer, so using the transform data of the last element
556 // should be fine.
557 if (!ApplyAnimatedValue(aCompositorBridge, layer,
558 lastPropertyAnimationGroup.mProperty,
559 previousValue, animationValues)) {
560 // Reset the last composition values in cases of jank so that we will
561 // never mis-compare in a sanity check in the case of
562 // SampleResult::Skipped below in this function.
563 //
564 // An example;
565 // a translateX(0px) -> translateX(100px) animation with step(2,start)
566 // and if the animation janked at translateX(50px) and in a later
567 // frame if the calculated transform value is going to be still
568 // translateX(50px) (i.e. at the same timing portion calculated by the
569 // step timing function), we skip sampling. That's correct ideally.
570 // But we have an assertion to do a sanity check for the skip sampling
571 // case that the check compares the calculated value translateX(50px)
572 // with the previous composited value. In this case the previous
573 // composited value is translateX(0px).
574 //
575 // NOTE: Ideally we shouldn't update the last composition values when
576 // we met janks, but it's quite hard to tell whether the jank will
577 // happen or not when we calculate each transform like properties'
578 // value (i.e. when we set the last composition value) since janks are
579 // caused by a result of the combinations of all transform like
580 // properties (e.g. `transform: translateX(50px)` and
581 // `translate: -50px` results `translateX(0px)`.
582 for (PropertyAnimationGroup& group : propertyAnimationGroups) {
583 group.ResetLastCompositionValues();
584 }
585 janked[layer->GetAnimationLayersId()].AppendElement(
586 layer->GetCompositorAnimationsId());
587 }
588 break;
589 case AnimationHelper::SampleResult::Skipped:
590 switch (lastPropertyAnimationGroup.mProperty) {
591 case eCSSProperty_background_color:
592 case eCSSProperty_opacity: {
593 if (lastPropertyAnimationGroup.mProperty == eCSSProperty_opacity) {
594 MOZ_ASSERT(
595 layer->AsHostLayer()->GetShadowOpacitySetByAnimation());
596 #ifdef DEBUG
597 // Disable this assertion until the root cause is fixed in bug
598 // 1459775.
599 // MOZ_ASSERT(FuzzyEqualsMultiplicative(
600 // Servo_AnimationValue_GetOpacity(animationValue),
601 // *(GetAnimationOpacity(layer->GetCompositorAnimationsId()))));
602 #endif
603 }
604 // Even if opacity or background-color animation value has
605 // unchanged, we have to set the shadow base transform value
606 // here since the value might have been changed by APZC.
607 HostLayer* layerCompositor = layer->AsHostLayer();
608 layerCompositor->SetShadowBaseTransform(layer->GetBaseTransform());
609 layerCompositor->SetShadowTransformSetByAnimation(false);
610 break;
611 }
612 case eCSSProperty_rotate:
613 case eCSSProperty_scale:
614 case eCSSProperty_translate:
615 case eCSSProperty_transform:
616 case eCSSProperty_offset_path:
617 case eCSSProperty_offset_distance:
618 case eCSSProperty_offset_rotate:
619 case eCSSProperty_offset_anchor: {
620 MOZ_ASSERT(
621 layer->AsHostLayer()->GetShadowTransformSetByAnimation());
622 MOZ_ASSERT(previousValue);
623 MOZ_ASSERT(layer->GetTransformData());
624 #ifdef DEBUG
625 gfx::Matrix4x4 frameTransform =
626 AnimationHelper::ServoAnimationValueToMatrix4x4(
627 animationValues, *layer->GetTransformData(),
628 layer->CachedMotionPath());
629 gfx::Matrix4x4 transformInDevice =
630 FrameTransformToTransformInDevice(frameTransform, layer,
631 *layer->GetTransformData());
632 MOZ_ASSERT(previousValue->Transform()
633 .mTransformInDevSpace.FuzzyEqualsMultiplicative(
634 transformInDevice));
635 #endif
636 // In the case of transform we have to set the unchanged
637 // transform value again because APZC might have modified the
638 // previous shadow base transform value.
639 HostLayer* layerCompositor = layer->AsHostLayer();
640 layerCompositor->SetShadowBaseTransform(
641 // FIXME: Bug 1459775: It seems possible that we somehow try
642 // to sample animations and skip it even if the previous value
643 // has been discarded from the animation storage when we enable
644 // layer tree cache. So for the safety, in the case where we
645 // have no previous animation value, we set non-animating value
646 // instead.
647 previousValue ? previousValue->Transform().mTransformInDevSpace
648 : layer->GetBaseTransform());
649 break;
650 }
651 default:
652 MOZ_ASSERT_UNREACHABLE("Unsupported properties");
653 break;
654 }
655 break;
656 case AnimationHelper::SampleResult::None: {
657 HostLayer* layerCompositor = layer->AsHostLayer();
658 layerCompositor->SetShadowBaseTransform(layer->GetBaseTransform());
659 layerCompositor->SetShadowTransformSetByAnimation(false);
660 layerCompositor->SetShadowOpacity(layer->GetOpacity());
661 layerCompositor->SetShadowOpacitySetByAnimation(false);
662 break;
663 }
664 default:
665 break;
666 }
667 });
668
669 if (!janked.empty()) {
670 aCompositorBridge->NotifyJankedAnimations(janked);
671 }
672
673 return isAnimating;
674 }
675
676 } // namespace layers
677 } // namespace mozilla
678