Bug 790505, part 4: If we're just updating the transform of a prerendered layer, then schedule an empty transaction to skip unnecessary display-list overhead. r=roc
authorChris Jones <jones.chris.g@gmail.com>
Tue, 02 Oct 2012 22:55:52 -0700
changeset 109123 6eccc1276660ca003c3e13eda6c60b5b66dcb545
parent 109122 734431216644e3f63d37aeb4294ab6cd8e260f2b
child 109124 799bd5855c27fe9a225f1d6516cbdea40891f392
child 109127 a2581a4bda012f78f7f1493465e6d5afc4ab7bcc
push id82
push usershu@rfrn.org
push dateFri, 05 Oct 2012 13:20:22 +0000
reviewersroc
bugs790505
milestone18.0a1
Bug 790505, part 4: If we're just updating the transform of a prerendered layer, then schedule an empty transaction to skip unnecessary display-list overhead. r=roc
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsDisplayList.cpp
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -7727,52 +7727,71 @@ DoApplyRenderingChangeToTree(nsIFrame* a
     // We don't need to update transforms in UpdateViewsForTree, because
     // there can't be any out-of-flows or popups that need to be transformed;
     // all out-of-flow descendants of the transformed element must also be
     // descendants of the transformed frame.
     UpdateViewsForTree(aFrame, aFrameManager,
                        nsChangeHint(aChange & (nsChangeHint_RepaintFrame |
                                                nsChangeHint_SyncFrameView |
                                                nsChangeHint_UpdateOpacityLayer)));
+    // This must be set to true if the rendering change needs to
+    // invalidate content.  If it's false, a composite-only paint
+    // (empty transaction) will be scheduled.
+    bool needInvalidatingPaint = false;
 
     // if frame has view, will already be invalidated
     if (aChange & nsChangeHint_RepaintFrame) {
       if (aFrame->IsFrameOfType(nsIFrame::eSVG) &&
           !(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG)) {
         if (aChange & nsChangeHint_UpdateEffects) {
+          needInvalidatingPaint = true;
           // Invalidate and update our area:
           nsSVGUtils::InvalidateAndScheduleReflowSVG(aFrame);
         } else {
+          needInvalidatingPaint = true;
           // Just invalidate our area:
           nsSVGUtils::InvalidateBounds(aFrame);
         }
       } else {
+        needInvalidatingPaint = true;
         aFrame->InvalidateFrameSubtree();
       }
     }
     if (aChange & nsChangeHint_UpdateOpacityLayer) {
+      // FIXME/bug 796697: we can get away with empty transactions for
+      // opacity updates in many cases.
+      needInvalidatingPaint = true;
       aFrame->MarkLayersActive(nsChangeHint_UpdateOpacityLayer);
     }
-    
     if (aChange & nsChangeHint_UpdateTransformLayer) {
       aFrame->MarkLayersActive(nsChangeHint_UpdateTransformLayer);
+      // If we're not already going to do an invalidating paint, see
+      // if we can get away with only updating the transform on a
+      // layer for this frame, and not scheduling an invalidating
+      // paint.
+      if (!needInvalidatingPaint) {
+        needInvalidatingPaint |= !aFrame->TryUpdateTransformOnly();
+      }
     }
     if (aChange & nsChangeHint_ChildrenOnlyTransform) {
+      needInvalidatingPaint = true;
       // The long comment in ProcessRestyledFrames that precedes the
       // |frame->GetContent()->GetPrimaryFrame()| and abort applies here too.
       nsIFrame *f = aFrame->GetContent()->GetPrimaryFrame();
       NS_ABORT_IF_FALSE(f->IsFrameOfType(nsIFrame::eSVG |
                                          nsIFrame::eSVGContainer),
                         "Children-only transforms only expected on SVG frames");
       nsIFrame* childFrame = f->GetFirstPrincipalChild();
       for ( ; childFrame; childFrame = childFrame->GetNextSibling()) {
         childFrame->MarkLayersActive(nsChangeHint_UpdateTransformLayer);
       }
     }
-    aFrame->SchedulePaint();
+    aFrame->SchedulePaint(needInvalidatingPaint ?
+                          nsIFrame::PAINT_DEFAULT :
+                          nsIFrame::PAINT_COMPOSITE_ONLY);
   }
 }
 
 static void
 ApplyRenderingChangeToTree(nsPresContext* aPresContext,
                            nsIFrame* aFrame,
                            nsChangeHint aChange)
 {
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -3740,16 +3740,22 @@ already_AddRefed<Layer> nsDisplayTransfo
   // Add the preserve-3d flag for this layer, BuildContainerLayerFor clears all flags,
   // so we never need to explicitely unset this flag.
   if (mFrame->Preserves3D() || mFrame->Preserves3DChildren()) {
     container->SetContentFlags(container->GetContentFlags() | Layer::CONTENT_PRESERVE_3D);
   }
 
   AddAnimationsAndTransitionsToLayer(container, aBuilder,
                                      this, eCSSProperty_transform);
+  if (ShouldPrerenderTransformedContent(aBuilder, mFrame, false)) {
+    container->SetUserData(nsIFrame::LayerIsPrerenderedDataKey(),
+                           /*the value is irrelevant*/nullptr);
+  } else {
+    container->RemoveUserData(nsIFrame::LayerIsPrerenderedDataKey());
+  }
   return container.forget();
 }
 
 nsDisplayItem::LayerState
 nsDisplayTransform::GetLayerState(nsDisplayListBuilder* aBuilder,
                                   LayerManager* aManager,
                                   const ContainerParameters& aParameters) {
   // Here we check if the *post-transform* bounds of this item are big enough
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -93,16 +93,17 @@
 #include "nsAbsoluteContainingBlock.h"
 #include "nsFontInflationData.h"
 #include "nsAnimationManager.h"
 #include "nsTransitionManager.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/css/ImageLoader.h"
+#include "mozilla/gfx/Tools.h"
 
 using namespace mozilla;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::css;
 
 // Struct containing cached metrics for box-wrapped frames.
 struct nsBoxLayoutMetrics
@@ -4877,17 +4878,58 @@ nsIFrame::InvalidateFrameWithRect(const 
     }
     rect = new nsRect();
     Properties().Set(InvalidationRect(), rect);
     AddStateBits(NS_FRAME_HAS_INVALID_RECT);
   }
 
   *rect = rect->Union(aRect);
 }
