Bug 822231; use the maximum scale for animated scaling. r=roc
authorNicholas Cameron <ncameron@mozilla.com>
Fri, 21 Dec 2012 16:58:17 -0500
changeset 125770 ae6237161b6c49c91cb0e439cdffeeb7f8c87123
parent 125769 aad19a7636f3766eb71549ef36267d2b4540d89a
child 125771 f845e89f4c6860eca00f07045d218fd1e84c3afe
child 125959 77d65b2c81c809d3f0342181ea3baf2693b0a576
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs822231
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 822231; use the maximum scale for animated scaling. r=roc
layout/base/FrameLayerBuilder.cpp
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -2727,44 +2727,52 @@ ChooseScaleAndSetTransform(FrameLayerBui
 
   bool canDraw2D = transform.CanDraw2D(&transform2d);
   gfxSize scale;
   bool isRetained = aLayer->Manager()->IsWidgetLayerManager();
   // Only fiddle with scale factors for the retaining layer manager, since
   // it only matters for retained layers
   // XXX Should we do something for 3D transforms?
   if (canDraw2D && isRetained) {
-    //Scale factors are normalized to a power of 2 to reduce the number of resolution changes
-    scale = transform2d.ScaleFactors(true);
-    // For frames with a changing transform that's not just a translation,
-    // round scale factors up to nearest power-of-2 boundary so that we don't
-    // keep having to redraw the content as it scales up and down. Rounding up to nearest
-    // power-of-2 boundary ensures we never scale up, only down --- avoiding
-    // jaggies. It also ensures we never scale down by more than a factor of 2,
-    // avoiding bad downscaling quality.
-    gfxMatrix frameTransform;
-    if (aContainerFrame->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer) &&
-        aTransform &&
-        (!aTransform->Is2D(&frameTransform) || frameTransform.HasNonTranslationOrFlip())) {
-      // Don't clamp the scale factor when the new desired scale factor matches the old one
-      // or it was previously unscaled.
-      bool clamp = true;
-      gfxMatrix oldFrameTransform2d;
-      if (aLayer->GetBaseTransform().Is2D(&oldFrameTransform2d)) {
-        gfxSize oldScale = oldFrameTransform2d.ScaleFactors(true);
-        if (oldScale == scale || oldScale == gfxSize(1.0, 1.0)) {
-          clamp = false;
+    // If the container's transform is animated off main thread, then use the
+    // maximum scale.
+    if (aContainerFrame->GetContent() &&
+        nsLayoutUtils::HasAnimationsForCompositor(
+          aContainerFrame->GetContent(), eCSSProperty_transform)) {
+      scale = nsLayoutUtils::GetMaximumAnimatedScale(aContainerFrame->GetContent());
+    } else {
+      //Scale factors are normalized to a power of 2 to reduce the number of resolution changes
+      scale = transform2d.ScaleFactors(true);
+      // For frames with a changing transform that's not just a translation,
+      // round scale factors up to nearest power-of-2 boundary so that we don't
+      // keep having to redraw the content as it scales up and down. Rounding up to nearest
+      // power-of-2 boundary ensures we never scale up, only down --- avoiding
+      // jaggies. It also ensures we never scale down by more than a factor of 2,
+      // avoiding bad downscaling quality.
+      gfxMatrix frameTransform;
+      if (aContainerFrame->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer) &&
+          aTransform &&
+          (!aTransform->Is2D(&frameTransform) || frameTransform.HasNonTranslationOrFlip())) {
+        // Don't clamp the scale factor when the new desired scale factor matches the old one
+        // or it was previously unscaled.
+        bool clamp = true;
+        gfxMatrix oldFrameTransform2d;
+        if (aLayer->GetBaseTransform().Is2D(&oldFrameTransform2d)) {
+          gfxSize oldScale = oldFrameTransform2d.ScaleFactors(true);
+          if (oldScale == scale || oldScale == gfxSize(1.0, 1.0)) {
+            clamp = false;
+          }
         }
+        if (clamp) {
+          scale.width = gfxUtils::ClampToScaleFactor(scale.width);
+          scale.height = gfxUtils::ClampToScaleFactor(scale.height);
+        }
+      } else {
+        // XXX Do we need to move nearly-integer values to integers here?
       }
-      if (clamp) {
-        scale.width = gfxUtils::ClampToScaleFactor(scale.width);
-        scale.height = gfxUtils::ClampToScaleFactor(scale.height);
-      }
-    } else {
-      // XXX Do we need to move nearly-integer values to integers here?
     }
     // If the scale factors are too small, just use 1.0. The content is being
     // scaled out of sight anyway.
     if (fabs(scale.width) < 1e-8 || fabs(scale.height) < 1e-8) {
       scale = gfxSize(1.0, 1.0);
     }
   } else {
     scale = gfxSize(1.0, 1.0);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -175,49 +175,144 @@ FlexboxEnabledPrefChangeCallback(const c
       isFlexboxEnabled ? eCSSKeyword_inline_flex : eCSSKeyword_UNKNOWN;
   }
 
   return 0;
 }
 #endif // MOZ_FLEXBOX
 
 template <class AnimationsOrTransitions>
-static bool
+static AnimationsOrTransitions*
 HasAnimationOrTransition(nsIContent* aContent,
                          nsIAtom* aAnimationProperty,
                          nsCSSProperty aProperty)
 {
   AnimationsOrTransitions* animations =
     static_cast<AnimationsOrTransitions*>(aContent->GetProperty(aAnimationProperty));
   if (animations) {
     bool propertyMatches = animations->HasAnimationOfProperty(aProperty);
     if (propertyMatches &&
         animations->CanPerformOnCompositorThread(
           CommonElementAnimationData::CanAnimate_AllowPartial)) {
-      return true;
+      return animations;
     }
   }
 
-  return false;
+  return nullptr;
 }
 
 bool
 nsLayoutUtils::HasAnimationsForCompositor(nsIContent* aContent,
                                           nsCSSProperty aProperty)
 {
   if (!aContent->MayHaveAnimations())
     return false;
   if (HasAnimationOrTransition<ElementAnimations>
         (aContent, nsGkAtoms::animationsProperty, aProperty)) {
     return true;
   }
   return HasAnimationOrTransition<ElementTransitions>
     (aContent, nsGkAtoms::transitionsProperty, aProperty);
 }
 
+static gfxSize
+GetScaleForValue(const nsStyleAnimation::Value& aValue,
+                 nsIFrame* aFrame)
+{
+  if (!aFrame) {
+    NS_WARNING("No frame.");
+    return gfxSize();
+  }
+  if (aValue.GetUnit() != nsStyleAnimation::eUnit_Transform) {
+    NS_WARNING("Expected a transform.");
+    return gfxSize();
+  }
+
+  nsCSSValueList* values = aValue.GetCSSValueListValue();
+  if (values->mValue.GetUnit() == eCSSUnit_None) {
+    // There is an animation, but no actual transform yet.
+    return gfxSize();
+  }
+
+  nsRect frameBounds = aFrame->GetRect();
+  bool dontCare;
+  gfx3DMatrix transform = nsStyleTransformMatrix::ReadTransforms(
+                            aValue.GetCSSValueListValue(),
+                            aFrame->GetStyleContext(),
+                            aFrame->PresContext(), dontCare, frameBounds,
+                            aFrame->PresContext()->AppUnitsPerDevPixel());
+
+  gfxMatrix transform2d;
+  bool canDraw2D = transform.CanDraw2D(&transform2d);
+  if (!canDraw2D) {
+    return gfxSize();
+  }
+
+  return transform2d.ScaleFactors(true);
+}
+
+gfxSize
+nsLayoutUtils::GetMaximumAnimatedScale(nsIContent* aContent)
+{
+  gfxSize result;
+  ElementAnimations* animations = HasAnimationOrTransition<ElementAnimations>
+    (aContent, nsGkAtoms::animationsProperty, eCSSProperty_transform);
+  if (animations) {
+    for (uint32_t animIdx = animations->mAnimations.Length(); animIdx-- != 0; ) {
+      ElementAnimation& anim = animations->mAnimations[animIdx];
+      for (uint32_t propIdx = anim.mProperties.Length(); propIdx-- != 0; ) {
+        AnimationProperty& prop = anim.mProperties[propIdx];
+        if (prop.mProperty == eCSSProperty_transform) {
+          for (uint32_t segIdx = prop.mSegments.Length(); segIdx-- != 0; ) {
+            AnimationPropertySegment& segment = prop.mSegments[segIdx];
+            gfxSize from = GetScaleForValue(segment.mFromValue,
+                                            aContent->GetPrimaryFrame());
+            result.width = NS_MAX<float>(result.width, from.width);
+            result.height = NS_MAX<float>(result.height, from.height);
+            gfxSize to = GetScaleForValue(segment.mToValue,
+                                          aContent->GetPrimaryFrame());
+            result.width = NS_MAX<float>(result.width, to.width);
+            result.height = NS_MAX<float>(result.height, to.height);
+          }
+        }
+      }
+    }
+  }
+  ElementTransitions* transitions = HasAnimationOrTransition<ElementTransitions>
+    (aContent, nsGkAtoms::transitionsProperty, eCSSProperty_transform);
+  if (transitions) {
+    for (uint32_t i = 0, i_end = transitions->mPropertyTransitions.Length();
+         i < i_end; ++i)
+    {
+      ElementPropertyTransition &pt = transitions->mPropertyTransitions[i];
+      if (pt.IsRemovedSentinel()) {
+        continue;
+      }
+
+      if (pt.mProperty == eCSSProperty_transform) {
+        gfxSize start = GetScaleForValue(pt.mStartValue,
+                                         aContent->GetPrimaryFrame());
+        result.width = NS_MAX<float>(result.width, start.width);
+        result.height = NS_MAX<float>(result.height, start.height);
+        gfxSize end = GetScaleForValue(pt.mEndValue,
+                                       aContent->GetPrimaryFrame());
+        result.width = NS_MAX<float>(result.width, end.width);
+        result.height = NS_MAX<float>(result.height, end.height);
+      }
+    }
+  }
+
+  // If we didn't manage to find a max scale, use no scale rather than 0,0
+  if (result == gfxSize()) {
+    return gfxSize(1, 1);
+  }
+
+  return result;
+}
+
 bool
 nsLayoutUtils::Are3DTransformsEnabled()
 {
   static bool s3DTransformsEnabled;
   static bool s3DTransformPrefCached = false;
 
   if (!s3DTransformPrefCached) {
     s3DTransformPrefCached = true;
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1567,16 +1567,23 @@ public:
   static bool AreTransformAnimationsEnabled();
 
   /**
    * Checks if we should warn about animations that can't be async
    */
   static bool IsAnimationLoggingEnabled();
 
   /**
+   * Find the maximum scale for an element (aContent) over the course of any
+   * animations and transitions on the element. Will return 1,1 if there is no
+   * animated scaling.
+   */
+  static gfxSize GetMaximumAnimatedScale(nsIContent* aContent);
+
+  /**
    * Checks if we should forcibly use nearest pixel filtering for the
    * background.
    */
   static bool UseBackgroundNearestFiltering();
 
   /**
    * Checks whether we want to use the GPU to scale images when
    * possible.