Bug 814864 - Do high precision updates before low precision updates. r=bgirard
authorChris Lord <chrislord.net@gmail.com>
Thu, 29 Nov 2012 13:08:40 +0000
changeset 123582 13ee7ebc101a00c4b9b7a7f9abeff8bf972a3061
parent 123581 b2175d0f4770574f63d1d9345cd8887508643240
child 123583 a281a71fc4e6ae51a318545c0148f9ea5404e219
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)
reviewersbgirard
bugs814864
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 814864 - Do high precision updates before low precision updates. r=bgirard Rather than interleaving high and low precision updates, do low after high. This patch also refactors the code somewhat so that we don't needlessly recalculate the values required for repeated transactions.
gfx/layers/basic/BasicTiledThebesLayer.cpp
gfx/layers/basic/BasicTiledThebesLayer.h
--- a/gfx/layers/basic/BasicTiledThebesLayer.cpp
+++ b/gfx/layers/basic/BasicTiledThebesLayer.cpp
@@ -363,34 +363,38 @@ BasicTiledThebesLayer::ComputeProgressiv
     }
     if (NS_ABS(scrollDiffY) >= NS_ABS(scrollDiffX)) {
       tileBounds.x += incX;
     } else {
       tileBounds.y += incY;
     }
   }
 
-  bool repeatImmediately = false;
   if (!aRegionToPaint.Contains(aInvalidRegion)) {
     // The region needed to paint is larger then our progressive chunk size
     // therefore update what we want to paint and ask for a new paint transaction.
 
     // If we need to draw more than one tile to maintain coherency, make
     // sure it happens in the same transaction by requesting this work be
     // repeated immediately.
     // If this is unnecessary, the remaining work will be done tile-by-tile in
     // subsequent transactions.
     if (!drawingLowPrecision && paintInSingleTransaction) {
-      repeatImmediately = true;
-    } else {
-      BasicManager()->SetRepeatTransaction();
+      return true;
     }
+
+    BasicManager()->SetRepeatTransaction();
+    return false;
   }
 
-  return repeatImmediately;
+  // We're not repeating painting and we've not requested a repeat transaction,
+  // so the paint is finished. If there's still a separate low precision
+  // paint to do, it will get marked as unfinished later.
+  mPaintData.mPaintFinished = true;
+  return false;
 }
 
 bool
 BasicTiledThebesLayer::ProgressiveUpdate(BasicTiledLayerBuffer& aTiledBuffer,
                                          nsIntRegion& aValidRegion,
                                          nsIntRegion& aInvalidRegion,
                                          const nsIntRegion& aOldValidRegion,
                                          const gfx3DMatrix& aTransform,
@@ -438,16 +442,90 @@ BasicTiledThebesLayer::ProgressiveUpdate
     aTiledBuffer.PaintThebes(this, validOrStale, regionToPaint, aCallback, aCallbackData);
     aInvalidRegion.Sub(aInvalidRegion, regionToPaint);
   } while (repeat);
 
   return true;
 }
 
 void
