Bug 1839315: part 4) Link from `SheetLoadData::mWasAlternate` to spec. r=emilio DONTBUILD
[gecko.git] / gfx / layers / CompositorAnimationStorage.cpp
blobfeb4259630529b29574a4843097cc0c180b3f497
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",
44 MS::Format::String);
45 schema.SetTableLabel("{marker.name} - {marker.data.property}");
46 return schema;
50 } // namespace geckoprofiler::markers
52 namespace mozilla {
53 namespace layers {
55 using gfx::Matrix4x4;
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);
92 if (!animatedValue) {
93 return omtaValue;
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; });
118 return omtaValue;
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(
129 aId,
130 MakeUnique<AnimatedValue>(gfx::Matrix4x4(), aFrameTransform, aData));
131 return;
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,
141 nscolor aColor) {
142 mLock.AssertCurrentThreadOwns();
144 if (!aPreviousValue) {
145 MOZ_ASSERT(!mAnimatedValues.Contains(aId));
146 mAnimatedValues.InsertOrUpdate(aId, MakeUnique<AnimatedValue>(aColor));
147 return;
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,
157 float aOpacity) {
158 mLock.AssertCurrentThreadOwns();
160 if (!aPreviousValue) {
161 MOZ_ASSERT(!mAnimatedValues.Contains(aId));
162 mAnimatedValues.InsertOrUpdate(aId, MakeUnique<AnimatedValue>(aOpacity));
163 return;
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) {
197 if (aSampler &&
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) {
213 switch (aProperty) {
214 case eCSSProperty_background_color: {
215 SetAnimatedValue(aId, aAnimatedValueEntry,
216 Servo_AnimationValue_GetColor(aAnimationValues[0],
217 NS_RGBA(0, 0, 0, 0)));
218 break;
220 case eCSSProperty_opacity: {
221 MOZ_ASSERT(aAnimationValues.Length() == 1);
222 SetAnimatedValue(aId, aAnimatedValueEntry,
223 Servo_AnimationValue_GetOpacity(aAnimationValues[0]));
224 break;
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,
259 aProofOfMapLock);
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,
271 clipRect)) {
272 if (aAnimatedValueEntry) {
273 frameTransform = aAnimatedValueEntry->Transform().mFrameTransform;
275 aJankedAnimationMap[aAnimationStorageData->mLayersId].AppendElement(
276 aId);
280 SetAnimatedValue(aId, aAnimatedValueEntry, frameTransform, transformData);
281 break;
283 default:
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()) {
298 return isAnimating;
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()) {
308 continue;
311 const nsCSSPropertyID lastPropertyAnimationGroupProperty =
312 animationStorageData->mAnimation.LastElement().mProperty;
313 isAnimating = true;
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);
322 PROFILER_MARKER(
323 "SampleAnimation", GRAPHICS,
324 MarkerOptions(
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,
369 janked);
371 continue;
374 // Store the normal sampled result.
375 StoreAnimatedValue(lastPropertyAnimationGroupProperty, iter.first,
376 animationStorageData, animationValues, aProofOfMapLock,
377 apzSampler, previousValue, janked);
381 if (apzSampler) {
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
385 // animations.
386 apzSampler->CallWithMapLock(callback);
387 } else {
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
390 // interface.
391 mozilla::Mutex dummy("DummyAPZMapLock");
392 MutexAutoLock lock(dummy);
393 callback(lock);
396 if (!janked.empty() && aOMTAController) {
397 aOMTAController->NotifyJankedAnimations(std::move(janked));
400 return isAnimating;
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))));
426 return animations;
429 } // namespace layers
430 } // namespace mozilla