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 bool CompositorAnimationStorage::SampleAnimations(
207 const OMTAController
* aOMTAController
, TimeStamp aPreviousFrameTime
,
208 TimeStamp aCurrentFrameTime
) {
209 MutexAutoLock
lock(mLock
);
211 bool isAnimating
= false;
212 auto cleanup
= MakeScopeExit([&] { mNewAnimations
.clear(); });
214 // Do nothing if there are no compositor animations
215 if (mAnimations
.empty()) {
219 std::unordered_map
<LayersId
, nsTArray
<uint64_t>, LayersId::HashFn
> janked
;
221 RefPtr
<APZSampler
> apzSampler
= mCompositorBridge
->GetAPZSampler();
223 auto callback
= [&](const MutexAutoLock
& aProofOfMapLock
) {
224 for (const auto& iter
: mAnimations
) {
225 const auto& animationStorageData
= iter
.second
;
226 if (animationStorageData
->mAnimation
.IsEmpty()) {
231 AutoTArray
<RefPtr
<RawServoAnimationValue
>, 1> animationValues
;
232 AnimatedValue
* previousValue
= GetAnimatedValue(iter
.first
);
233 AnimationHelper::SampleResult sampleResult
=
234 AnimationHelper::SampleAnimationForEachNode(
235 apzSampler
, animationStorageData
->mLayersId
, aProofOfMapLock
,
236 aPreviousFrameTime
, aCurrentFrameTime
, previousValue
,
237 animationStorageData
->mAnimation
, animationValues
);
239 if (sampleResult
!= AnimationHelper::SampleResult::Sampled
) {
240 if (mNewAnimations
.find(iter
.first
) != mNewAnimations
.end()) {
241 mAnimatedValues
.Remove(iter
.first
);
246 const PropertyAnimationGroup
& lastPropertyAnimationGroup
=
247 animationStorageData
->mAnimation
.LastElement();
250 "SampleAnimation", GRAPHICS
,
252 MarkerThreadId(CompositorThreadHolder::GetThreadId()),
253 MarkerInnerWindowId(mCompositorBridge
->GetInnerWindowId())),
254 CompositorAnimationMarker
, iter
.first
,
255 lastPropertyAnimationGroup
.mProperty
);
257 // Store the AnimatedValue
258 switch (lastPropertyAnimationGroup
.mProperty
) {
259 case eCSSProperty_background_color
: {
260 SetAnimatedValue(iter
.first
, previousValue
,
261 Servo_AnimationValue_GetColor(animationValues
[0],
262 NS_RGBA(0, 0, 0, 0)));
265 case eCSSProperty_opacity
: {
266 MOZ_ASSERT(animationValues
.Length() == 1);
267 SetAnimatedValue(iter
.first
, previousValue
,
268 Servo_AnimationValue_GetOpacity(animationValues
[0]));
271 case eCSSProperty_rotate
:
272 case eCSSProperty_scale
:
273 case eCSSProperty_translate
:
274 case eCSSProperty_transform
:
275 case eCSSProperty_offset_path
:
276 case eCSSProperty_offset_distance
:
277 case eCSSProperty_offset_rotate
:
278 case eCSSProperty_offset_anchor
: {
279 MOZ_ASSERT(animationStorageData
->mTransformData
);
281 const TransformData
& transformData
=
282 *animationStorageData
->mTransformData
;
283 MOZ_ASSERT(transformData
.origin() == nsPoint());
285 gfx::Matrix4x4 frameTransform
=
286 AnimationHelper::ServoAnimationValueToMatrix4x4(
287 animationValues
, transformData
,
288 animationStorageData
->mCachedMotionPath
);
290 if (const Maybe
<PartialPrerenderData
>& partialPrerenderData
=
291 transformData
.partialPrerenderData()) {
292 gfx::Matrix4x4 transform
= frameTransform
;
293 transform
.PostTranslate(
294 partialPrerenderData
->position().ToUnknownPoint());
296 gfx::Matrix4x4 transformInClip
=
297 partialPrerenderData
->transformInClip();
298 if (apzSampler
&& partialPrerenderData
->scrollId() !=
299 ScrollableLayerGuid::NULL_SCROLL_ID
) {
300 AsyncTransform asyncTransform
=
301 apzSampler
->GetCurrentAsyncTransform(
302 animationStorageData
->mLayersId
,
303 partialPrerenderData
->scrollId(), LayoutAndVisual
,
305 transformInClip
.PostTranslate(
306 asyncTransform
.mTranslation
.ToUnknownPoint());
308 transformInClip
= transform
* transformInClip
;
310 ParentLayerRect clipRect
= GetClipRectForPartialPrerender(
311 animationStorageData
->mLayersId
, *partialPrerenderData
,
312 apzSampler
, aProofOfMapLock
);
313 if (AnimationHelper::ShouldBeJank(
314 partialPrerenderData
->rect(),
315 partialPrerenderData
->overflowedSides(), transformInClip
,
318 frameTransform
= previousValue
->Transform().mFrameTransform
;
320 janked
[animationStorageData
->mLayersId
].AppendElement(iter
.first
);
324 SetAnimatedValue(iter
.first
, previousValue
, frameTransform
,
329 MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
335 // Hold APZCTreeManager::mMapLock for all the animations inside this
336 // CompositorBridgeParent. This ensures that APZ cannot process a
337 // transaction on the updater thread in between sampling different
339 apzSampler
->CallWithMapLock(callback
);
341 // A fallback way if we don't have |apzSampler|. We don't care about
342 // APZCTreeManager::mMapLock in this case because we don't use any APZ
344 mozilla::Mutex
dummy("DummyAPZMapLock");
345 MutexAutoLock
lock(dummy
);
349 if (!janked
.empty() && aOMTAController
) {
350 aOMTAController
->NotifyJankedAnimations(std::move(janked
));
356 WrAnimations
CompositorAnimationStorage::CollectWebRenderAnimations() const {
357 MutexAutoLock
lock(mLock
);
359 WrAnimations animations
;
361 for (const auto& animatedValueEntry
: mAnimatedValues
) {
362 AnimatedValue
* value
= animatedValueEntry
.GetWeak();
363 value
->Value().match(
364 [&](const AnimationTransform
& aTransform
) {
365 animations
.mTransformArrays
.AppendElement(wr::ToWrTransformProperty(
366 animatedValueEntry
.GetKey(), aTransform
.mFrameTransform
));
368 [&](const float& aOpacity
) {
369 animations
.mOpacityArrays
.AppendElement(
370 wr::ToWrOpacityProperty(animatedValueEntry
.GetKey(), aOpacity
));
372 [&](const nscolor
& aColor
) {
373 animations
.mColorArrays
.AppendElement(wr::ToWrColorProperty(
374 animatedValueEntry
.GetKey(),
375 ToDeviceColor(gfx::sRGBColor::FromABGR(aColor
))));
382 } // namespace layers
383 } // namespace mozilla