CompositorAnimationStorage.cpp (18424B)
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/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 23 24 namespace geckoprofiler::markers { 25 26 using namespace mozilla; 27 28 struct CompositorAnimationMarker { 29 static constexpr Span<const char> MarkerTypeName() { 30 return MakeStringSpan("CompositorAnimation"); 31 } 32 static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, 33 uint64_t aId, 34 NonCustomCSSPropertyId aProperty) { 35 aWriter.IntProperty("pid", int64_t(aId >> 32)); 36 aWriter.IntProperty("id", int64_t(aId & 0xffffffff)); 37 aWriter.StringProperty("property", nsCSSProps::GetStringValue(aProperty)); 38 } 39 static MarkerSchema MarkerTypeDisplay() { 40 using MS = MarkerSchema; 41 MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; 42 schema.AddKeyLabelFormat("pid", "Process Id", MS::Format::Integer); 43 schema.AddKeyLabelFormat("id", "Animation Id", MS::Format::Integer); 44 schema.AddKeyLabelFormat("property", "Animated Property", 45 MS::Format::String); 46 schema.SetTableLabel("{marker.data.property}"); 47 return schema; 48 } 49 }; 50 51 } // namespace geckoprofiler::markers 52 53 namespace mozilla { 54 namespace layers { 55 56 using gfx::Matrix4x4; 57 58 already_AddRefed<StyleAnimationValue> AnimatedValue::AsAnimationValue( 59 NonCustomCSSPropertyId aProperty) const { 60 RefPtr<StyleAnimationValue> result; 61 mValue.match( 62 [&](const AnimationTransform& aTransform) { 63 // Linear search. It's likely that the length of the array is one in 64 // most common case, so it shouldn't have much performance impact. 65 for (const auto& value : Transform().mAnimationValues) { 66 CSSPropertyId property(eCSSProperty_UNKNOWN); 67 Servo_AnimationValue_GetPropertyId(value, &property); 68 if (property.mId == aProperty) { 69 result = value; 70 break; 71 } 72 } 73 }, 74 [&](const float& aOpacity) { 75 result = Servo_AnimationValue_Opacity(aOpacity).Consume(); 76 }, 77 [&](const nscolor& aColor) { 78 result = Servo_AnimationValue_Color(aProperty, aColor).Consume(); 79 }); 80 return result.forget(); 81 } 82 83 void CompositorAnimationStorage::ClearById(const uint64_t& aId) { 84 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 85 MutexAutoLock lock(mLock); 86 87 const auto& animationStorageData = mAnimations[aId]; 88 if (animationStorageData) { 89 PROFILER_MARKER("ClearAnimation", GRAPHICS, 90 MarkerInnerWindowId(mCompositorBridge->GetInnerWindowId()), 91 CompositorAnimationMarker, aId, 92 animationStorageData->mAnimation.LastElement().mProperty); 93 } 94 95 mAnimatedValues.Remove(aId); 96 mAnimations.erase(aId); 97 } 98 99 bool CompositorAnimationStorage::HasAnimations() const { 100 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 101 MutexAutoLock lock(mLock); 102 103 return !mAnimations.empty(); 104 } 105 106 AnimatedValue* CompositorAnimationStorage::GetAnimatedValue( 107 const uint64_t& aId) const { 108 mLock.AssertCurrentThreadOwns(); 109 110 return mAnimatedValues.Get(aId); 111 } 112 113 OMTAValue CompositorAnimationStorage::GetOMTAValue(const uint64_t& aId) const { 114 MutexAutoLock lock(mLock); 115 116 OMTAValue omtaValue = mozilla::null_t(); 117 auto animatedValue = GetAnimatedValue(aId); 118 if (!animatedValue) { 119 return omtaValue; 120 } 121 122 animatedValue->Value().match( 123 [&](const AnimationTransform& aTransform) { 124 gfx::Matrix4x4 transform = aTransform.mFrameTransform; 125 const TransformData& data = aTransform.mData; 126 float scale = data.appUnitsPerDevPixel(); 127 gfx::Point3D transformOrigin = data.transformOrigin(); 128 129 // Undo the rebasing applied by 130 // nsDisplayTransform::GetResultingTransformMatrixInternal 131 transform.ChangeBasis(-transformOrigin); 132 133 // Convert to CSS pixels (this undoes the operations performed by 134 // nsStyleTransformMatrix::ProcessTranslatePart which is called from 135 // nsDisplayTransform::GetResultingTransformMatrix) 136 double devPerCss = double(scale) / double(AppUnitsPerCSSPixel()); 137 transform._41 *= devPerCss; 138 transform._42 *= devPerCss; 139 transform._43 *= devPerCss; 140 omtaValue = transform; 141 }, 142 [&](const float& aOpacity) { omtaValue = aOpacity; }, 143 [&](const nscolor& aColor) { omtaValue = aColor; }); 144 return omtaValue; 145 } 146 147 void CompositorAnimationStorage::SetAnimatedValue( 148 uint64_t aId, AnimatedValue* aPreviousValue, 149 const gfx::Matrix4x4& aFrameTransform, const TransformData& aData, 150 SampledAnimationArray&& aValue) { 151 mLock.AssertCurrentThreadOwns(); 152 153 if (!aPreviousValue) { 154 MOZ_ASSERT(!mAnimatedValues.Contains(aId)); 155 mAnimatedValues.InsertOrUpdate( 156 aId, 157 MakeUnique<AnimatedValue>(aFrameTransform, aData, std::move(aValue))); 158 return; 159 } 160 MOZ_ASSERT(aPreviousValue->Is<AnimationTransform>()); 161 MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId)); 162 163 aPreviousValue->SetTransform(aFrameTransform, aData, std::move(aValue)); 164 } 165 166 void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId, 167 AnimatedValue* aPreviousValue, 168 nscolor aColor) { 169 mLock.AssertCurrentThreadOwns(); 170 171 if (!aPreviousValue) { 172 MOZ_ASSERT(!mAnimatedValues.Contains(aId)); 173 mAnimatedValues.InsertOrUpdate(aId, MakeUnique<AnimatedValue>(aColor)); 174 return; 175 } 176 177 MOZ_ASSERT(aPreviousValue->Is<nscolor>()); 178 MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId)); 179 aPreviousValue->SetColor(aColor); 180 } 181 182 void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId, 183 AnimatedValue* aPreviousValue, 184 float aOpacity) { 185 mLock.AssertCurrentThreadOwns(); 186 187 if (!aPreviousValue) { 188 MOZ_ASSERT(!mAnimatedValues.Contains(aId)); 189 mAnimatedValues.InsertOrUpdate(aId, MakeUnique<AnimatedValue>(aOpacity)); 190 return; 191 } 192 193 MOZ_ASSERT(aPreviousValue->Is<float>()); 194 MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId)); 195 aPreviousValue->SetOpacity(aOpacity); 196 } 197 198 void CompositorAnimationStorage::SetAnimations( 199 uint64_t aId, const LayersId& aLayersId, const AnimationArray& aValue, 200 const TimeStamp& aPreviousSampleTime) { 201 MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); 202 MutexAutoLock lock(mLock); 203 204 mAnimations[aId] = 205 std::make_unique<AnimationStorageData>(AnimationHelper::ExtractAnimations( 206 aLayersId, aValue, this, aPreviousSampleTime)); 207 208 PROFILER_MARKER("SetAnimation", GRAPHICS, 209 MarkerInnerWindowId(mCompositorBridge->GetInnerWindowId()), 210 CompositorAnimationMarker, aId, 211 mAnimations[aId]->mAnimation.LastElement().mProperty); 212 213 // If there is the last animated value, then we need to store the id to remove 214 // the value if the new animation doesn't produce any animated data (i.e. in 215 // the delay phase) when we sample this new animation. 216 if (mAnimatedValues.Contains(aId)) { 217 mNewAnimations.insert(aId); 218 } 219 } 220 221 // Returns clip rect in the scroll frame's coordinate space. 222 static ParentLayerRect GetClipRectForPartialPrerender( 223 const LayersId aLayersId, const PartialPrerenderData& aPartialPrerenderData, 224 const RefPtr<APZSampler>& aSampler, const MutexAutoLock& aProofOfMapLock) { 225 if (aSampler && 226 aPartialPrerenderData.scrollId() != ScrollableLayerGuid::NULL_SCROLL_ID) { 227 return aSampler->GetCompositionBounds( 228 aLayersId, aPartialPrerenderData.scrollId(), aProofOfMapLock); 229 } 230 231 return aPartialPrerenderData.clipRect(); 232 } 233 234 void CompositorAnimationStorage::StoreAnimatedValue( 235 NonCustomCSSPropertyId aProperty, uint64_t aId, 236 const std::unique_ptr<AnimationStorageData>& aAnimationStorageData, 237 SampledAnimationArray&& aAnimationValues, 238 const MutexAutoLock& aProofOfMapLock, const RefPtr<APZSampler>& aApzSampler, 239 AnimatedValue* aAnimatedValueEntry, 240 JankedAnimationMap& aJankedAnimationMap) { 241 switch (aProperty) { 242 case eCSSProperty_background_color: { 243 SetAnimatedValue(aId, aAnimatedValueEntry, 244 Servo_AnimationValue_GetColor(aAnimationValues[0], 245 NS_RGBA(0, 0, 0, 0))); 246 break; 247 } 248 case eCSSProperty_opacity: { 249 MOZ_ASSERT(aAnimationValues.Length() == 1); 250 SetAnimatedValue(aId, aAnimatedValueEntry, 251 Servo_AnimationValue_GetOpacity(aAnimationValues[0])); 252 break; 253 } 254 case eCSSProperty_rotate: 255 case eCSSProperty_scale: 256 case eCSSProperty_translate: 257 case eCSSProperty_transform: 258 case eCSSProperty_offset_path: 259 case eCSSProperty_offset_distance: 260 case eCSSProperty_offset_rotate: 261 case eCSSProperty_offset_anchor: 262 case eCSSProperty_offset_position: { 263 MOZ_ASSERT(aAnimationStorageData->mTransformData); 264 265 const TransformData& transformData = 266 *aAnimationStorageData->mTransformData; 267 MOZ_ASSERT(transformData.origin() == nsPoint()); 268 269 gfx::Matrix4x4 frameTransform = 270 AnimationHelper::ServoAnimationValueToMatrix4x4( 271 aAnimationValues, transformData, 272 aAnimationStorageData->mCachedMotionPath); 273 274 if (const Maybe<PartialPrerenderData>& partialPrerenderData = 275 transformData.partialPrerenderData()) { 276 gfx::Matrix4x4 transform = frameTransform; 277 transform.PostTranslate( 278 partialPrerenderData->position().ToUnknownPoint()); 279 280 gfx::Matrix4x4 transformInClip = 281 partialPrerenderData->transformInClip(); 282 if (aApzSampler && partialPrerenderData->scrollId() != 283 ScrollableLayerGuid::NULL_SCROLL_ID) { 284 AsyncTransform asyncTransform = aApzSampler->GetCurrentAsyncTransform( 285 aAnimationStorageData->mLayersId, 286 partialPrerenderData->scrollId(), LayoutAndVisual, 287 aProofOfMapLock); 288 transformInClip.PostTranslate( 289 asyncTransform.mTranslation.ToUnknownPoint()); 290 } 291 transformInClip = transform * transformInClip; 292 293 ParentLayerRect clipRect = GetClipRectForPartialPrerender( 294 aAnimationStorageData->mLayersId, *partialPrerenderData, 295 aApzSampler, aProofOfMapLock); 296 if (AnimationHelper::ShouldBeJank( 297 partialPrerenderData->rect(), 298 partialPrerenderData->overflowedSides(), transformInClip, 299 clipRect)) { 300 if (aAnimatedValueEntry) { 301 frameTransform = aAnimatedValueEntry->Transform().mFrameTransform; 302 } 303 aJankedAnimationMap[aAnimationStorageData->mLayersId].AppendElement( 304 aId); 305 } 306 } 307 308 SetAnimatedValue(aId, aAnimatedValueEntry, frameTransform, transformData, 309 std::move(aAnimationValues)); 310 break; 311 } 312 default: 313 MOZ_ASSERT_UNREACHABLE("Unhandled animated property"); 314 } 315 } 316 317 bool CompositorAnimationStorage::SampleAnimations( 318 const OMTAController* aOMTAController, TimeStamp aPreviousFrameTime, 319 TimeStamp aCurrentFrameTime) { 320 MutexAutoLock lock(mLock); 321 322 bool isAnimating = false; 323 auto cleanup = MakeScopeExit([&] { mNewAnimations.clear(); }); 324 325 // Do nothing if there are no compositor animations 326 if (mAnimations.empty()) { 327 return isAnimating; 328 } 329 330 JankedAnimationMap janked; 331 RefPtr<APZSampler> apzSampler = mCompositorBridge->GetAPZSampler(); 332 333 auto callback = [&](const MutexAutoLock& aProofOfMapLock) { 334 for (const auto& iter : mAnimations) { 335 const auto& animationStorageData = iter.second; 336 if (animationStorageData->mAnimation.IsEmpty()) { 337 continue; 338 } 339 340 const NonCustomCSSPropertyId lastPropertyAnimationGroupProperty = 341 animationStorageData->mAnimation.LastElement().mProperty; 342 isAnimating = true; 343 SampledAnimationArray animationValues; 344 AnimatedValue* previousValue = GetAnimatedValue(iter.first); 345 AnimationHelper::SampleResult sampleResult = 346 AnimationHelper::SampleAnimationForEachNode( 347 apzSampler, animationStorageData->mLayersId, aProofOfMapLock, 348 aPreviousFrameTime, aCurrentFrameTime, previousValue, 349 animationStorageData->mAnimation, animationValues); 350 351 PROFILER_MARKER( 352 "SampleAnimation", GRAPHICS, 353 MarkerOptions( 354 MarkerThreadId(CompositorThreadHolder::GetThreadId()), 355 MarkerInnerWindowId(mCompositorBridge->GetInnerWindowId())), 356 CompositorAnimationMarker, iter.first, 357 lastPropertyAnimationGroupProperty); 358 359 if (!sampleResult.IsSampled()) { 360 // Note: Checking new animations first. If new animations arrive and we 361 // scroll back to delay phase in the meantime for scroll-driven 362 // animations, removing the previous animated value is still the 363 // preferable way because the newly animation case would probably more 364 // often than the scroll timeline. Besides, we expect the display items 365 // get sync with main thread at this moment, so dropping the old 366 // animation sampled result is more suitable. 367 // FIXME: Bug 1791884: We might have to revisit this to make sure we 368 // respect animation composition order. 369 if (mNewAnimations.find(iter.first) != mNewAnimations.end()) { 370 mAnimatedValues.Remove(iter.first); 371 } else if (sampleResult.mReason == 372 AnimationHelper::SampleResult::Reason::ScrollToDelayPhase) { 373 // For the scroll-driven animations, its animation effect phases may 374 // be changed between the active phase and the before/after phase. 375 // Basically we don't produce any sampled animation value for 376 // before/after phase (if we don't have fills). In this case, we have 377 // to make sure the animations are not applied on the compositor. 378 // Removing the previous animated value is not enough because the 379 // display item in webrender may be out-of-date. Therefore, we should 380 // not just skip these animation values. Instead, storing their base 381 // styles (which are in |animationValues| already) to simulate these 382 // delayed animations. 383 // 384 // There are two possible situations: 385 // 1. If there is just a new animation arrived but there is no sampled 386 // animation value when we go from active phase, we remove the 387 // previous AnimatedValue. This is done in the above condition. 388 // 2. If |animation| is not replaced by a new arrived one, we detect 389 // it in SampleAnimationForProperty(), which sets 390 // ScrollToDelayPhase if it goes from the active phase to the 391 // before/after phase. 392 // 393 // For the 2nd case, we store the base styles until we have some other 394 // new sampled results or the new animations arrived (i.e. case 1). 395 StoreAnimatedValue(lastPropertyAnimationGroupProperty, iter.first, 396 animationStorageData, std::move(animationValues), 397 aProofOfMapLock, apzSampler, previousValue, 398 janked); 399 } 400 continue; 401 } 402 403 // Store the normal sampled result. 404 StoreAnimatedValue(lastPropertyAnimationGroupProperty, iter.first, 405 animationStorageData, std::move(animationValues), 406 aProofOfMapLock, apzSampler, previousValue, janked); 407 } 408 }; 409 410 if (apzSampler) { 411 // Hold APZCTreeManager::mMapLock for all the animations inside this 412 // CompositorBridgeParent. This ensures that APZ cannot process a 413 // transaction on the updater thread in between sampling different 414 // animations. 415 apzSampler->CallWithMapLock(callback); 416 } else { 417 // A fallback way if we don't have |apzSampler|. We don't care about 418 // APZCTreeManager::mMapLock in this case because we don't use any APZ 419 // interface. 420 mozilla::Mutex dummy("DummyAPZMapLock"); 421 MutexAutoLock lock(dummy); 422 callback(lock); 423 } 424 425 if (!janked.empty() && aOMTAController) { 426 aOMTAController->NotifyJankedAnimations(std::move(janked)); 427 } 428 429 return isAnimating; 430 } 431 432 WrAnimations CompositorAnimationStorage::CollectWebRenderAnimations() const { 433 MutexAutoLock lock(mLock); 434 435 WrAnimations animations; 436 437 for (const auto& animatedValueEntry : mAnimatedValues) { 438 AnimatedValue* value = animatedValueEntry.GetWeak(); 439 value->Value().match( 440 [&](const AnimationTransform& aTransform) { 441 animations.mTransformArrays.AppendElement(wr::ToWrTransformProperty( 442 animatedValueEntry.GetKey(), aTransform.mFrameTransform)); 443 }, 444 [&](const float& aOpacity) { 445 animations.mOpacityArrays.AppendElement( 446 wr::ToWrOpacityProperty(animatedValueEntry.GetKey(), aOpacity)); 447 }, 448 [&](const nscolor& aColor) { 449 animations.mColorArrays.AppendElement(wr::ToWrColorProperty( 450 animatedValueEntry.GetKey(), 451 ToDeviceColor(gfx::sRGBColor::FromABGR(aColor)))); 452 }); 453 } 454 455 return animations; 456 } 457 458 } // namespace layers 459 } // namespace mozilla