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 "CompositorAnimationStorage.h"
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/OMTAController.h" // for OMTAController
15 #include "mozilla/ProfilerMarkers.h"
16 #include "mozilla/ScopeExit.h"
17 #include "mozilla/ServoStyleConsts.h"
18 #include "mozilla/webrender/WebRenderTypes.h" // for ToWrTransformProperty, etc
19 #include "nsDeviceContext.h" // for AppUnitsPerCSSPixel
20 #include "nsDisplayList.h" // for nsDisplayTransform, etc
21 #include "nsLayoutUtils.h"
22 #include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch
24 namespace geckoprofiler::markers
{
26 using namespace mozilla
;
28 struct CompositorAnimationMarker
{
29 static constexpr Span
<const char> MarkerTypeName() {
30 return MakeStringSpan("CompositorAnimation");
32 static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter
& aWriter
,
33 uint64_t aId
, nsCSSPropertyID aProperty
) {
34 aWriter
.IntProperty("pid", int64_t(aId
>> 32));
35 aWriter
.IntProperty("id", int64_t(aId
& 0xffffffff));
36 aWriter
.StringProperty("property", nsCSSProps::GetStringValue(aProperty
));
38 static MarkerSchema
MarkerTypeDisplay() {
39 using MS
= MarkerSchema
;
40 MS schema
{MS::Location::MarkerChart
, MS::Location::MarkerTable
};
41 schema
.AddKeyLabelFormat("pid", "Process Id", MS::Format::Integer
);
42 schema
.AddKeyLabelFormat("id", "Animation Id", MS::Format::Integer
);
43 schema
.AddKeyLabelFormat("property", "Animated Property",
45 schema
.SetTableLabel("{marker.name} - {marker.data.property}");
50 } // namespace geckoprofiler::markers
57 void CompositorAnimationStorage::ClearById(const uint64_t& aId
) {
58 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
59 MutexAutoLock
lock(mLock
);
61 const auto& animationStorageData
= mAnimations
[aId
];
62 if (animationStorageData
) {
63 PROFILER_MARKER("ClearAnimation", GRAPHICS
,
64 MarkerInnerWindowId(mCompositorBridge
->GetInnerWindowId()),
65 CompositorAnimationMarker
, aId
,
66 animationStorageData
->mAnimation
.LastElement().mProperty
);
69 mAnimatedValues
.Remove(aId
);
70 mAnimations
.erase(aId
);
73 bool CompositorAnimationStorage::HasAnimations() const {
74 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
75 MutexAutoLock
lock(mLock
);
77 return !mAnimations
.empty();
80 AnimatedValue
* CompositorAnimationStorage::GetAnimatedValue(
81 const uint64_t& aId
) const {
82 mLock
.AssertCurrentThreadOwns();
84 return mAnimatedValues
.Get(aId
);
87 OMTAValue
CompositorAnimationStorage::GetOMTAValue(const uint64_t& aId
) const {
88 MutexAutoLock
lock(mLock
);
90 OMTAValue omtaValue
= mozilla::null_t();
91 auto animatedValue
= GetAnimatedValue(aId
);
96 animatedValue
->Value().match(
97 [&](const AnimationTransform
& aTransform
) {
98 gfx::Matrix4x4 transform
= aTransform
.mFrameTransform
;
99 const TransformData
& data
= aTransform
.mData
;
100 float scale
= data
.appUnitsPerDevPixel();
101 gfx::Point3D transformOrigin
= data
.transformOrigin();
103 // Undo the rebasing applied by
104 // nsDisplayTransform::GetResultingTransformMatrixInternal
105 transform
.ChangeBasis(-transformOrigin
);
107 // Convert to CSS pixels (this undoes the operations performed by
108 // nsStyleTransformMatrix::ProcessTranslatePart which is called from
109 // nsDisplayTransform::GetResultingTransformMatrix)
110 double devPerCss
= double(scale
) / double(AppUnitsPerCSSPixel());
111 transform
._41
*= devPerCss
;
112 transform
._42
*= devPerCss
;
113 transform
._43
*= devPerCss
;
114 omtaValue
= transform
;
116 [&](const float& aOpacity
) { omtaValue
= aOpacity
; },
117 [&](const nscolor
& aColor
) { omtaValue
= aColor
; });
121 void CompositorAnimationStorage::SetAnimatedValue(
122 uint64_t aId
, AnimatedValue
* aPreviousValue
,
123 const gfx::Matrix4x4
& aFrameTransform
, const TransformData
& aData
) {
124 mLock
.AssertCurrentThreadOwns();
126 if (!aPreviousValue
) {
127 MOZ_ASSERT(!mAnimatedValues
.Contains(aId
));
128 mAnimatedValues
.InsertOrUpdate(
130 MakeUnique
<AnimatedValue
>(gfx::Matrix4x4(), aFrameTransform
, aData
));
133 MOZ_ASSERT(aPreviousValue
->Is
<AnimationTransform
>());
134 MOZ_ASSERT(aPreviousValue
== GetAnimatedValue(aId
));
136 aPreviousValue
->SetTransform(aFrameTransform
, aData
);
139 void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId
,
140 AnimatedValue
* aPreviousValue
,
142 mLock
.AssertCurrentThreadOwns();
144 if (!aPreviousValue
) {
145 MOZ_ASSERT(!mAnimatedValues
.Contains(aId
));
146 mAnimatedValues
.InsertOrUpdate(aId
, MakeUnique
<AnimatedValue
>(aColor
));
150 MOZ_ASSERT(aPreviousValue
->Is
<nscolor
>());
151 MOZ_ASSERT(aPreviousValue
== GetAnimatedValue(aId
));
152 aPreviousValue
->SetColor(aColor
);
155 void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId
,
156 AnimatedValue
* aPreviousValue
,
158 mLock
.AssertCurrentThreadOwns();
160 if (!aPreviousValue
) {
161 MOZ_ASSERT(!mAnimatedValues
.Contains(aId
));
162 mAnimatedValues
.InsertOrUpdate(aId
, MakeUnique
<AnimatedValue
>(aOpacity
));
166 MOZ_ASSERT(aPreviousValue
->Is
<float>());
167 MOZ_ASSERT(aPreviousValue
== GetAnimatedValue(aId
));
168 aPreviousValue
->SetOpacity(aOpacity
);
171 void CompositorAnimationStorage::SetAnimations(uint64_t aId
,
172 const LayersId
& aLayersId
,
173 const AnimationArray
& aValue
) {
174 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
175 MutexAutoLock
lock(mLock
);
177 mAnimations
[aId
] = std::make_unique
<AnimationStorageData
>(
178 AnimationHelper::ExtractAnimations(aLayersId
, aValue
));
180 PROFILER_MARKER("SetAnimation", GRAPHICS
,
181 MarkerInnerWindowId(mCompositorBridge
->GetInnerWindowId()),
182 CompositorAnimationMarker
, aId
,
183 mAnimations
[aId
]->mAnimation
.LastElement().mProperty
);
185 // If there is the last animated value, then we need to store the id to remove
186 // the value if the new animation doesn't produce any animated data (i.e. in
187 // the delay phase) when we sample this new animation.
188 if (mAnimatedValues
.Contains(aId
)) {
189 mNewAnimations
.insert(aId
);
193 // Returns clip rect in the scroll frame's coordinate space.
194 static ParentLayerRect
GetClipRectForPartialPrerender(
195 const LayersId aLayersId
, const PartialPrerenderData
& aPartialPrerenderData
,
196 const RefPtr
<APZSampler
>& aSampler
, const MutexAutoLock
& aProofOfMapLock
) {
198 aPartialPrerenderData
.scrollId() != ScrollableLayerGuid::NULL_SCROLL_ID
) {
199 return aSampler
->GetCompositionBounds(
200 aLayersId
, aPartialPrerenderData
.scrollId(), aProofOfMapLock
);
203 return aPartialPrerenderData
.clipRect();
206 void CompositorAnimationStorage::StoreAnimatedValue(
207 nsCSSPropertyID aProperty
, uint64_t aId
,
208 const std::unique_ptr
<AnimationStorageData
>& aAnimationStorageData
,
209 const AutoTArray
<RefPtr
<StyleAnimationValue
>, 1>& aAnimationValues
,
210 const MutexAutoLock
& aProofOfMapLock
, const RefPtr
<APZSampler
>& aApzSampler
,
211 AnimatedValue
* aAnimatedValueEntry
,
212 JankedAnimationMap
& aJankedAnimationMap
) {
214 case eCSSProperty_background_color
: {
215 SetAnimatedValue(aId
, aAnimatedValueEntry
,
216 Servo_AnimationValue_GetColor(aAnimationValues
[0],
217 NS_RGBA(0, 0, 0, 0)));
220 case eCSSProperty_opacity
: {
221 MOZ_ASSERT(aAnimationValues
.Length() == 1);
222 SetAnimatedValue(aId
, aAnimatedValueEntry
,
223 Servo_AnimationValue_GetOpacity(aAnimationValues
[0]));
226 case eCSSProperty_rotate
:
227 case eCSSProperty_scale
:
228 case eCSSProperty_translate
:
229 case eCSSProperty_transform
:
230 case eCSSProperty_offset_path
:
231 case eCSSProperty_offset_distance
:
232 case eCSSProperty_offset_rotate
:
233 case eCSSProperty_offset_anchor
:
234 case eCSSProperty_offset_position
: {
235 MOZ_ASSERT(aAnimationStorageData
->mTransformData
);
237 const TransformData
& transformData
=
238 *aAnimationStorageData
->mTransformData
;
239 MOZ_ASSERT(transformData
.origin() == nsPoint());
241 gfx::Matrix4x4 frameTransform
=
242 AnimationHelper::ServoAnimationValueToMatrix4x4(
243 aAnimationValues
, transformData
,
244 aAnimationStorageData
->mCachedMotionPath
);
246 if (const Maybe
<PartialPrerenderData
>& partialPrerenderData
=
247 transformData
.partialPrerenderData()) {
248 gfx::Matrix4x4 transform
= frameTransform
;
249 transform
.PostTranslate(
250 partialPrerenderData
->position().ToUnknownPoint());
252 gfx::Matrix4x4 transformInClip
=
253 partialPrerenderData
->transformInClip();
254 if (aApzSampler
&& partialPrerenderData
->scrollId() !=
255 ScrollableLayerGuid::NULL_SCROLL_ID
) {
256 AsyncTransform asyncTransform
= aApzSampler
->GetCurrentAsyncTransform(
257 aAnimationStorageData
->mLayersId
,
258 partialPrerenderData
->scrollId(), LayoutAndVisual
,
260 transformInClip
.PostTranslate(
261 asyncTransform
.mTranslation
.ToUnknownPoint());
263 transformInClip
= transform
* transformInClip
;
265 ParentLayerRect clipRect
= GetClipRectForPartialPrerender(
266 aAnimationStorageData
->mLayersId
, *partialPrerenderData
,
267 aApzSampler
, aProofOfMapLock
);
268 if (AnimationHelper::ShouldBeJank(
269 partialPrerenderData
->rect(),
270 partialPrerenderData
->overflowedSides(), transformInClip
,
272 if (aAnimatedValueEntry
) {
273 frameTransform
= aAnimatedValueEntry
->Transform().mFrameTransform
;
275 aJankedAnimationMap
[aAnimationStorageData
->mLayersId
].AppendElement(
280 SetAnimatedValue(aId
, aAnimatedValueEntry
, frameTransform
, transformData
);
284 MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
288 bool CompositorAnimationStorage::SampleAnimations(
289 const OMTAController
* aOMTAController
, TimeStamp aPreviousFrameTime
,
290 TimeStamp aCurrentFrameTime
) {
291 MutexAutoLock
lock(mLock
);
293 bool isAnimating
= false;
294 auto cleanup
= MakeScopeExit([&] { mNewAnimations
.clear(); });
296 // Do nothing if there are no compositor animations
297 if (mAnimations
.empty()) {
301 JankedAnimationMap janked
;
302 RefPtr
<APZSampler
> apzSampler
= mCompositorBridge
->GetAPZSampler();
304 auto callback
= [&](const MutexAutoLock
& aProofOfMapLock
) {
305 for (const auto& iter
: mAnimations
) {
306 const auto& animationStorageData
= iter
.second
;
307 if (animationStorageData
->mAnimation
.IsEmpty()) {
311 const nsCSSPropertyID lastPropertyAnimationGroupProperty
=
312 animationStorageData
->mAnimation
.LastElement().mProperty
;
314 AutoTArray
<RefPtr
<StyleAnimationValue
>, 1> animationValues
;
315 AnimatedValue
* previousValue
= GetAnimatedValue(iter
.first
);
316 AnimationHelper::SampleResult sampleResult
=
317 AnimationHelper::SampleAnimationForEachNode(
318 apzSampler
, animationStorageData
->mLayersId
, aProofOfMapLock
,
319 aPreviousFrameTime
, aCurrentFrameTime
, previousValue
,
320 animationStorageData
->mAnimation
, animationValues
);
323 "SampleAnimation", GRAPHICS
,
325 MarkerThreadId(CompositorThreadHolder::GetThreadId()),
326 MarkerInnerWindowId(mCompositorBridge
->GetInnerWindowId())),
327 CompositorAnimationMarker
, iter
.first
,
328 lastPropertyAnimationGroupProperty
);
330 if (!sampleResult
.IsSampled()) {
331 // Note: Checking new animations first. If new animations arrive and we
332 // scroll back to delay phase in the meantime for scroll-driven
333 // animations, removing the previous animated value is still the
334 // preferable way because the newly animation case would probably more
335 // often than the scroll timeline. Besides, we expect the display items
336 // get sync with main thread at this moment, so dropping the old
337 // animation sampled result is more suitable.
338 // FIXME: Bug 1791884: We might have to revisit this to make sure we
339 // respect animation composition order.
340 if (mNewAnimations
.find(iter
.first
) != mNewAnimations
.end()) {
341 mAnimatedValues
.Remove(iter
.first
);
342 } else if (sampleResult
.mReason
==
343 AnimationHelper::SampleResult::Reason::ScrollToDelayPhase
) {
344 // For the scroll-driven animations, its animation effect phases may
345 // be changed between the active phase and the before/after phase.
346 // Basically we don't produce any sampled animation value for
347 // before/after phase (if we don't have fills). In this case, we have
348 // to make sure the animations are not applied on the compositor.
349 // Removing the previous animated value is not enough because the
350 // display item in webrender may be out-of-date. Therefore, we should
351 // not just skip these animation values. Instead, storing their base
352 // styles (which are in |animationValues| already) to simulate these
353 // delayed animations.
355 // There are two possible situations:
356 // 1. If there is just a new animation arrived but there is no sampled
357 // animation value when we go from active phase, we remove the
358 // previous AnimatedValue. This is done in the above condition.
359 // 2. If |animation| is not replaced by a new arrived one, we detect
360 // it in SampleAnimationForProperty(), which sets
361 // ScrollToDelayPhase if it goes from the active phase to the
362 // before/after phase.
364 // For the 2nd case, we store the base styles until we have some other
365 // new sampled results or the new animations arrived (i.e. case 1).
366 StoreAnimatedValue(lastPropertyAnimationGroupProperty
, iter
.first
,
367 animationStorageData
, animationValues
,
368 aProofOfMapLock
, apzSampler
, previousValue
,
374 // Store the normal sampled result.
375 StoreAnimatedValue(lastPropertyAnimationGroupProperty
, iter
.first
,
376 animationStorageData
, animationValues
, aProofOfMapLock
,
377 apzSampler
, previousValue
, janked
);
382 // Hold APZCTreeManager::mMapLock for all the animations inside this
383 // CompositorBridgeParent. This ensures that APZ cannot process a
384 // transaction on the updater thread in between sampling different
386 apzSampler
->CallWithMapLock(callback
);
388 // A fallback way if we don't have |apzSampler|. We don't care about
389 // APZCTreeManager::mMapLock in this case because we don't use any APZ
391 mozilla::Mutex
dummy("DummyAPZMapLock");
392 MutexAutoLock
lock(dummy
);
396 if (!janked
.empty() && aOMTAController
) {
397 aOMTAController
->NotifyJankedAnimations(std::move(janked
));
403 WrAnimations
CompositorAnimationStorage::CollectWebRenderAnimations() const {
404 MutexAutoLock
lock(mLock
);
406 WrAnimations animations
;
408 for (const auto& animatedValueEntry
: mAnimatedValues
) {
409 AnimatedValue
* value
= animatedValueEntry
.GetWeak();
410 value
->Value().match(
411 [&](const AnimationTransform
& aTransform
) {
412 animations
.mTransformArrays
.AppendElement(wr::ToWrTransformProperty(
413 animatedValueEntry
.GetKey(), aTransform
.mFrameTransform
));
415 [&](const float& aOpacity
) {
416 animations
.mOpacityArrays
.AppendElement(
417 wr::ToWrOpacityProperty(animatedValueEntry
.GetKey(), aOpacity
));
419 [&](const nscolor
& aColor
) {
420 animations
.mColorArrays
.AppendElement(wr::ToWrColorProperty(
421 animatedValueEntry
.GetKey(),
422 ToDeviceColor(gfx::sRGBColor::FromABGR(aColor
))));
429 } // namespace layers
430 } // namespace mozilla