Bug 637852. Part 17: Tighten up AreLayersMarkedActive so we track exactly what properties are changing. Also, clamp resolution to power-of-2 only if the frame's transform has a scale. r=tnikkel
authorRobert O'Callahan <robert@ocallahan.org>
Thu, 23 Jun 2011 00:11:28 +1200
changeset 71806 500265c61f37887aa038076384a94717bdb65737
parent 71805 c6c5f217fedffde4c5f37dd3832bb9aee7e2d783
child 71807 4c323a5e40c2a43d2a204bf5c9a34c1a229a163c
push idunknown
push userunknown
push dateunknown
reviewerstnikkel
bugs637852
milestone7.0a1
Bug 637852. Part 17: Tighten up AreLayersMarkedActive so we track exactly what properties are changing. Also, clamp resolution to power-of-2 only if the frame's transform has a scale. r=tnikkel This lets us avoid clamping the scale in more situations. We should only clamp the scale when we think the scale is changing due to a changing transform --- the goal of clamping is to not have to redraw the content too often when the content is zooming in or out.
content/html/content/src/nsHTMLCanvasElement.cpp
layout/base/FrameLayerBuilder.cpp
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsDisplayList.cpp
layout/generic/nsFrame.cpp
layout/generic/nsHTMLCanvasFrame.cpp
layout/generic/nsIFrame.h
--- a/content/html/content/src/nsHTMLCanvasElement.cpp
+++ b/content/html/content/src/nsHTMLCanvasElement.cpp
@@ -616,17 +616,17 @@ void
 nsHTMLCanvasElement::InvalidateCanvasContent(const gfxRect* damageRect)
 {
   // We don't need to flush anything here; if there's no frame or if
   // we plan to reframe we don't need to invalidate it anyway.
   nsIFrame *frame = GetPrimaryFrame();
   if (!frame)
     return;
 
-  frame->MarkLayersActive();
+  frame->MarkLayersActive(nsChangeHint(0));
 
   nsRect invalRect;
   nsRect contentArea = frame->GetContentRect();
   if (damageRect) {
     nsIntSize size = GetWidthHeight();
     if (size.width != 0 && size.height != 0) {
 
       // damageRect and size are in CSS pixels; contentArea is in appunits
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -1661,24 +1661,26 @@ ChooseScaleAndSetTransform(FrameLayerBui
   gfxMatrix transform2d;
   gfxSize scale;
   // Only fiddle with scale factors for the retaining layer manager, since
   // it only matters for retained layers
   if (aLayerBuilder->GetRetainingLayerManager() == aLayer->Manager() &&
       transform.Is2D(&transform2d)) {
     //Scale factors are normalized to a power of 2 to reduce the number of resolution changes
     scale = transform2d.ScaleFactors(PR_TRUE);
-    // For frames that aren't marked active (i.e. opacity/transform not
-    // changed recently), let them take their exact resolution. Otherwise
-    // round 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
+    // 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.
-    if (aContainerFrame->AreLayersMarkedActive()) {
+    gfxMatrix frameTransform;
+    if (aContainerFrame->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer) &&
+        aTransform &&
+        (!aTransform->Is2D(&frameTransform) || frameTransform.HasNonTranslationOrFlip())) {
       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 (abs(scale.width) < 1e-8 || abs(scale.height) < 1e-8) {
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -7684,23 +7684,23 @@ DoApplyRenderingChangeToTree(nsIFrame* a
             outerSVGFrame->UpdateAndInvalidateCoveredRegion(aFrame);
           }
         }
       } else {
         aFrame->InvalidateOverflowRect();
       }
     }
     if (aChange & nsChangeHint_UpdateOpacityLayer) {
-      aFrame->MarkLayersActive();
+      aFrame->MarkLayersActive(nsChangeHint_UpdateOpacityLayer);
       aFrame->InvalidateLayer(aFrame->GetVisualOverflowRectRelativeToSelf(),
                               nsDisplayItem::TYPE_OPACITY);
     }
     
     if (aChange & nsChangeHint_UpdateTransformLayer) {
-      aFrame->MarkLayersActive();
+      aFrame->MarkLayersActive(nsChangeHint_UpdateTransformLayer);
       // Invalidate the old transformed area. The new transformed area
       // will be invalidated by nsFrame::FinishAndStoreOverflowArea.
       aFrame->InvalidateTransformLayer();
     }
   }
 }
 
 static void
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1709,17 +1709,17 @@ nsDisplayOpacity::BuildLayer(nsDisplayLi
 
   layer->SetOpacity(mFrame->GetStyleDisplay()->mOpacity);
   return layer.forget();
 }
 
 nsDisplayItem::LayerState
 nsDisplayOpacity::GetLayerState(nsDisplayListBuilder* aBuilder,
                                 LayerManager* aManager) {
-  if (mFrame->AreLayersMarkedActive())
+  if (mFrame->AreLayersMarkedActive(nsChangeHint_UpdateOpacityLayer))
     return LAYER_ACTIVE;
   nsIFrame* activeScrolledRoot =
     nsLayoutUtils::GetActiveScrolledRootFor(mFrame, nsnull);
   return !ChildrenCanBeInactive(aBuilder, aManager, mList, activeScrolledRoot)
       ? LAYER_ACTIVE : LAYER_INACTIVE;
 }
 
 PRBool
@@ -2393,17 +2393,17 @@ already_AddRefed<Layer> nsDisplayTransfo
   return aBuilder->LayerBuilder()->
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, *mStoredList.GetList(),
                            aContainerParameters, &matrix);
 }
 
 nsDisplayItem::LayerState
 nsDisplayTransform::GetLayerState(nsDisplayListBuilder* aBuilder,
                                   LayerManager* aManager) {
-  if (mFrame->AreLayersMarkedActive())
+  if (mFrame->AreLayersMarkedActive(nsChangeHint_UpdateTransformLayer))
     return LAYER_ACTIVE;
   nsIFrame* activeScrolledRoot =
     nsLayoutUtils::GetActiveScrolledRootFor(mFrame, nsnull);
   return !mStoredList.ChildrenCanBeInactive(aBuilder, 
                                              aManager, 
                                              *mStoredList.GetList(), 
                                              activeScrolledRoot)
       ? LAYER_ACTIVE : LAYER_INACTIVE;
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -113,16 +113,17 @@
 #include "nsHTMLContainerFrame.h"
 #include "nsBoxLayoutState.h"
 #include "nsBlockFrame.h"
 #include "nsDisplayList.h"
 #include "nsIObjectLoadingContent.h"
 #include "nsExpirationTracker.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsSVGEffects.h"
+#include "nsChangeHint.h"
 
 #include "gfxContext.h"
 #include "CSSCalc.h"
 #include "nsAbsoluteContainingBlock.h"
 
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
@@ -4094,22 +4095,27 @@ nsIFrame::InvalidateTransformLayer()
   // are performed before applying transforms.
   mParent->InvalidateInternal(GetVisualOverflowRect() + GetPosition(),
                               0, 0, this,
                               hasLayer ? INVALIDATE_NO_THEBES_LAYERS : 0);
 }
 
 class LayerActivity {
 public:
-  LayerActivity(nsIFrame* aFrame) : mFrame(aFrame) {}
+  LayerActivity(nsIFrame* aFrame) : mFrame(aFrame), mChangeHint(nsChangeHint(0)) {}
   ~LayerActivity();
   nsExpirationState* GetExpirationState() { return &mState; }
 
   nsIFrame* mFrame;
   nsExpirationState mState;
+  // mChangeHint can be some combination of nsChangeHint_UpdateOpacityLayer and
+  // nsChangeHint_UpdateTransformLayer (or neither)
+  // The presence of those bits indicates whether opacity or transform
+  // changes have been detected.
+  nsChangeHint mChangeHint;
 };
 
 class LayerActivityTracker : public nsExpirationTracker<LayerActivity,4> {
 public:
   // 75-100ms is a good timeout period. We use 4 generations of 25ms each.
   enum { GENERATION_MS = 100 };
   LayerActivityTracker()
     : nsExpirationTracker<LayerActivity,4>(GENERATION_MS) {}
@@ -4144,39 +4150,48 @@ LayerActivityTracker::NotifyExpired(Laye
 
   nsIFrame* f = aObject->mFrame;
   aObject->mFrame = nsnull;
   f->Properties().Delete(LayerActivityProperty());
   f->InvalidateFrameSubtree();
 }
 
 void
-nsIFrame::MarkLayersActive()
+nsIFrame::MarkLayersActive(nsChangeHint aChangeHint)
 {
   FrameProperties properties = Properties();
   LayerActivity* layerActivity =
     static_cast<LayerActivity*>(properties.Get(LayerActivityProperty()));
   if (layerActivity) {
     gLayerActivityTracker->MarkUsed(layerActivity);
   } else {
     if (!gLayerActivityTracker) {
       gLayerActivityTracker = new LayerActivityTracker();
     }
     layerActivity = new LayerActivity(this);
     gLayerActivityTracker->AddObject(layerActivity);
     properties.Set(LayerActivityProperty(), layerActivity);
   }
+  NS_UpdateHint(layerActivity->mChangeHint, aChangeHint);
 }
 
 PRBool
 nsIFrame::AreLayersMarkedActive()
 {
   return Properties().Get(LayerActivityProperty()) != nsnull;
 }
 
+PRBool
+nsIFrame::AreLayersMarkedActive(nsChangeHint aChangeHint)
+{
+  LayerActivity* layerActivity =
+    static_cast<LayerActivity*>(Properties().Get(LayerActivityProperty()));
+  return layerActivity && (layerActivity->mChangeHint & aChangeHint);
+}
+
 /* static */ void
 nsFrame::ShutdownLayerActivityTimer()
 {
   delete gLayerActivityTracker;
   gLayerActivityTracker = nsnull;
 }
 
 void
--- a/layout/generic/nsHTMLCanvasFrame.cpp
+++ b/layout/generic/nsHTMLCanvasFrame.cpp
@@ -128,17 +128,17 @@ nsHTMLCanvasFrame::Init(nsIContent* aCon
                         nsIFrame*   aParent,
                         nsIFrame*   aPrevInFlow)
 {
   nsresult rv = nsSplittableFrame::Init(aContent, aParent, aPrevInFlow);
 
   // We can fill in the canvas before the canvas frame is created, in
   // which case we never get around to marking the layer active. Therefore,
   // we mark it active here when we create the frame.
-  MarkLayersActive();
+  MarkLayersActive(nsChangeHint(0));
 
   return rv;
 }
 
 nsHTMLCanvasFrame::~nsHTMLCanvasFrame()
 {
 }
 
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -1992,24 +1992,35 @@ public:
    */
   void EndDeferringInvalidatesForDisplayRoot();
 
   /**
    * Mark this frame as using active layers. This marking will time out
    * after a short period. This call does no immediate invalidation,
    * but when the mark times out, we'll invalidate the frame's overflow
    * area.
+   * @param aChangeHint nsChangeHint_UpdateTransformLayer or
+   * nsChangeHint_UpdateOpacityLayer or 0, depending on whether the change
+   * triggering the activity is a changing transform, changing opacity, or
+   * something else.
    */
-  void MarkLayersActive();
-
+  void MarkLayersActive(nsChangeHint aHint);
   /**
    * Return true if this frame is marked as needing active layers.
    */
   PRBool AreLayersMarkedActive();
-  
+  /**
+   * Return true if this frame is marked as needing active layers.
+   * @param aChangeHint nsChangeHint_UpdateTransformLayer or
+   * nsChangeHint_UpdateOpacityLayer. We return true only if
+   * a change in the transform or opacity has been recorded while layers have
+   * been marked active for this frame.
+   */
+  PRBool AreLayersMarkedActive(nsChangeHint aChangeHint);
+
   /**
    * @param aFlags see InvalidateInternal below
    */
   void InvalidateWithFlags(const nsRect& aDamageRect, PRUint32 aFlags);
 
   /**
    * Invalidate part of the frame by asking the view manager to repaint.
    * aDamageRect is allowed to extend outside the frame's bounds. We'll do the right