tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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