+BasicTiledThebesLayer::BeginPaint()
+{
+  if (BasicManager()->IsRepeatTransaction()) {
+    return;
+  }
+
+  mPaintData.mLowPrecisionPaintCount = 0;
+  mPaintData.mPaintFinished = false;
+
+  // Calculate the transform required to convert screen space into layer space
+  mPaintData.mTransformScreenToLayer = GetEffectiveTransform();
+  // XXX Not sure if this code for intermediate surfaces is correct.
+  //     It rarely gets hit though, and shouldn't have terrible consequences
+  //     even if it is wrong.
+  for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) {
+    if (parent->UseIntermediateSurface()) {
+      mPaintData.mTransformScreenToLayer.PreMultiply(parent->GetEffectiveTransform());
+    }
+  }
+  mPaintData.mTransformScreenToLayer.Invert();
+
+  // Compute the critical display port in layer space.
+  mPaintData.mLayerCriticalDisplayPort.SetEmpty();
+  const gfx::Rect& criticalDisplayPort = GetParent()->GetFrameMetrics().mCriticalDisplayPort;
+  if (!criticalDisplayPort.IsEmpty()) {
+    gfxRect transformedCriticalDisplayPort =
+      mPaintData.mTransformScreenToLayer.TransformBounds(
+        gfxRect(criticalDisplayPort.x, criticalDisplayPort.y,
+                criticalDisplayPort.width, criticalDisplayPort.height));
+    transformedCriticalDisplayPort.RoundOut();
+    mPaintData.mLayerCriticalDisplayPort = nsIntRect(transformedCriticalDisplayPort.x,
+                                             transformedCriticalDisplayPort.y,
+                                             transformedCriticalDisplayPort.width,
+                                             transformedCriticalDisplayPort.height);
+  }
+
+  // Calculate the frame resolution.
+  mPaintData.mResolution.SizeTo(1, 1);
+  for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) {
+    const FrameMetrics& metrics = parent->GetFrameMetrics();
+    mPaintData.mResolution.width *= metrics.mResolution.width;
+    mPaintData.mResolution.height *= metrics.mResolution.height;
+  }
+
+  // Calculate the scroll offset since the last transaction, and the
+  // composition bounds.
+  mPaintData.mCompositionBounds.SetEmpty();
+  mPaintData.mScrollOffset.MoveTo(0, 0);
+  Layer* primaryScrollable = BasicManager()->GetPrimaryScrollableLayer();
+  if (primaryScrollable) {
+    const FrameMetrics& metrics = primaryScrollable->AsContainerLayer()->GetFrameMetrics();
+    mPaintData.mScrollOffset = metrics.mScrollOffset;
+    gfxRect transformedViewport = mPaintData.mTransformScreenToLayer.TransformBounds(
+      gfxRect(metrics.mCompositionBounds.x, metrics.mCompositionBounds.y,
+              metrics.mCompositionBounds.width, metrics.mCompositionBounds.height));
+    transformedViewport.RoundOut();
+    mPaintData.mCompositionBounds =
+      nsIntRect(transformedViewport.x, transformedViewport.y,
+                transformedViewport.width, transformedViewport.height);
+  }
+}
+
+void
+BasicTiledThebesLayer::EndPaint(bool aFinish)
+{
+  if (!aFinish && !mPaintData.mPaintFinished) {
+    return;
+  }
+
+  mLastScrollOffset = mPaintData.mScrollOffset;
+  mPaintData.mPaintFinished = true;
+}
+
+void
 BasicTiledThebesLayer::PaintThebes(gfxContext* aContext,
                                    Layer* aMaskLayer,
                                    LayerManager::DrawThebesLayerCallback aCallback,
                                    void* aCallbackData,
                                    ReadbackProcessor* aReadback)
 {
   if (!aCallback) {
     BasicManager()->SetTransactionIncomplete();
@@ -463,200 +541,197 @@ BasicTiledThebesLayer::PaintThebes(gfxCo
     mValidRegion = nsIntRegion();
   }
   if (mLowPrecisionTiledBuffer.HasFormatChanged(this)) {
     mLowPrecisionValidRegion = nsIntRegion();
   }
 
   nsIntRegion invalidRegion = mVisibleRegion;
   invalidRegion.Sub(invalidRegion, mValidRegion);
-  if (invalidRegion.IsEmpty())
+  if (invalidRegion.IsEmpty()) {
+    EndPaint(true);
     return;
+  }
 
-  // Calculate the transform required to convert screen space into layer space
-  gfx3DMatrix transform = GetEffectiveTransform();
-  // XXX Not sure if this code for intermediate surfaces is correct.
-  //     It rarely gets hit though, and shouldn't have terrible consequences
-  //     even if it is wrong.
-  for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) {
-    if (parent->UseIntermediateSurface()) {
-      transform.PreMultiply(parent->GetEffectiveTransform());
-    }
+  // Fast path for no progressive updates, no low-precision updates and no
+  // critical display-port set.
+  if (!gfxPlatform::UseProgressiveTilePainting() &&
+      !gfxPlatform::UseLowPrecisionBuffer() &&
+      GetParent()->GetFrameMetrics().mCriticalDisplayPort.IsEmpty()) {
+    mValidRegion = mVisibleRegion;
+    mTiledBuffer.PaintThebes(this, mValidRegion, invalidRegion, aCallback, aCallbackData);
+    mTiledBuffer.ReadLock();
+
+    static_cast<BasicImplData*>(aMaskLayer->ImplData())->Paint(aContext, nullptr);
+
+    // Create a heap copy owned and released by the compositor. This is needed
+    // since we're sending this over an async message and content needs to be
+    // be able to modify the tiled buffer in the next transaction.
+    // TODO: Remove me once Bug 747811 lands.
+    BasicTiledLayerBuffer *heapCopy = new BasicTiledLayerBuffer(mTiledBuffer);
+    BasicManager()->PaintedTiledLayerBuffer(BasicManager()->Hold(this), heapCopy);
+    mTiledBuffer.ClearPaintedRegion();
+
+    return;
   }
-  transform.Invert();
 
-  nsIntRect layerDisplayPort;
-  nsIntRegion lowPrecisionInvalidRegion;
-  const gfx::Rect& criticalDisplayPort = GetParent()->GetFrameMetrics().mCriticalDisplayPort;
-  if (!criticalDisplayPort.IsEmpty()) {
-    // Calculate the invalid region for the low precision buffer
-    lowPrecisionInvalidRegion.Sub(mVisibleRegion, mLowPrecisionValidRegion);
+  // Calculate everything we need to perform the paint.
+  BeginPaint();
+  if (mPaintData.mPaintFinished) {
+    return;
+  }
+
+  // Make sure that tiles that fall outside of the visible region are
+  // discarded on the first update.
+  if (!BasicManager()->IsRepeatTransaction()) {
+    mValidRegion.And(mValidRegion, mVisibleRegion);
+  }
 
-    // Find the critical display port in layer space.
-    gfxRect transformedCriticalDisplayPort = transform.TransformBounds(
-      gfxRect(criticalDisplayPort.x, criticalDisplayPort.y,
-              criticalDisplayPort.width, criticalDisplayPort.height));
-    transformedCriticalDisplayPort.RoundOut();
-    layerDisplayPort = nsIntRect(transformedCriticalDisplayPort.x,
-                                 transformedCriticalDisplayPort.y,
-                                 transformedCriticalDisplayPort.width,
-                                 transformedCriticalDisplayPort.height);
+  nsIntRegion lowPrecisionInvalidRegion;
+  if (!mPaintData.mLayerCriticalDisplayPort.IsEmpty()) {
+    // Make sure that tiles that fall outside of the critical displayport are
+    // discarded on the first update.
+    if (!BasicManager()->IsRepeatTransaction()) {
+      mValidRegion.And(mValidRegion, mPaintData.mLayerCriticalDisplayPort);
+    }
+
+    if (gfxPlatform::UseLowPrecisionBuffer()) {
+      // Calculate the invalid region for the low precision buffer
+      lowPrecisionInvalidRegion.Sub(mVisibleRegion, mLowPrecisionValidRegion);
+
+      // Remove the valid region from the low precision valid region (we don't
+      // validate this part of the low precision buffer).
+      lowPrecisionInvalidRegion.Sub(lowPrecisionInvalidRegion, mValidRegion);
+    }
 
     // Clip the invalid region to the critical display-port
-    invalidRegion.And(invalidRegion, layerDisplayPort);
+    invalidRegion.And(invalidRegion, mPaintData.mLayerCriticalDisplayPort);
     if (invalidRegion.IsEmpty() && lowPrecisionInvalidRegion.IsEmpty()) {
+      EndPaint(true);
       return;
     }
   }
 
-  gfxSize resolution(1, 1);
-  for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) {
-    const FrameMetrics& metrics = parent->GetFrameMetrics();
-    resolution.width *= metrics.mResolution.width;
-    resolution.height *= metrics.mResolution.height;
-  }
-
-  // Calculate the scroll offset since the last transaction, and the
-  // composition bounds.
-  nsIntRect compositionBounds;
-  gfx::Point scrollOffset(0, 0);
-  Layer* primaryScrollable = BasicManager()->GetPrimaryScrollableLayer();
-  if (primaryScrollable) {
-    const FrameMetrics& metrics = primaryScrollable->AsContainerLayer()->GetFrameMetrics();
-    scrollOffset = metrics.mScrollOffset;
-    gfxRect transformedViewport = transform.TransformBounds(
-      gfxRect(metrics.mCompositionBounds.x, metrics.mCompositionBounds.y,
-              metrics.mCompositionBounds.width, metrics.mCompositionBounds.height));
-    transformedViewport.RoundOut();
-    compositionBounds = nsIntRect(transformedViewport.x, transformedViewport.y,
-                                  transformedViewport.width, transformedViewport.height);
-  }
-
-  if (!invalidRegion.IsEmpty()) {
+  if (!invalidRegion.IsEmpty() && mPaintData.mLowPrecisionPaintCount == 0) {
     bool updatedBuffer = false;
     // Only draw progressively when the resolution is unchanged.
     if (gfxPlatform::UseProgressiveTilePainting() &&
         !BasicManager()->HasShadowTarget() &&
-        mTiledBuffer.GetFrameResolution() == resolution) {
+        mTiledBuffer.GetFrameResolution() == mPaintData.mResolution) {
       // Store the old valid region, then clear it before painting.
       // We clip the old valid region to the visible region, as it only gets
       // used to decide stale content (currently valid and previously visible)
       nsIntRegion oldValidRegion = mTiledBuffer.GetValidRegion();
       oldValidRegion.And(oldValidRegion, mVisibleRegion);
-      if (!layerDisplayPort.IsEmpty()) {
-        oldValidRegion.And(oldValidRegion, layerDisplayPort);
-      }
-
-      // Make sure that tiles that fall outside of the visible region are
-      // discarded on the first update.
-      if (!BasicManager()->IsRepeatTransaction()) {
-        mValidRegion.And(mValidRegion, mVisibleRegion);
-        if (!layerDisplayPort.IsEmpty()) {
-          mValidRegion.And(mValidRegion, layerDisplayPort);
-        }
+      if (!mPaintData.mLayerCriticalDisplayPort.IsEmpty()) {
+        oldValidRegion.And(oldValidRegion, mPaintData.mLayerCriticalDisplayPort);
       }
 
       updatedBuffer =
         ProgressiveUpdate(mTiledBuffer, mValidRegion, invalidRegion,
-                          oldValidRegion, transform, compositionBounds,
-                          scrollOffset, resolution, aCallback, aCallbackData);
+                          oldValidRegion, mPaintData.mTransformScreenToLayer,
+                          mPaintData.mCompositionBounds, mPaintData.mScrollOffset,
+                          mPaintData.mResolution, aCallback, aCallbackData);
     } else {
       updatedBuffer = true;
-      mTiledBuffer.SetFrameResolution(resolution);
+      mTiledBuffer.SetFrameResolution(mPaintData.mResolution);
       mValidRegion = mVisibleRegion;
-      if (!layerDisplayPort.IsEmpty()) {
-        mValidRegion.And(mValidRegion, layerDisplayPort);
+      if (!mPaintData.mLayerCriticalDisplayPort.IsEmpty()) {
+        mValidRegion.And(mValidRegion, mPaintData.mLayerCriticalDisplayPort);
       }
       mTiledBuffer.PaintThebes(this, mValidRegion, invalidRegion, aCallback, aCallbackData);
     }
 
     if (updatedBuffer) {
+      mFirstPaint = false;
       mTiledBuffer.ReadLock();
 
       // Only paint the mask layer on the first transaction.
       if (aMaskLayer && !BasicManager()->IsRepeatTransaction()) {
         static_cast<BasicImplData*>(aMaskLayer->ImplData())
           ->Paint(aContext, nullptr);
       }
 
-      // Create a heap copy owned and released by the compositor. This is needed
-      // since we're sending this over an async message and content needs to be
-      // be able to modify the tiled buffer in the next transaction.
       // TODO: Remove me once Bug 747811 lands.
       BasicTiledLayerBuffer *heapCopy = new BasicTiledLayerBuffer(mTiledBuffer);
 
       BasicManager()->PaintedTiledLayerBuffer(BasicManager()->Hold(this), heapCopy);
       mTiledBuffer.ClearPaintedRegion();
+
+      // If there are low precision updates, mark the paint as unfinished and
+      // request a repeat transaction.
+      if (!lowPrecisionInvalidRegion.IsEmpty() && mPaintData.mPaintFinished) {
+        BasicManager()->SetRepeatTransaction();
+        mPaintData.mLowPrecisionPaintCount = 1;
+        mPaintData.mPaintFinished = false;
+      }
+
+      // Return so that low precision updates aren't performed in the same
+      // transaction as high-precision updates.
+      EndPaint(false);
+      return;
     }
   }
 
-  // If we have a critical display-port defined, render the full display-port
-  // progressively in the low-precision tiled buffer.
+  // Render the low precision buffer, if there's area to invalidate and the
+  // visible region is larger than the critical display port.
   bool updatedLowPrecision = false;
-  if (gfxPlatform::UseLowPrecisionBuffer() &&
-      !criticalDisplayPort.IsEmpty() &&
-      !nsIntRegion(layerDisplayPort).Contains(mVisibleRegion)) {
+  if (!lowPrecisionInvalidRegion.IsEmpty() &&
+      !nsIntRegion(mPaintData.mLayerCriticalDisplayPort).Contains(mVisibleRegion)) {
     nsIntRegion oldValidRegion = mLowPrecisionTiledBuffer.GetValidRegion();
     oldValidRegion.And(oldValidRegion, mVisibleRegion);
 
     // If the frame resolution has changed, invalidate the buffer
-    if (mLowPrecisionTiledBuffer.GetFrameResolution() != resolution) {
+    if (mLowPrecisionTiledBuffer.GetFrameResolution() != mPaintData.mResolution) {
       if (!mLowPrecisionValidRegion.IsEmpty()) {
         updatedLowPrecision = true;
       }
       oldValidRegion.SetEmpty();
       mLowPrecisionValidRegion.SetEmpty();
-      mLowPrecisionTiledBuffer.SetFrameResolution(resolution);
+      mLowPrecisionTiledBuffer.SetFrameResolution(mPaintData.mResolution);
     }
 
     // Invalidate previously valid content that is no longer visible
-    if (!BasicManager()->IsRepeatTransaction()) {
+    if (mPaintData.mLowPrecisionPaintCount == 1) {
       mLowPrecisionValidRegion.And(mLowPrecisionValidRegion, mVisibleRegion);
     }
+    mPaintData.mLowPrecisionPaintCount++;
 
     // Remove the valid high-precision region from the invalid low-precision
     // region. We don't want to spend time drawing things twice.
-    nsIntRegion invalidHighPrecisionIntersect;
-    invalidHighPrecisionIntersect.And(lowPrecisionInvalidRegion, mValidRegion);
-    lowPrecisionInvalidRegion.Sub(lowPrecisionInvalidRegion, invalidHighPrecisionIntersect);
+    lowPrecisionInvalidRegion.Sub(lowPrecisionInvalidRegion, mValidRegion);
 
     if (!lowPrecisionInvalidRegion.IsEmpty()) {
       updatedLowPrecision =
         ProgressiveUpdate(mLowPrecisionTiledBuffer, mLowPrecisionValidRegion,
-                          lowPrecisionInvalidRegion, oldValidRegion, transform,
-                          compositionBounds, scrollOffset, resolution, aCallback,
-                          aCallbackData);
+                          lowPrecisionInvalidRegion, oldValidRegion,
+                          mPaintData.mTransformScreenToLayer,
+                          mPaintData.mCompositionBounds, mPaintData.mScrollOffset,
+                          mPaintData.mResolution, aCallback, aCallbackData);
     }
-
-    // Re-add the high-precision valid region intersection so that we can
-    // maintain coherency when the valid region changes.
-    lowPrecisionInvalidRegion.Or(lowPrecisionInvalidRegion, invalidHighPrecisionIntersect);
   } else if (!mLowPrecisionValidRegion.IsEmpty()) {
     // Clear the low precision tiled buffer
     updatedLowPrecision = true;
     mLowPrecisionValidRegion.SetEmpty();
     mLowPrecisionTiledBuffer.PaintThebes(this, mLowPrecisionValidRegion,
                                          mLowPrecisionValidRegion, aCallback,
                                          aCallbackData);
   }
 
   // We send a Painted callback if we clear the valid region of the low
   // precision buffer, so that the shadow buffer's valid region can be updated
   // and the associated resources can be freed.
   if (updatedLowPrecision) {
     mLowPrecisionTiledBuffer.ReadLock();
+    // TODO: Remove me once Bug 747811 lands.
     BasicTiledLayerBuffer *heapCopy = new BasicTiledLayerBuffer(mLowPrecisionTiledBuffer);
 
     // The GL layer manager uses the buffer resolution to distinguish calls
     // to PaintedTiledLayerBuffer.
     BasicManager()->PaintedTiledLayerBuffer(BasicManager()->Hold(this), heapCopy);
     mLowPrecisionTiledBuffer.ClearPaintedRegion();
   }
 
-  // The transaction is completed, store the last scroll offset.
-  if (!BasicManager()->GetRepeatTransaction()) {
-    mLastScrollOffset = scrollOffset;
-  }
-  mFirstPaint = false;
+  EndPaint(false);
 }
 
 } // mozilla
 } // layers
--- a/gfx/layers/basic/BasicTiledThebesLayer.h
+++ b/gfx/layers/basic/BasicTiledThebesLayer.h
@@ -62,16 +62,30 @@ struct BasicTiledLayerTile {
   void ReadUnlock() {
     mSurface->ReadUnlock();
   }
   void ReadLock() {
     mSurface->ReadLock();
   }
 };
 
+/**
+ * This struct stores all the data necessary to perform a paint so that it
+ * doesn't need to be recalculated on every repeated transaction.
+ */
+struct BasicTiledLayerPaintData {
+  gfx::Point mScrollOffset;
+  gfx3DMatrix mTransformScreenToLayer;
+  nsIntRect mLayerCriticalDisplayPort;
+  gfxSize mResolution;
+  nsIntRect mCompositionBounds;
+  uint16_t mLowPrecisionPaintCount;
+  bool mPaintFinished : 1;
+};
+
 class BasicTiledThebesLayer;
 
 /**
  * Provide an instance of TiledLayerBuffer backed by image surfaces.
  * This buffer provides an implementation to ValidateTile using a
  * thebes callback and can support painting using a single paint buffer
  * which is much faster then painting directly into the tiles.
  */
@@ -260,20 +274,34 @@ private:
                          const nsIntRegion& aOldValidRegion,
                          const gfx3DMatrix& aTransform,
                          const nsIntRect& aCompositionBounds,
                          const gfx::Point& aScrollOffset,
                          const gfxSize& aResolution,
                          LayerManager::DrawThebesLayerCallback aCallback,
                          void* aCallbackData);
 
+  /**
+   * For the initial PaintThebes of a transaction, calculates all the data
+   * needed for that paint and any repeated transactions.
+   */
+  void BeginPaint();
+
+  /**
+   * When a paint ends, updates any data necessary to persist until the next
+   * paint. If aFinish is true, this will cause the paint to be marked as
+   * finished.
+   */
+  void EndPaint(bool aFinish);
+
   // Members
   BasicTiledLayerBuffer mTiledBuffer;
   BasicTiledLayerBuffer mLowPrecisionTiledBuffer;
   nsIntRegion mLowPrecisionValidRegion;
   gfx::Point mLastScrollOffset;
   bool mFirstPaint;
+  BasicTiledLayerPaintData mPaintData;
 };
 
 } // layers
 } // mozilla
 
 #endif