-  
+
+/*static*/ uint8_t nsIFrame::sLayerIsPrerenderedDataKey;
+
+bool
+nsIFrame::TryUpdateTransformOnly()
+{
+  Layer* layer = FrameLayerBuilder::GetDedicatedLayer(
+    this, nsDisplayItem::TYPE_TRANSFORM);
+  if (!layer || !layer->HasUserData(LayerIsPrerenderedDataKey())) {
+    // This layer isn't prerendered, so we can't correctly optimize to
+    // an empty transaction in general.
+    return false;
+  }
+
+  gfx3DMatrix transform3d;
+  if (!nsLayoutUtils::GetLayerTransformForFrame(this, &transform3d)) {
+    // We're not able to compute a layer transform that we know would
+    // be used at the next layers transaction, so we can't only update
+    // the transform and will need to schedule an invalidating paint.
+    return false;
+  }
+  gfxMatrix transform, previousTransform;
+  // FIXME/bug 796690 and 796705: in general, changes to 3D
+  // transforms, or transform changes to properties other than
+  // translation, may lead us to choose a different rendering
+  // resolution for our layer.  So if the transform is 3D or has a
+  // non-translation change, bail and schedule an invalidating paint.
+  // (We can often do better than this, for example for scale-down
+  // changes.)
+ static const gfx::Float kError = 0.0001;
+  if (!transform3d.Is2D(&transform) ||
+      !layer->GetTransform().Is2D(&previousTransform) ||
+      !gfx::FuzzyEqual(transform.xx, previousTransform.xx, kError) ||
+      !gfx::FuzzyEqual(transform.yy, previousTransform.yy, kError) ||
+      !gfx::FuzzyEqual(transform.xy, previousTransform.xy, kError) ||
+      !gfx::FuzzyEqual(transform.yx, previousTransform.yx, kError)) {
+    return false;
+  }
+  layer->SetBaseTransformForNextTransaction(transform3d);
+  return true;
+}
+
 bool 
 nsIFrame::IsInvalid(nsRect& aRect)
 {
   if (!HasAnyStateBits(NS_FRAME_NEEDS_PAINT)) {
     return false;
   }
   
   if (HasAnyStateBits(NS_FRAME_HAS_INVALID_RECT)) {
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -2227,16 +2227,33 @@ public:
   /**
    * Called when a frame is about to be removed and needs to be invalidated.
    * Normally does nothing since DLBI handles removed frames.
    * 
    */
   virtual void InvalidateFrameForRemoval() {}
 
   /**
+   * When HasUserData(frame->LayerIsPrerenderedDataKey()), then the
+   * entire overflow area of this frame has been rendered in its
+   * layer(s).
+   */
+  static void* LayerIsPrerenderedDataKey() { 
+    return &sLayerIsPrerenderedDataKey;
+  }
+  static uint8_t sLayerIsPrerenderedDataKey;
+
+   /**
+   * Try to update this frame's transform without invalidating any
+   * content.  Return true iff successful.  If unsuccessful, the
+   * caller is responsible for scheduling an invalidating paint.
+   */
+  bool TryUpdateTransformOnly();
+
+  /**
    * Checks if a frame has had InvalidateFrame() called on it since the
    * last paint.
    *
    * If true, then the invalid rect is returned in aRect, with an
    * empty rect meaning all pixels drawn by this frame should be
    * invalidated.
    * If false, aRect is left unchanged.
    */