diff options
Diffstat (limited to 'gfx/layers/CompositorAnimationStorage.cpp')
-rw-r--r-- | gfx/layers/CompositorAnimationStorage.cpp | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/gfx/layers/CompositorAnimationStorage.cpp b/gfx/layers/CompositorAnimationStorage.cpp new file mode 100644 index 0000000000..8def6e3107 --- /dev/null +++ b/gfx/layers/CompositorAnimationStorage.cpp @@ -0,0 +1,429 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "CompositorAnimationStorage.h" + +#include "AnimationHelper.h" +#include "mozilla/gfx/MatrixFwd.h" +#include "mozilla/layers/APZSampler.h" // for APZSampler +#include "mozilla/layers/CompositorBridgeParent.h" // for CompositorBridgeParent +#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder +#include "mozilla/layers/OMTAController.h" // for OMTAController +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/ServoStyleConsts.h" +#include "mozilla/webrender/WebRenderTypes.h" // for ToWrTransformProperty, etc +#include "nsDeviceContext.h" // for AppUnitsPerCSSPixel +#include "nsDisplayList.h" // for nsDisplayTransform, etc +#include "nsLayoutUtils.h" +#include "TreeTraversal.h" // for ForEachNode, BreadthFirstSearch + +namespace geckoprofiler::markers { + +using namespace mozilla; + +struct CompositorAnimationMarker { + static constexpr Span<const char> MarkerTypeName() { + return MakeStringSpan("CompositorAnimation"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + uint64_t aId, nsCSSPropertyID aProperty) { + aWriter.IntProperty("pid", int64_t(aId >> 32)); + aWriter.IntProperty("id", int64_t(aId & 0xffffffff)); + aWriter.StringProperty("property", nsCSSProps::GetStringValue(aProperty)); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormat("pid", "Process Id", MS::Format::Integer); + schema.AddKeyLabelFormat("id", "Animation Id", MS::Format::Integer); + schema.AddKeyLabelFormat("property", "Animated Property", + MS::Format::String); + schema.SetTableLabel("{marker.name} - {marker.data.property}"); + return schema; + } +}; + +} // namespace geckoprofiler::markers + +namespace mozilla { +namespace layers { + +using gfx::Matrix4x4; + +void CompositorAnimationStorage::ClearById(const uint64_t& aId) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mLock); + + const auto& animationStorageData = mAnimations[aId]; + if (animationStorageData) { + PROFILER_MARKER("ClearAnimation", GRAPHICS, + MarkerInnerWindowId(mCompositorBridge->GetInnerWindowId()), + CompositorAnimationMarker, aId, + animationStorageData->mAnimation.LastElement().mProperty); + } + + mAnimatedValues.Remove(aId); + mAnimations.erase(aId); +} + +bool CompositorAnimationStorage::HasAnimations() const { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mLock); + + return !mAnimations.empty(); +} + +AnimatedValue* CompositorAnimationStorage::GetAnimatedValue( + const uint64_t& aId) const { + mLock.AssertCurrentThreadOwns(); + + return mAnimatedValues.Get(aId); +} + +OMTAValue CompositorAnimationStorage::GetOMTAValue(const uint64_t& aId) const { + MutexAutoLock lock(mLock); + + OMTAValue omtaValue = mozilla::null_t(); + auto animatedValue = GetAnimatedValue(aId); + if (!animatedValue) { + return omtaValue; + } + + animatedValue->Value().match( + [&](const AnimationTransform& aTransform) { + gfx::Matrix4x4 transform = aTransform.mFrameTransform; + const TransformData& data = aTransform.mData; + float scale = data.appUnitsPerDevPixel(); + gfx::Point3D transformOrigin = data.transformOrigin(); + + // Undo the rebasing applied by + // nsDisplayTransform::GetResultingTransformMatrixInternal + transform.ChangeBasis(-transformOrigin); + + // Convert to CSS pixels (this undoes the operations performed by + // nsStyleTransformMatrix::ProcessTranslatePart which is called from + // nsDisplayTransform::GetResultingTransformMatrix) + double devPerCss = double(scale) / double(AppUnitsPerCSSPixel()); + transform._41 *= devPerCss; + transform._42 *= devPerCss; + transform._43 *= devPerCss; + omtaValue = transform; + }, + [&](const float& aOpacity) { omtaValue = aOpacity; }, + [&](const nscolor& aColor) { omtaValue = aColor; }); + return omtaValue; +} + +void CompositorAnimationStorage::SetAnimatedValue( + uint64_t aId, AnimatedValue* aPreviousValue, + const gfx::Matrix4x4& aFrameTransform, const TransformData& aData) { + mLock.AssertCurrentThreadOwns(); + + if (!aPreviousValue) { + MOZ_ASSERT(!mAnimatedValues.Contains(aId)); + mAnimatedValues.InsertOrUpdate( + aId, + MakeUnique<AnimatedValue>(gfx::Matrix4x4(), aFrameTransform, aData)); + return; + } + MOZ_ASSERT(aPreviousValue->Is<AnimationTransform>()); + MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId)); + + aPreviousValue->SetTransform(aFrameTransform, aData); +} + +void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId, + AnimatedValue* aPreviousValue, + nscolor aColor) { + mLock.AssertCurrentThreadOwns(); + + if (!aPreviousValue) { + MOZ_ASSERT(!mAnimatedValues.Contains(aId)); + mAnimatedValues.InsertOrUpdate(aId, MakeUnique<AnimatedValue>(aColor)); + return; + } + + MOZ_ASSERT(aPreviousValue->Is<nscolor>()); + MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId)); + aPreviousValue->SetColor(aColor); +} + +void CompositorAnimationStorage::SetAnimatedValue(uint64_t aId, + AnimatedValue* aPreviousValue, + float aOpacity) { + mLock.AssertCurrentThreadOwns(); + + if (!aPreviousValue) { + MOZ_ASSERT(!mAnimatedValues.Contains(aId)); + mAnimatedValues.InsertOrUpdate(aId, MakeUnique<AnimatedValue>(aOpacity)); + return; + } + + MOZ_ASSERT(aPreviousValue->Is<float>()); + MOZ_ASSERT(aPreviousValue == GetAnimatedValue(aId)); + aPreviousValue->SetOpacity(aOpacity); +} + +void CompositorAnimationStorage::SetAnimations(uint64_t aId, + const LayersId& aLayersId, + const AnimationArray& aValue) { + MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); + MutexAutoLock lock(mLock); + + mAnimations[aId] = std::make_unique<AnimationStorageData>( + AnimationHelper::ExtractAnimations(aLayersId, aValue)); + + PROFILER_MARKER("SetAnimation", GRAPHICS, + MarkerInnerWindowId(mCompositorBridge->GetInnerWindowId()), + CompositorAnimationMarker, aId, + mAnimations[aId]->mAnimation.LastElement().mProperty); + + // If there is the last animated value, then we need to store the id to remove + // the value if the new animation doesn't produce any animated data (i.e. in + // the delay phase) when we sample this new animation. + if (mAnimatedValues.Contains(aId)) { + mNewAnimations.insert(aId); + } +} + +// Returns clip rect in the scroll frame's coordinate space. +static ParentLayerRect GetClipRectForPartialPrerender( + const LayersId aLayersId, const PartialPrerenderData& aPartialPrerenderData, + const RefPtr<APZSampler>& aSampler, const MutexAutoLock& aProofOfMapLock) { + if (aSampler && + aPartialPrerenderData.scrollId() != ScrollableLayerGuid::NULL_SCROLL_ID) { + return aSampler->GetCompositionBounds( + aLayersId, aPartialPrerenderData.scrollId(), aProofOfMapLock); + } + + return aPartialPrerenderData.clipRect(); +} + +void CompositorAnimationStorage::StoreAnimatedValue( + nsCSSPropertyID aProperty, uint64_t aId, + const std::unique_ptr<AnimationStorageData>& aAnimationStorageData, + const AutoTArray<RefPtr<StyleAnimationValue>, 1>& aAnimationValues, + const MutexAutoLock& aProofOfMapLock, const RefPtr<APZSampler>& aApzSampler, + AnimatedValue* aAnimatedValueEntry, + JankedAnimationMap& aJankedAnimationMap) { + switch (aProperty) { + case eCSSProperty_background_color: { + SetAnimatedValue(aId, aAnimatedValueEntry, + Servo_AnimationValue_GetColor(aAnimationValues[0], + NS_RGBA(0, 0, 0, 0))); + break; + } + case eCSSProperty_opacity: { + MOZ_ASSERT(aAnimationValues.Length() == 1); + SetAnimatedValue(aId, aAnimatedValueEntry, + Servo_AnimationValue_GetOpacity(aAnimationValues[0])); + break; + } + case eCSSProperty_rotate: + case eCSSProperty_scale: + case eCSSProperty_translate: + case eCSSProperty_transform: + case eCSSProperty_offset_path: + case eCSSProperty_offset_distance: + case eCSSProperty_offset_rotate: + case eCSSProperty_offset_anchor: { + MOZ_ASSERT(aAnimationStorageData->mTransformData); + + const TransformData& transformData = + *aAnimationStorageData->mTransformData; + MOZ_ASSERT(transformData.origin() == nsPoint()); + + gfx::Matrix4x4 frameTransform = + AnimationHelper::ServoAnimationValueToMatrix4x4( + aAnimationValues, transformData, + aAnimationStorageData->mCachedMotionPath); + + if (const Maybe<PartialPrerenderData>& partialPrerenderData = + transformData.partialPrerenderData()) { + gfx::Matrix4x4 transform = frameTransform; + transform.PostTranslate( + partialPrerenderData->position().ToUnknownPoint()); + + gfx::Matrix4x4 transformInClip = + partialPrerenderData->transformInClip(); + if (aApzSampler && partialPrerenderData->scrollId() != + ScrollableLayerGuid::NULL_SCROLL_ID) { + AsyncTransform asyncTransform = aApzSampler->GetCurrentAsyncTransform( + aAnimationStorageData->mLayersId, + partialPrerenderData->scrollId(), LayoutAndVisual, + aProofOfMapLock); + transformInClip.PostTranslate( + asyncTransform.mTranslation.ToUnknownPoint()); + } + transformInClip = transform * transformInClip; + + ParentLayerRect clipRect = GetClipRectForPartialPrerender( + aAnimationStorageData->mLayersId, *partialPrerenderData, + aApzSampler, aProofOfMapLock); + if (AnimationHelper::ShouldBeJank( + partialPrerenderData->rect(), + partialPrerenderData->overflowedSides(), transformInClip, + clipRect)) { + if (aAnimatedValueEntry) { + frameTransform = aAnimatedValueEntry->Transform().mFrameTransform; + } + aJankedAnimationMap[aAnimationStorageData->mLayersId].AppendElement( + aId); + } + } + + SetAnimatedValue(aId, aAnimatedValueEntry, frameTransform, transformData); + break; + } + default: + MOZ_ASSERT_UNREACHABLE("Unhandled animated property"); + } +} + +bool CompositorAnimationStorage::SampleAnimations( + const OMTAController* aOMTAController, TimeStamp aPreviousFrameTime, + TimeStamp aCurrentFrameTime) { + MutexAutoLock lock(mLock); + + bool isAnimating = false; + auto cleanup = MakeScopeExit([&] { mNewAnimations.clear(); }); + + // Do nothing if there are no compositor animations + if (mAnimations.empty()) { + return isAnimating; + } + + JankedAnimationMap janked; + RefPtr<APZSampler> apzSampler = mCompositorBridge->GetAPZSampler(); + + auto callback = [&](const MutexAutoLock& aProofOfMapLock) { + for (const auto& iter : mAnimations) { + const auto& animationStorageData = iter.second; + if (animationStorageData->mAnimation.IsEmpty()) { + continue; + } + + const nsCSSPropertyID lastPropertyAnimationGroupProperty = + animationStorageData->mAnimation.LastElement().mProperty; + isAnimating = true; + AutoTArray<RefPtr<StyleAnimationValue>, 1> animationValues; + AnimatedValue* previousValue = GetAnimatedValue(iter.first); + AnimationHelper::SampleResult sampleResult = + AnimationHelper::SampleAnimationForEachNode( + apzSampler, animationStorageData->mLayersId, aProofOfMapLock, + aPreviousFrameTime, aCurrentFrameTime, previousValue, + animationStorageData->mAnimation, animationValues); + + PROFILER_MARKER( + "SampleAnimation", GRAPHICS, + MarkerOptions( + MarkerThreadId(CompositorThreadHolder::GetThreadId()), + MarkerInnerWindowId(mCompositorBridge->GetInnerWindowId())), + CompositorAnimationMarker, iter.first, + lastPropertyAnimationGroupProperty); + + if (!sampleResult.IsSampled()) { + // Note: Checking new animations first. If new animations arrive and we + // scroll back to delay phase in the meantime for scroll-driven + // animations, removing the previous animated value is still the + // preferable way because the newly animation case would probably more + // often than the scroll timeline. Besides, we expect the display items + // get sync with main thread at this moment, so dropping the old + // animation sampled result is more suitable. + // FIXME: Bug 1791884: We might have to revisit this to make sure we + // respect animation composition order. + if (mNewAnimations.find(iter.first) != mNewAnimations.end()) { + mAnimatedValues.Remove(iter.first); + } else if (sampleResult.mReason == + AnimationHelper::SampleResult::Reason::ScrollToDelayPhase) { + // For the scroll-driven animations, its animation effect phases may + // be changed between the active phase and the before/after phase. + // Basically we don't produce any sampled animation value for + // before/after phase (if we don't have fills). In this case, we have + // to make sure the animations are not applied on the compositor. + // Removing the previous animated value is not enough because the + // display item in webrender may be out-of-date. Therefore, we should + // not just skip these animation values. Instead, storing their base + // styles (which are in |animationValues| already) to simulate these + // delayed animations. + // + // There are two possible situations: + // 1. If there is just a new animation arrived but there is no sampled + // animation value when we go from active phase, we remove the + // previous AnimatedValue. This is done in the above condition. + // 2. If |animation| is not replaced by a new arrived one, we detect + // it in SampleAnimationForProperty(), which sets + // ScrollToDelayPhase if it goes from the active phase to the + // before/after phase. + // + // For the 2nd case, we store the base styles until we have some other + // new sampled results or the new animations arrived (i.e. case 1). + StoreAnimatedValue(lastPropertyAnimationGroupProperty, iter.first, + animationStorageData, animationValues, + aProofOfMapLock, apzSampler, previousValue, + janked); + } + continue; + } + + // Store the normal sampled result. + StoreAnimatedValue(lastPropertyAnimationGroupProperty, iter.first, + animationStorageData, animationValues, aProofOfMapLock, + apzSampler, previousValue, janked); + } + }; + + if (apzSampler) { + // Hold APZCTreeManager::mMapLock for all the animations inside this + // CompositorBridgeParent. This ensures that APZ cannot process a + // transaction on the updater thread in between sampling different + // animations. + apzSampler->CallWithMapLock(callback); + } else { + // A fallback way if we don't have |apzSampler|. We don't care about + // APZCTreeManager::mMapLock in this case because we don't use any APZ + // interface. + mozilla::Mutex dummy("DummyAPZMapLock"); + MutexAutoLock lock(dummy); + callback(lock); + } + + if (!janked.empty() && aOMTAController) { + aOMTAController->NotifyJankedAnimations(std::move(janked)); + } + + return isAnimating; +} + +WrAnimations CompositorAnimationStorage::CollectWebRenderAnimations() const { + MutexAutoLock lock(mLock); + + WrAnimations animations; + + for (const auto& animatedValueEntry : mAnimatedValues) { + AnimatedValue* value = animatedValueEntry.GetWeak(); + value->Value().match( + [&](const AnimationTransform& aTransform) { + animations.mTransformArrays.AppendElement(wr::ToWrTransformProperty( + animatedValueEntry.GetKey(), aTransform.mFrameTransform)); + }, + [&](const float& aOpacity) { + animations.mOpacityArrays.AppendElement( + wr::ToWrOpacityProperty(animatedValueEntry.GetKey(), aOpacity)); + }, + [&](const nscolor& aColor) { + animations.mColorArrays.AppendElement(wr::ToWrColorProperty( + animatedValueEntry.GetKey(), + ToDeviceColor(gfx::sRGBColor::FromABGR(aColor)))); + }); + } + + return animations; +} + +} // namespace layers +} // namespace mozilla |