/src/mozilla-central/gfx/layers/AnimationHelper.cpp
Line | Count | Source (jump to first uncovered line) |
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/ServoBindings.h" // for Servo_ComposeAnimationSegment, etc |
16 | | #include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc |
17 | | #include "nsDeviceContext.h" // for AppUnitsPerCSSPixel |
18 | | #include "nsDisplayList.h" // for nsDisplayTransform, etc |
19 | | |
20 | | namespace mozilla { |
21 | | namespace layers { |
22 | | |
23 | | void |
24 | | CompositorAnimationStorage::Clear() |
25 | 0 | { |
26 | 0 | MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); |
27 | 0 |
|
28 | 0 | mAnimatedValues.Clear(); |
29 | 0 | mAnimations.Clear(); |
30 | 0 | } |
31 | | |
32 | | void |
33 | | CompositorAnimationStorage::ClearById(const uint64_t& aId) |
34 | 0 | { |
35 | 0 | MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); |
36 | 0 |
|
37 | 0 | mAnimatedValues.Remove(aId); |
38 | 0 | mAnimations.Remove(aId); |
39 | 0 | } |
40 | | |
41 | | AnimatedValue* |
42 | | CompositorAnimationStorage::GetAnimatedValue(const uint64_t& aId) const |
43 | 0 | { |
44 | 0 | MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); |
45 | 0 | return mAnimatedValues.Get(aId); |
46 | 0 | } |
47 | | |
48 | | OMTAValue |
49 | | CompositorAnimationStorage::GetOMTAValue(const uint64_t& aId) const |
50 | 0 | { |
51 | 0 | OMTAValue omtaValue = mozilla::null_t(); |
52 | 0 | auto animatedValue = GetAnimatedValue(aId); |
53 | 0 | if (!animatedValue) { |
54 | 0 | return omtaValue; |
55 | 0 | } |
56 | 0 | |
57 | 0 | switch (animatedValue->mType) { |
58 | 0 | case AnimatedValue::OPACITY: |
59 | 0 | omtaValue = animatedValue->mOpacity; |
60 | 0 | break; |
61 | 0 | case AnimatedValue::TRANSFORM: { |
62 | 0 | gfx::Matrix4x4 transform = animatedValue->mTransform.mFrameTransform; |
63 | 0 | const TransformData& data = animatedValue->mTransform.mData; |
64 | 0 | float scale = data.appUnitsPerDevPixel(); |
65 | 0 | gfx::Point3D transformOrigin = data.transformOrigin(); |
66 | 0 |
|
67 | 0 | // Undo the rebasing applied by |
68 | 0 | // nsDisplayTransform::GetResultingTransformMatrixInternal |
69 | 0 | transform.ChangeBasis(-transformOrigin); |
70 | 0 |
|
71 | 0 | // Convert to CSS pixels (this undoes the operations performed by |
72 | 0 | // nsStyleTransformMatrix::ProcessTranslatePart which is called from |
73 | 0 | // nsDisplayTransform::GetResultingTransformMatrix) |
74 | 0 | double devPerCss = |
75 | 0 | double(scale) / double(AppUnitsPerCSSPixel()); |
76 | 0 | transform._41 *= devPerCss; |
77 | 0 | transform._42 *= devPerCss; |
78 | 0 | transform._43 *= devPerCss; |
79 | 0 | omtaValue = transform; |
80 | 0 | break; |
81 | 0 | } |
82 | 0 | case AnimatedValue::NONE: |
83 | 0 | break; |
84 | 0 | } |
85 | 0 | |
86 | 0 | return omtaValue; |
87 | 0 | } |
88 | | |
89 | | void |
90 | | CompositorAnimationStorage::SetAnimatedValue(uint64_t aId, |
91 | | gfx::Matrix4x4&& aTransformInDevSpace, |
92 | | gfx::Matrix4x4&& aFrameTransform, |
93 | | const TransformData& aData) |
94 | 0 | { |
95 | 0 | MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); |
96 | 0 | auto count = mAnimatedValues.Count(); |
97 | 0 | AnimatedValue* value = mAnimatedValues.LookupOrAdd(aId, |
98 | 0 | std::move(aTransformInDevSpace), |
99 | 0 | std::move(aFrameTransform), |
100 | 0 | aData); |
101 | 0 | if (count == mAnimatedValues.Count()) { |
102 | 0 | MOZ_ASSERT(value->mType == AnimatedValue::TRANSFORM); |
103 | 0 | value->mTransform.mTransformInDevSpace = std::move(aTransformInDevSpace); |
104 | 0 | value->mTransform.mFrameTransform = std::move(aFrameTransform); |
105 | 0 | value->mTransform.mData = aData; |
106 | 0 | } |
107 | 0 | } |
108 | | |
109 | | void |
110 | | CompositorAnimationStorage::SetAnimatedValue(uint64_t aId, |
111 | | gfx::Matrix4x4&& aTransformInDevSpace) |
112 | 0 | { |
113 | 0 | MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); |
114 | 0 | const TransformData dontCare = {}; |
115 | 0 | SetAnimatedValue(aId, |
116 | 0 | std::move(aTransformInDevSpace), |
117 | 0 | gfx::Matrix4x4(), |
118 | 0 | dontCare); |
119 | 0 | } |
120 | | |
121 | | void |
122 | | CompositorAnimationStorage::SetAnimatedValue(uint64_t aId, |
123 | | const float& aOpacity) |
124 | 0 | { |
125 | 0 | MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); |
126 | 0 | auto count = mAnimatedValues.Count(); |
127 | 0 | AnimatedValue* value = mAnimatedValues.LookupOrAdd(aId, aOpacity); |
128 | 0 | if (count == mAnimatedValues.Count()) { |
129 | 0 | MOZ_ASSERT(value->mType == AnimatedValue::OPACITY); |
130 | 0 | value->mOpacity = aOpacity; |
131 | 0 | } |
132 | 0 | } |
133 | | |
134 | | AnimationArray* |
135 | | CompositorAnimationStorage::GetAnimations(const uint64_t& aId) const |
136 | 0 | { |
137 | 0 | MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); |
138 | 0 | return mAnimations.Get(aId); |
139 | 0 | } |
140 | | |
141 | | void |
142 | | CompositorAnimationStorage::SetAnimations(uint64_t aId, const AnimationArray& aValue) |
143 | 0 | { |
144 | 0 | MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); |
145 | 0 | AnimationArray* value = new AnimationArray(aValue); |
146 | 0 | mAnimations.Put(aId, value); |
147 | 0 | } |
148 | | |
149 | | |
150 | | AnimationHelper::SampleResult |
151 | | AnimationHelper::SampleAnimationForEachNode( |
152 | | TimeStamp aPreviousFrameTime, |
153 | | TimeStamp aCurrentFrameTime, |
154 | | AnimationArray& aAnimations, |
155 | | InfallibleTArray<AnimData>& aAnimationData, |
156 | | RefPtr<RawServoAnimationValue>& aAnimationValue, |
157 | | const AnimatedValue* aPreviousValue) |
158 | 0 | { |
159 | 0 | MOZ_ASSERT(!aAnimations.IsEmpty(), "Should be called with animations"); |
160 | 0 |
|
161 | 0 | bool hasInEffectAnimations = false; |
162 | | #ifdef DEBUG |
163 | | // In cases where this function returns a SampleResult::Skipped, we actually |
164 | | // do populate aAnimationValue in debug mode, so that we can MOZ_ASSERT at the |
165 | | // call site that the value that would have been computed matches the stored |
166 | | // value that we end up using. This flag is used to ensure we populate |
167 | | // aAnimationValue in this scenario. |
168 | | bool shouldBeSkipped = false; |
169 | | #endif |
170 | | // Process in order, since later aAnimations override earlier ones. |
171 | 0 | for (size_t i = 0, iEnd = aAnimations.Length(); i < iEnd; ++i) { |
172 | 0 | Animation& animation = aAnimations[i]; |
173 | 0 | AnimData& animData = aAnimationData[i]; |
174 | 0 |
|
175 | 0 | MOZ_ASSERT((!animation.originTime().IsNull() && |
176 | 0 | animation.startTime().type() == |
177 | 0 | MaybeTimeDuration::TTimeDuration) || |
178 | 0 | animation.isNotPlaying(), |
179 | 0 | "If we are playing, we should have an origin time and a start" |
180 | 0 | " time"); |
181 | 0 |
|
182 | 0 | // Determine if the animation was play-pending and used a ready time later |
183 | 0 | // than the previous frame time. |
184 | 0 | // |
185 | 0 | // To determine this, _all_ of the following consitions need to hold: |
186 | 0 | // |
187 | 0 | // * There was no previous animation value (i.e. this is the first frame for |
188 | 0 | // the animation since it was sent to the compositor), and |
189 | 0 | // * The animation is playing, and |
190 | 0 | // * There is a previous frame time, and |
191 | 0 | // * The ready time of the animation is ahead of the previous frame time. |
192 | 0 | // |
193 | 0 | bool hasFutureReadyTime = false; |
194 | 0 | if (!aPreviousValue && |
195 | 0 | !animation.isNotPlaying() && |
196 | 0 | !aPreviousFrameTime.IsNull()) { |
197 | 0 | // This is the inverse of the calculation performed in |
198 | 0 | // AnimationInfo::StartPendingAnimations to calculate the start time of |
199 | 0 | // play-pending animations. |
200 | 0 | // Note that we have to calculate (TimeStamp + TimeDuration) last to avoid |
201 | 0 | // underflow in the middle of the calulation. |
202 | 0 | const TimeStamp readyTime = |
203 | 0 | animation.originTime() + |
204 | 0 | (animation.startTime().get_TimeDuration() + |
205 | 0 | animation.holdTime().MultDouble(1.0 / animation.playbackRate())); |
206 | 0 | hasFutureReadyTime = |
207 | 0 | !readyTime.IsNull() && readyTime > aPreviousFrameTime; |
208 | 0 | } |
209 | 0 | // Use the previous vsync time to make main thread animations and compositor |
210 | 0 | // more closely aligned. |
211 | 0 | // |
212 | 0 | // On the first frame where we have animations the previous timestamp will |
213 | 0 | // not be set so we simply use the current timestamp. As a result we will |
214 | 0 | // end up painting the first frame twice. That doesn't appear to be |
215 | 0 | // noticeable, however. |
216 | 0 | // |
217 | 0 | // Likewise, if the animation is play-pending, it may have a ready time that |
218 | 0 | // is *after* |aPreviousFrameTime| (but *before* |aCurrentFrameTime|). |
219 | 0 | // To avoid flicker we need to use |aCurrentFrameTime| to avoid temporarily |
220 | 0 | // jumping backwards into the range prior to when the animation starts. |
221 | 0 | const TimeStamp& timeStamp = |
222 | 0 | aPreviousFrameTime.IsNull() || hasFutureReadyTime |
223 | 0 | ? aCurrentFrameTime |
224 | 0 | : aPreviousFrameTime; |
225 | 0 |
|
226 | 0 | // If the animation is not currently playing, e.g. paused or |
227 | 0 | // finished, then use the hold time to stay at the same position. |
228 | 0 | TimeDuration elapsedDuration = |
229 | 0 | animation.isNotPlaying() || |
230 | 0 | animation.startTime().type() != MaybeTimeDuration::TTimeDuration |
231 | 0 | ? animation.holdTime() |
232 | 0 | : (timeStamp - animation.originTime() - |
233 | 0 | animation.startTime().get_TimeDuration()) |
234 | 0 | .MultDouble(animation.playbackRate()); |
235 | 0 |
|
236 | 0 | ComputedTiming computedTiming = |
237 | 0 | dom::AnimationEffect::GetComputedTimingAt( |
238 | 0 | dom::Nullable<TimeDuration>(elapsedDuration), animData.mTiming, |
239 | 0 | animation.playbackRate()); |
240 | 0 |
|
241 | 0 | if (computedTiming.mProgress.IsNull()) { |
242 | 0 | continue; |
243 | 0 | } |
244 | 0 | |
245 | 0 | dom::IterationCompositeOperation iterCompositeOperation = |
246 | 0 | static_cast<dom::IterationCompositeOperation>( |
247 | 0 | animation.iterationComposite()); |
248 | 0 |
|
249 | 0 | // Skip caluculation if the progress hasn't changed since the last |
250 | 0 | // calculation. |
251 | 0 | // Note that we don't skip calculate this animation if there is another |
252 | 0 | // animation since the other animation might be 'accumulate' or 'add', or |
253 | 0 | // might have a missing keyframe (i.e. this animation value will be used in |
254 | 0 | // the missing keyframe). |
255 | 0 | // FIXME Bug 1455476: We should do this optimizations for the case where |
256 | 0 | // the layer has multiple animations. |
257 | 0 | if (iEnd == 1 && |
258 | 0 | !dom::KeyframeEffect::HasComputedTimingChanged( |
259 | 0 | computedTiming, |
260 | 0 | iterCompositeOperation, |
261 | 0 | animData.mProgressOnLastCompose, |
262 | 0 | animData.mCurrentIterationOnLastCompose)) { |
263 | | #ifdef DEBUG |
264 | | shouldBeSkipped = true; |
265 | | #else |
266 | | return SampleResult::Skipped; |
267 | 0 | #endif |
268 | 0 | } |
269 | 0 |
|
270 | 0 | uint32_t segmentIndex = 0; |
271 | 0 | size_t segmentSize = animation.segments().Length(); |
272 | 0 | AnimationSegment* segment = animation.segments().Elements(); |
273 | 0 | while (segment->endPortion() < computedTiming.mProgress.Value() && |
274 | 0 | segmentIndex < segmentSize - 1) { |
275 | 0 | ++segment; |
276 | 0 | ++segmentIndex; |
277 | 0 | } |
278 | 0 |
|
279 | 0 | double positionInSegment = |
280 | 0 | (computedTiming.mProgress.Value() - segment->startPortion()) / |
281 | 0 | (segment->endPortion() - segment->startPortion()); |
282 | 0 |
|
283 | 0 | double portion = |
284 | 0 | ComputedTimingFunction::GetPortion(animData.mFunctions[segmentIndex], |
285 | 0 | positionInSegment, |
286 | 0 | computedTiming.mBeforeFlag); |
287 | 0 |
|
288 | 0 | // Like above optimization, skip caluculation if the target segment isn't |
289 | 0 | // changed and if the portion in the segment isn't changed. |
290 | 0 | // This optimization is needed for CSS animations/transitions with step |
291 | 0 | // timing functions (e.g. the throbber animation on tab or frame based |
292 | 0 | // animations). |
293 | 0 | // FIXME Bug 1455476: Like the above optimization, we should apply this |
294 | 0 | // optimizations for multiple animation cases as well. |
295 | 0 | if (iEnd == 1 && |
296 | 0 | animData.mSegmentIndexOnLastCompose == segmentIndex && |
297 | 0 | !animData.mPortionInSegmentOnLastCompose.IsNull() && |
298 | 0 | animData.mPortionInSegmentOnLastCompose.Value() == portion) { |
299 | | #ifdef DEBUG |
300 | | shouldBeSkipped = true; |
301 | | #else |
302 | | return SampleResult::Skipped; |
303 | 0 | #endif |
304 | 0 | } |
305 | 0 |
|
306 | 0 | AnimationPropertySegment animSegment; |
307 | 0 | animSegment.mFromKey = 0.0; |
308 | 0 | animSegment.mToKey = 1.0; |
309 | 0 | animSegment.mFromValue = |
310 | 0 | AnimationValue(animData.mStartValues[segmentIndex]); |
311 | 0 | animSegment.mToValue = |
312 | 0 | AnimationValue(animData.mEndValues[segmentIndex]); |
313 | 0 | animSegment.mFromComposite = |
314 | 0 | static_cast<dom::CompositeOperation>(segment->startComposite()); |
315 | 0 | animSegment.mToComposite = |
316 | 0 | static_cast<dom::CompositeOperation>(segment->endComposite()); |
317 | 0 |
|
318 | 0 | // interpolate the property |
319 | 0 | aAnimationValue = |
320 | 0 | Servo_ComposeAnimationSegment( |
321 | 0 | &animSegment, |
322 | 0 | aAnimationValue, |
323 | 0 | animData.mEndValues.LastElement(), |
324 | 0 | iterCompositeOperation, |
325 | 0 | portion, |
326 | 0 | computedTiming.mCurrentIteration).Consume(); |
327 | 0 |
|
328 | | #ifdef DEBUG |
329 | | if (shouldBeSkipped) { |
330 | | return SampleResult::Skipped; |
331 | | } |
332 | | #endif |
333 | |
|
334 | 0 | hasInEffectAnimations = true; |
335 | 0 | animData.mProgressOnLastCompose = computedTiming.mProgress; |
336 | 0 | animData.mCurrentIterationOnLastCompose = computedTiming.mCurrentIteration; |
337 | 0 | animData.mSegmentIndexOnLastCompose = segmentIndex; |
338 | 0 | animData.mPortionInSegmentOnLastCompose.SetValue(portion); |
339 | 0 | } |
340 | 0 |
|
341 | | #ifdef DEBUG |
342 | | // Sanity check that all of animation data are the same. |
343 | | const AnimationData& lastData = aAnimations.LastElement().data(); |
344 | | for (const Animation& animation : aAnimations) { |
345 | | const AnimationData& data = animation.data(); |
346 | | MOZ_ASSERT(data.type() == lastData.type(), |
347 | | "The type of AnimationData should be the same"); |
348 | | if (data.type() == AnimationData::Tnull_t) { |
349 | | continue; |
350 | | } |
351 | | |
352 | | MOZ_ASSERT(data.type() == AnimationData::TTransformData); |
353 | | const TransformData& transformData = data.get_TransformData(); |
354 | | const TransformData& lastTransformData = lastData.get_TransformData(); |
355 | | MOZ_ASSERT(transformData.origin() == lastTransformData.origin() && |
356 | | transformData.transformOrigin() == |
357 | | lastTransformData.transformOrigin() && |
358 | | transformData.bounds() == lastTransformData.bounds() && |
359 | | transformData.appUnitsPerDevPixel() == |
360 | | lastTransformData.appUnitsPerDevPixel(), |
361 | | "All of members of TransformData should be the same"); |
362 | | } |
363 | | #endif |
364 | |
|
365 | 0 | return hasInEffectAnimations ? SampleResult::Sampled : SampleResult::None; |
366 | 0 | } |
367 | | |
368 | | struct BogusAnimation {}; |
369 | | |
370 | | static inline Result<Ok, BogusAnimation> |
371 | | SetCSSAngle(const CSSAngle& aAngle, nsCSSValue& aValue) |
372 | 0 | { |
373 | 0 | aValue.SetFloatValue(aAngle.value(), nsCSSUnit(aAngle.unit())); |
374 | 0 | if (!aValue.IsAngularUnit()) { |
375 | 0 | NS_ERROR("Bogus animation from IPC"); |
376 | 0 | return Err(BogusAnimation { }); |
377 | 0 | } |
378 | 0 | return Ok(); |
379 | 0 | } |
380 | | |
381 | | static Result<nsCSSValueSharedList*, BogusAnimation> |
382 | | CreateCSSValueList(const InfallibleTArray<TransformFunction>& aFunctions) |
383 | 0 | { |
384 | 0 | nsAutoPtr<nsCSSValueList> result; |
385 | 0 | nsCSSValueList** resultTail = getter_Transfers(result); |
386 | 0 | for (uint32_t i = 0; i < aFunctions.Length(); i++) { |
387 | 0 | RefPtr<nsCSSValue::Array> arr; |
388 | 0 | switch (aFunctions[i].type()) { |
389 | 0 | case TransformFunction::TRotationX: |
390 | 0 | { |
391 | 0 | const CSSAngle& angle = aFunctions[i].get_RotationX().angle(); |
392 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_rotatex, |
393 | 0 | resultTail); |
394 | 0 | MOZ_TRY(SetCSSAngle(angle, arr->Item(1))); |
395 | 0 | break; |
396 | 0 | } |
397 | 0 | case TransformFunction::TRotationY: |
398 | 0 | { |
399 | 0 | const CSSAngle& angle = aFunctions[i].get_RotationY().angle(); |
400 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_rotatey, |
401 | 0 | resultTail); |
402 | 0 | MOZ_TRY(SetCSSAngle(angle, arr->Item(1))); |
403 | 0 | break; |
404 | 0 | } |
405 | 0 | case TransformFunction::TRotationZ: |
406 | 0 | { |
407 | 0 | const CSSAngle& angle = aFunctions[i].get_RotationZ().angle(); |
408 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_rotatez, |
409 | 0 | resultTail); |
410 | 0 | MOZ_TRY(SetCSSAngle(angle, arr->Item(1))); |
411 | 0 | break; |
412 | 0 | } |
413 | 0 | case TransformFunction::TRotation: |
414 | 0 | { |
415 | 0 | const CSSAngle& angle = aFunctions[i].get_Rotation().angle(); |
416 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_rotate, |
417 | 0 | resultTail); |
418 | 0 | MOZ_TRY(SetCSSAngle(angle, arr->Item(1))); |
419 | 0 | break; |
420 | 0 | } |
421 | 0 | case TransformFunction::TRotation3D: |
422 | 0 | { |
423 | 0 | float x = aFunctions[i].get_Rotation3D().x(); |
424 | 0 | float y = aFunctions[i].get_Rotation3D().y(); |
425 | 0 | float z = aFunctions[i].get_Rotation3D().z(); |
426 | 0 | const CSSAngle& angle = aFunctions[i].get_Rotation3D().angle(); |
427 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_rotate3d, |
428 | 0 | resultTail); |
429 | 0 | arr->Item(1).SetFloatValue(x, eCSSUnit_Number); |
430 | 0 | arr->Item(2).SetFloatValue(y, eCSSUnit_Number); |
431 | 0 | arr->Item(3).SetFloatValue(z, eCSSUnit_Number); |
432 | 0 | MOZ_TRY(SetCSSAngle(angle, arr->Item(4))); |
433 | 0 | break; |
434 | 0 | } |
435 | 0 | case TransformFunction::TScale: |
436 | 0 | { |
437 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_scale3d, |
438 | 0 | resultTail); |
439 | 0 | arr->Item(1).SetFloatValue(aFunctions[i].get_Scale().x(), eCSSUnit_Number); |
440 | 0 | arr->Item(2).SetFloatValue(aFunctions[i].get_Scale().y(), eCSSUnit_Number); |
441 | 0 | arr->Item(3).SetFloatValue(aFunctions[i].get_Scale().z(), eCSSUnit_Number); |
442 | 0 | break; |
443 | 0 | } |
444 | 0 | case TransformFunction::TTranslation: |
445 | 0 | { |
446 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_translate3d, |
447 | 0 | resultTail); |
448 | 0 | arr->Item(1).SetFloatValue(aFunctions[i].get_Translation().x(), eCSSUnit_Pixel); |
449 | 0 | arr->Item(2).SetFloatValue(aFunctions[i].get_Translation().y(), eCSSUnit_Pixel); |
450 | 0 | arr->Item(3).SetFloatValue(aFunctions[i].get_Translation().z(), eCSSUnit_Pixel); |
451 | 0 | break; |
452 | 0 | } |
453 | 0 | case TransformFunction::TSkewX: |
454 | 0 | { |
455 | 0 | const CSSAngle& x = aFunctions[i].get_SkewX().x(); |
456 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_skewx, |
457 | 0 | resultTail); |
458 | 0 | MOZ_TRY(SetCSSAngle(x, arr->Item(1))); |
459 | 0 | break; |
460 | 0 | } |
461 | 0 | case TransformFunction::TSkewY: |
462 | 0 | { |
463 | 0 | const CSSAngle& y = aFunctions[i].get_SkewY().y(); |
464 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_skewy, |
465 | 0 | resultTail); |
466 | 0 | MOZ_TRY(SetCSSAngle(y, arr->Item(1))); |
467 | 0 | break; |
468 | 0 | } |
469 | 0 | case TransformFunction::TSkew: |
470 | 0 | { |
471 | 0 | const CSSAngle& x = aFunctions[i].get_Skew().x(); |
472 | 0 | const CSSAngle& y = aFunctions[i].get_Skew().y(); |
473 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_skew, |
474 | 0 | resultTail); |
475 | 0 | MOZ_TRY(SetCSSAngle(x, arr->Item(1))); |
476 | 0 | MOZ_TRY(SetCSSAngle(y, arr->Item(2))); |
477 | 0 | break; |
478 | 0 | } |
479 | 0 | case TransformFunction::TTransformMatrix: |
480 | 0 | { |
481 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_matrix3d, |
482 | 0 | resultTail); |
483 | 0 | const gfx::Matrix4x4& matrix = aFunctions[i].get_TransformMatrix().value(); |
484 | 0 | arr->Item(1).SetFloatValue(matrix._11, eCSSUnit_Number); |
485 | 0 | arr->Item(2).SetFloatValue(matrix._12, eCSSUnit_Number); |
486 | 0 | arr->Item(3).SetFloatValue(matrix._13, eCSSUnit_Number); |
487 | 0 | arr->Item(4).SetFloatValue(matrix._14, eCSSUnit_Number); |
488 | 0 | arr->Item(5).SetFloatValue(matrix._21, eCSSUnit_Number); |
489 | 0 | arr->Item(6).SetFloatValue(matrix._22, eCSSUnit_Number); |
490 | 0 | arr->Item(7).SetFloatValue(matrix._23, eCSSUnit_Number); |
491 | 0 | arr->Item(8).SetFloatValue(matrix._24, eCSSUnit_Number); |
492 | 0 | arr->Item(9).SetFloatValue(matrix._31, eCSSUnit_Number); |
493 | 0 | arr->Item(10).SetFloatValue(matrix._32, eCSSUnit_Number); |
494 | 0 | arr->Item(11).SetFloatValue(matrix._33, eCSSUnit_Number); |
495 | 0 | arr->Item(12).SetFloatValue(matrix._34, eCSSUnit_Number); |
496 | 0 | arr->Item(13).SetFloatValue(matrix._41, eCSSUnit_Number); |
497 | 0 | arr->Item(14).SetFloatValue(matrix._42, eCSSUnit_Number); |
498 | 0 | arr->Item(15).SetFloatValue(matrix._43, eCSSUnit_Number); |
499 | 0 | arr->Item(16).SetFloatValue(matrix._44, eCSSUnit_Number); |
500 | 0 | break; |
501 | 0 | } |
502 | 0 | case TransformFunction::TPerspective: |
503 | 0 | { |
504 | 0 | float perspective = aFunctions[i].get_Perspective().value(); |
505 | 0 | arr = AnimationValue::AppendTransformFunction(eCSSKeyword_perspective, |
506 | 0 | resultTail); |
507 | 0 | arr->Item(1).SetFloatValue(perspective, eCSSUnit_Pixel); |
508 | 0 | break; |
509 | 0 | } |
510 | 0 | default: |
511 | 0 | NS_ASSERTION(false, "All functions should be implemented?"); |
512 | 0 | } |
513 | 0 | } |
514 | 0 | if (aFunctions.Length() == 0) { |
515 | 0 | result = new nsCSSValueList(); |
516 | 0 | result->mValue.SetNoneValue(); |
517 | 0 | } |
518 | 0 | return new nsCSSValueSharedList(result.forget()); |
519 | 0 | } |
520 | | |
521 | | static already_AddRefed<RawServoAnimationValue> |
522 | | ToAnimationValue(const Animatable& aAnimatable) |
523 | 0 | { |
524 | 0 | RefPtr<RawServoAnimationValue> result; |
525 | 0 |
|
526 | 0 | switch (aAnimatable.type()) { |
527 | 0 | case Animatable::Tnull_t: |
528 | 0 | break; |
529 | 0 | case Animatable::TArrayOfTransformFunction: { |
530 | 0 | const InfallibleTArray<TransformFunction>& transforms = |
531 | 0 | aAnimatable.get_ArrayOfTransformFunction(); |
532 | 0 | auto listOrError = CreateCSSValueList(transforms); |
533 | 0 | if (listOrError.isOk()) { |
534 | 0 | RefPtr<nsCSSValueSharedList> list = listOrError.unwrap(); |
535 | 0 | MOZ_ASSERT(list, "Transform list should be non null"); |
536 | 0 | result = Servo_AnimationValue_Transform(*list).Consume(); |
537 | 0 | } |
538 | 0 | break; |
539 | 0 | } |
540 | 0 | case Animatable::Tfloat: |
541 | 0 | result = Servo_AnimationValue_Opacity(aAnimatable.get_float()).Consume(); |
542 | 0 | break; |
543 | 0 | default: |
544 | 0 | MOZ_ASSERT_UNREACHABLE("Unsupported type"); |
545 | 0 | } |
546 | 0 | return result.forget(); |
547 | 0 | } |
548 | | |
549 | | void |
550 | | AnimationHelper::SetAnimations( |
551 | | AnimationArray& aAnimations, |
552 | | InfallibleTArray<AnimData>& aAnimData, |
553 | | RefPtr<RawServoAnimationValue>& aBaseAnimationStyle) |
554 | 0 | { |
555 | 0 | for (uint32_t i = 0; i < aAnimations.Length(); i++) { |
556 | 0 | Animation& animation = aAnimations[i]; |
557 | 0 | // Adjust fill mode to fill forwards so that if the main thread is delayed |
558 | 0 | // in clearing this animation we don't introduce flicker by jumping back to |
559 | 0 | // the old underlying value |
560 | 0 | switch (static_cast<dom::FillMode>(animation.fillMode())) { |
561 | 0 | case dom::FillMode::None: |
562 | 0 | animation.fillMode() = static_cast<uint8_t>(dom::FillMode::Forwards); |
563 | 0 | break; |
564 | 0 | case dom::FillMode::Backwards: |
565 | 0 | animation.fillMode() = static_cast<uint8_t>(dom::FillMode::Both); |
566 | 0 | break; |
567 | 0 | default: |
568 | 0 | break; |
569 | 0 | } |
570 | 0 | |
571 | 0 | if (animation.baseStyle().type() != Animatable::Tnull_t) { |
572 | 0 | aBaseAnimationStyle = ToAnimationValue(animation.baseStyle()); |
573 | 0 | } |
574 | 0 |
|
575 | 0 | AnimData* data = aAnimData.AppendElement(); |
576 | 0 |
|
577 | 0 | data->mTiming = TimingParams { |
578 | 0 | animation.duration(), |
579 | 0 | animation.delay(), |
580 | 0 | animation.endDelay(), |
581 | 0 | animation.iterations(), |
582 | 0 | animation.iterationStart(), |
583 | 0 | static_cast<dom::PlaybackDirection>(animation.direction()), |
584 | 0 | static_cast<dom::FillMode>(animation.fillMode()), |
585 | 0 | AnimationUtils::TimingFunctionToComputedTimingFunction( |
586 | 0 | animation.easingFunction()) |
587 | 0 | }; |
588 | 0 | InfallibleTArray<Maybe<ComputedTimingFunction>>& functions = |
589 | 0 | data->mFunctions; |
590 | 0 | InfallibleTArray<RefPtr<RawServoAnimationValue>>& startValues = |
591 | 0 | data->mStartValues; |
592 | 0 | InfallibleTArray<RefPtr<RawServoAnimationValue>>& endValues = |
593 | 0 | data->mEndValues; |
594 | 0 |
|
595 | 0 | const InfallibleTArray<AnimationSegment>& segments = animation.segments(); |
596 | 0 | for (const AnimationSegment& segment : segments) { |
597 | 0 | startValues.AppendElement(ToAnimationValue(segment.startState())); |
598 | 0 | endValues.AppendElement(ToAnimationValue(segment.endState())); |
599 | 0 |
|
600 | 0 | TimingFunction tf = segment.sampleFn(); |
601 | 0 | Maybe<ComputedTimingFunction> ctf = |
602 | 0 | AnimationUtils::TimingFunctionToComputedTimingFunction(tf); |
603 | 0 | functions.AppendElement(ctf); |
604 | 0 | } |
605 | 0 | } |
606 | 0 | } |
607 | | |
608 | | uint64_t |
609 | | AnimationHelper::GetNextCompositorAnimationsId() |
610 | 0 | { |
611 | 0 | static uint32_t sNextId = 0; |
612 | 0 | ++sNextId; |
613 | 0 |
|
614 | 0 | uint32_t procId = static_cast<uint32_t>(base::GetCurrentProcId()); |
615 | 0 | uint64_t nextId = procId; |
616 | 0 | nextId = nextId << 32 | sNextId; |
617 | 0 | return nextId; |
618 | 0 | } |
619 | | |
620 | | bool |
621 | | AnimationHelper::SampleAnimations(CompositorAnimationStorage* aStorage, |
622 | | TimeStamp aPreviousFrameTime, |
623 | | TimeStamp aCurrentFrameTime) |
624 | 0 | { |
625 | 0 | MOZ_ASSERT(aStorage); |
626 | 0 | bool isAnimating = false; |
627 | 0 |
|
628 | 0 | // Do nothing if there are no compositor animations |
629 | 0 | if (!aStorage->AnimationsCount()) { |
630 | 0 | return isAnimating; |
631 | 0 | } |
632 | 0 | |
633 | 0 | //Sample the animations in CompositorAnimationStorage |
634 | 0 | for (auto iter = aStorage->ConstAnimationsTableIter(); |
635 | 0 | !iter.Done(); iter.Next()) { |
636 | 0 | AnimationArray* animations = iter.UserData(); |
637 | 0 | if (animations->IsEmpty()) { |
638 | 0 | continue; |
639 | 0 | } |
640 | 0 | |
641 | 0 | isAnimating = true; |
642 | 0 | RefPtr<RawServoAnimationValue> animationValue; |
643 | 0 | InfallibleTArray<AnimData> animationData; |
644 | 0 | AnimationHelper::SetAnimations(*animations, |
645 | 0 | animationData, |
646 | 0 | animationValue); |
647 | 0 | AnimatedValue* previousValue = aStorage->GetAnimatedValue(iter.Key()); |
648 | 0 | AnimationHelper::SampleResult sampleResult = |
649 | 0 | AnimationHelper::SampleAnimationForEachNode(aPreviousFrameTime, |
650 | 0 | aCurrentFrameTime, |
651 | 0 | *animations, |
652 | 0 | animationData, |
653 | 0 | animationValue, |
654 | 0 | previousValue); |
655 | 0 |
|
656 | 0 | if (sampleResult != AnimationHelper::SampleResult::Sampled) { |
657 | 0 | continue; |
658 | 0 | } |
659 | 0 | |
660 | 0 | // Store the AnimatedValue |
661 | 0 | Animation& animation = animations->LastElement(); |
662 | 0 | switch (animation.property()) { |
663 | 0 | case eCSSProperty_opacity: { |
664 | 0 | aStorage->SetAnimatedValue( |
665 | 0 | iter.Key(), |
666 | 0 | Servo_AnimationValue_GetOpacity(animationValue)); |
667 | 0 | break; |
668 | 0 | } |
669 | 0 | case eCSSProperty_transform: { |
670 | 0 | RefPtr<nsCSSValueSharedList> list; |
671 | 0 | Servo_AnimationValue_GetTransform(animationValue, &list); |
672 | 0 | const TransformData& transformData = animation.data().get_TransformData(); |
673 | 0 | nsPoint origin = transformData.origin(); |
674 | 0 | // we expect all our transform data to arrive in device pixels |
675 | 0 | gfx::Point3D transformOrigin = transformData.transformOrigin(); |
676 | 0 | nsDisplayTransform::FrameTransformProperties props(std::move(list), |
677 | 0 | transformOrigin); |
678 | 0 |
|
679 | 0 | gfx::Matrix4x4 transform = |
680 | 0 | nsDisplayTransform::GetResultingTransformMatrix(props, origin, |
681 | 0 | transformData.appUnitsPerDevPixel(), |
682 | 0 | 0, &transformData.bounds()); |
683 | 0 | gfx::Matrix4x4 frameTransform = transform; |
684 | 0 | // If the parent has perspective transform, then the offset into reference |
685 | 0 | // frame coordinates is already on this transform. If not, then we need to ask |
686 | 0 | // for it to be added here. |
687 | 0 | if (!transformData.hasPerspectiveParent()) { |
688 | 0 | nsLayoutUtils::PostTranslate(transform, origin, |
689 | 0 | transformData.appUnitsPerDevPixel(), |
690 | 0 | true); |
691 | 0 | } |
692 | 0 |
|
693 | 0 | transform.PostScale(transformData.inheritedXScale(), |
694 | 0 | transformData.inheritedYScale(), |
695 | 0 | 1); |
696 | 0 |
|
697 | 0 | aStorage->SetAnimatedValue(iter.Key(), |
698 | 0 | std::move(transform), std::move(frameTransform), |
699 | 0 | transformData); |
700 | 0 | break; |
701 | 0 | } |
702 | 0 | default: |
703 | 0 | MOZ_ASSERT_UNREACHABLE("Unhandled animated property"); |
704 | 0 | } |
705 | 0 | } |
706 | 0 |
|
707 | 0 | return isAnimating; |
708 | 0 | } |
709 | | |
710 | | } // namespace layers |
711 | | } // namespace mozilla |