Bug 783368 - Add a low precision buffer to tiled thebes layers. r=bgirard,kats
authorChris Lord <chrislord.net@gmail.com>
Wed, 21 Nov 2012 22:34:19 +0000
changeset 113946 f74b22565be08b34b0ea93a0c14ac33fffef7763
parent 113945 13917d4b99b8367eeabca3a77b86ba6aadf08254
child 113947 7d09617883c7457cd72e3b125c7a6addf4a3eeec
push id18466
push userchrislord.net@gmail.com
push dateWed, 21 Nov 2012 22:34:39 +0000
treeherdermozilla-inbound@c4d013240eac [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgirard, kats
bugs783368
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 783368 - Add a low precision buffer to tiled thebes layers. r=bgirard,kats If there is a critical display port set, render the area outside it into a lower precision buffer.
gfx/layers/TiledLayerBuffer.h
gfx/layers/basic/BasicLayers.h
gfx/layers/basic/BasicTiledThebesLayer.cpp
gfx/layers/basic/BasicTiledThebesLayer.h
gfx/layers/opengl/TiledThebesLayerOGL.cpp
gfx/layers/opengl/TiledThebesLayerOGL.h
mobile/android/chrome/content/browser.js
--- a/gfx/layers/TiledLayerBuffer.h
+++ b/gfx/layers/TiledLayerBuffer.h
@@ -138,16 +138,17 @@ public:
   void SetResolution(float aResolution) {
     if (mResolution == aResolution) {
       return;
     }
 
     Update(nsIntRegion(), nsIntRegion());
     mResolution = aResolution;
   }
+  bool IsLowPrecision() const { return mResolution < 1; }
 
 protected:
   // The implementor should call Update() to change
   // the new valid region. This implementation will call
   // validateTile on each tile that is dirty, which is left
   // to the implementor.
   void Update(const nsIntRegion& aNewValidRegion, const nsIntRegion& aPaintRegion);
 
--- a/gfx/layers/basic/BasicLayers.h
+++ b/gfx/layers/basic/BasicLayers.h
@@ -271,16 +271,17 @@ public:
 
   virtual void SetIsFirstPaint() MOZ_OVERRIDE;
 
   // Drop cached resources and ask our shadow manager to do the same,
   // if we have one.
   virtual void ClearCachedResources(Layer* aSubtree = nullptr) MOZ_OVERRIDE;
 
   void SetRepeatTransaction() { mRepeatTransaction = true; }
+  bool GetRepeatTransaction() { return mRepeatTransaction; }
 
   bool IsRepeatTransaction() { return mIsRepeatTransaction; }
 
   /**
    * Called for each iteration of a progressive tile update. Fills
    * aViewport, aScaleX and aScaleY with the current scale and viewport
    * being used to composite the layers in this manager, to determine what area
    * intersects with the target render rectangle.
--- a/gfx/layers/basic/BasicTiledThebesLayer.cpp
+++ b/gfx/layers/basic/BasicTiledThebesLayer.cpp
@@ -259,26 +259,32 @@ RoundedTransformViewportBounds(const gfx
 
   return nsIntRect((int32_t)floor(transformedViewport.x),
                    (int32_t)floor(transformedViewport.y),
                    (int32_t)ceil(transformedViewport.width),
                    (int32_t)ceil(transformedViewport.height));
 }
 
 bool
-BasicTiledThebesLayer::ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion,
+BasicTiledThebesLayer::ComputeProgressiveUpdateRegion(BasicTiledLayerBuffer& aTiledBuffer,
+                                                      const nsIntRegion& aInvalidRegion,
                                                       const nsIntRegion& aOldValidRegion,
                                                       nsIntRegion& aRegionToPaint,
                                                       const gfx3DMatrix& aTransform,
                                                       const gfx::Point& aScrollOffset,
                                                       const gfxSize& aResolution,
                                                       bool aIsRepeated)
 {
   aRegionToPaint = aInvalidRegion;
 
+  // If this is a low precision buffer, we force progressive updates. The
+  // assumption is that the contents is less important, so visual coherency
+  // is lower priority than speed.
+  bool forceProgressive = aTiledBuffer.IsLowPrecision();
+
   // Find out if we have any non-stale content to update.
   nsIntRegion freshRegion;
   if (!mFirstPaint) {
     freshRegion.And(aInvalidRegion, aOldValidRegion);
     freshRegion.Sub(aInvalidRegion, freshRegion);
   }
 
   // Find out the current view transform to determine which tiles to draw
@@ -311,30 +317,30 @@ BasicTiledThebesLayer::ComputeProgressiv
   }
 
   // The following code decides what order to draw tiles in, based on the
   // current scroll direction of the primary scrollable layer.
   NS_ASSERTION(!aRegionToPaint.IsEmpty(), "Unexpectedly empty paint region!");
   nsIntRect paintBounds = aRegionToPaint.GetBounds();
 
   int startX, incX, startY, incY;
-  int tileLength = mTiledBuffer.GetScaledTileLength();
+  int tileLength = aTiledBuffer.GetScaledTileLength();
   if (aScrollOffset.x >= mLastScrollOffset.x) {
-    startX = mTiledBuffer.RoundDownToTileEdge(paintBounds.x);
+    startX = aTiledBuffer.RoundDownToTileEdge(paintBounds.x);
     incX = tileLength;
   } else {
-    startX = mTiledBuffer.RoundDownToTileEdge(paintBounds.XMost() - 1);
+    startX = aTiledBuffer.RoundDownToTileEdge(paintBounds.XMost() - 1);
     incX = -tileLength;
   }
 
   if (aScrollOffset.y >= mLastScrollOffset.y) {
-    startY = mTiledBuffer.RoundDownToTileEdge(paintBounds.y);
+    startY = aTiledBuffer.RoundDownToTileEdge(paintBounds.y);
     incY = tileLength;
   } else {
-    startY = mTiledBuffer.RoundDownToTileEdge(paintBounds.YMost() - 1);
+    startY = aTiledBuffer.RoundDownToTileEdge(paintBounds.YMost() - 1);
     incY = -tileLength;
   }
 
   // Find a tile to draw.
   nsIntRect tileBounds(startX, startY, tileLength, tileLength);
   int32_t scrollDiffX = aScrollOffset.x - mLastScrollOffset.x;
   int32_t scrollDiffY = aScrollOffset.y - mLastScrollOffset.y;
   // This loop will always terminate, as there is at least one tile area
@@ -356,29 +362,78 @@ BasicTiledThebesLayer::ComputeProgressiv
   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're drawing stale, visible content, make sure that it happens
     // in one go by repeating this work without calling the painted
     // callback. The remaining content is then drawn tile-by-tile in
     // multiple transactions.
-    if (paintVisible && drawingStale) {
+    if (!forceProgressive && paintVisible && drawingStale) {
       repeatImmediately = true;
     } else {
       BasicManager()->SetRepeatTransaction();
     }
-  } else {
-    // The transaction is completed, store the last scroll offset.
-    mLastScrollOffset = aScrollOffset;
   }
 
   return repeatImmediately;
 }
 
+bool
+BasicTiledThebesLayer::ProgressiveUpdate(BasicTiledLayerBuffer& aTiledBuffer,
+                                         nsIntRegion& aValidRegion,
+                                         nsIntRegion& aInvalidRegion,
+                                         const nsIntRegion& aOldValidRegion,
+                                         const gfx3DMatrix& aTransform,
+                                         const gfx::Point& aScrollOffset,
+                                         const gfxSize& aResolution,
+                                         LayerManager::DrawThebesLayerCallback aCallback,
+                                         void* aCallbackData)
+{
+  bool repeat = false;
+  do {
+    // Compute the region that should be updated. Repeat as many times as
+    // is required.
+    nsIntRegion regionToPaint;
+    repeat = ComputeProgressiveUpdateRegion(aTiledBuffer,
+                                            aInvalidRegion,
+                                            aOldValidRegion,
+                                            regionToPaint,
+                                            aTransform,
+                                            aScrollOffset,
+                                            aResolution,
+                                            repeat);
+
+    // There's no further work to be done, return if nothing has been
+    // drawn, or give what has been drawn to the shadow layer to upload.
+    if (regionToPaint.IsEmpty()) {
+      if (repeat) {
+        break;
+      } else {
+        return false;
+      }
+    }
+
+    // Keep track of what we're about to refresh.
+    aValidRegion.Or(aValidRegion, regionToPaint);
+
+    // aValidRegion may have been altered by InvalidateRegion, but we still
+    // want to display stale content until it gets progressively updated.
+    // Create a region that includes stale content.
+    nsIntRegion validOrStale;
+    validOrStale.Or(aValidRegion, aOldValidRegion);
+
+    // Paint the computed region and subtract it from the invalid region.
+    aTiledBuffer.PaintThebes(this, validOrStale, regionToPaint, aCallback, aCallbackData);
+    aInvalidRegion.Sub(aInvalidRegion, regionToPaint);
+  } while (repeat);
+
+  return true;
+}
+
 void
 BasicTiledThebesLayer::PaintThebes(gfxContext* aContext,
                                    Layer* aMaskLayer,
                                    LayerManager::DrawThebesLayerCallback aCallback,
                                    void* aCallbackData,
                                    ReadbackProcessor* aReadback)
 {
   if (!aCallback) {
@@ -389,16 +444,19 @@ BasicTiledThebesLayer::PaintThebes(gfxCo
   if (!HasShadow()) {
     NS_ASSERTION(false, "Shadow requested for painting\n");
     return;
   }
 
   if (mTiledBuffer.HasFormatChanged(this)) {
     mValidRegion = nsIntRegion();
   }
+  if (mLowPrecisionTiledBuffer.HasFormatChanged(this)) {
+    mLowPrecisionValidRegion = nsIntRegion();
+  }
 
   nsIntRegion invalidRegion = mVisibleRegion;
   invalidRegion.Sub(invalidRegion, mValidRegion);
   if (invalidRegion.IsEmpty())
     return;
 
   // Calculate the transform required to convert screen space into layer space
   gfx3DMatrix transform = GetEffectiveTransform();
@@ -433,85 +491,51 @@ BasicTiledThebesLayer::PaintThebes(gfxCo
 
   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.
+  gfx::Point scrollOffset(0, 0);
+  Layer* primaryScrollable = BasicManager()->GetPrimaryScrollableLayer();
+  if (primaryScrollable) {
+    const FrameMetrics& metrics = primaryScrollable->AsContainerLayer()->GetFrameMetrics();
+    scrollOffset = metrics.mScrollOffset;
+  }
+
   // Only draw progressively when the resolution is unchanged.
   if (gfxPlatform::UseProgressiveTilePainting() &&
       !BasicManager()->HasShadowTarget() &&
       mTiledBuffer.GetFrameResolution() == resolution) {
     // 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);
     }
-    mTiledBuffer.ClearPaintedRegion();
 
     // 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);
       }
     }
 
-    // Calculate the scroll offset since the last transaction.
-    gfx::Point scrollOffset(0, 0);
-    Layer* primaryScrollable = BasicManager()->GetPrimaryScrollableLayer();
-    if (primaryScrollable) {
-      const FrameMetrics& metrics = primaryScrollable->AsContainerLayer()->GetFrameMetrics();
-      scrollOffset = metrics.mScrollOffset;
-    }
-
-    bool repeat = false;
-    do {
-      // Compute the region that should be updated. Repeat as many times as
-      // is required.
-      nsIntRegion regionToPaint;
-      repeat = ComputeProgressiveUpdateRegion(invalidRegion,
-                                              oldValidRegion,
-                                              regionToPaint,
-                                              transform,
-                                              scrollOffset,
-                                              resolution,
-                                              repeat);
-
-      // There's no further work to be done, return if nothing has been
-      // drawn, or give what has been drawn to the shadow layer to upload.
-      if (regionToPaint.IsEmpty()) {
-        if (repeat) {
-          break;
-        } else {
-          return;
-        }
-      }
-
-      // Keep track of what we're about to refresh.
-      mValidRegion.Or(mValidRegion, regionToPaint);
-
-      // mValidRegion would have been altered by InvalidateRegion, but we still
-      // want to display stale content until it gets progressively updated.
-      // Create a region that includes stale content.
-      nsIntRegion validOrStale;
-      validOrStale.Or(mValidRegion, oldValidRegion);
-
-      // Paint the computed region and subtract it from the invalid region.
-      mTiledBuffer.PaintThebes(this, validOrStale, regionToPaint, aCallback, aCallbackData);
-      invalidRegion.Sub(invalidRegion, regionToPaint);
-    } while (repeat);
+    if (!ProgressiveUpdate(mTiledBuffer, mValidRegion, invalidRegion,
+                           oldValidRegion, transform, scrollOffset, resolution,
+                           aCallback, aCallbackData))
+      return;
   } else {
-    mTiledBuffer.ClearPaintedRegion();
     mTiledBuffer.SetFrameResolution(resolution);
     mValidRegion = mVisibleRegion;
     if (!layerDisplayPort.IsEmpty()) {
       mValidRegion.And(mValidRegion, layerDisplayPort);
     }
     mTiledBuffer.PaintThebes(this, mValidRegion, invalidRegion, aCallback, aCallbackData);
   }
 
@@ -525,13 +549,74 @@ BasicTiledThebesLayer::PaintThebes(gfxCo
 
   // 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 we have a critical display-port defined, render the full display-port
+  // progressively in the low-precision tiled buffer.
+  bool clearedLowPrecision = false;
+  bool updatedLowPrecision = false;
+  if (!criticalDisplayPort.IsEmpty() &&
+      !nsIntRegion(layerDisplayPort).Contains(mVisibleRegion)) {
+    nsIntRegion oldValidRegion = mLowPrecisionTiledBuffer.GetValidRegion();
+    oldValidRegion.And(oldValidRegion, mVisibleRegion);
+
+    // If the frame resolution has changed, invalidate the buffer
+    if (mLowPrecisionTiledBuffer.GetFrameResolution() != resolution) {
+      if (!mLowPrecisionValidRegion.IsEmpty()) {
+        clearedLowPrecision = true;
+      }
+      oldValidRegion.SetEmpty();
+      mLowPrecisionValidRegion.SetEmpty();
+      mLowPrecisionTiledBuffer.SetFrameResolution(resolution);
+    }
+
+    // Invalidate previously valid content that is no longer visible
+    if (!BasicManager()->IsRepeatTransaction()) {
+      mLowPrecisionValidRegion.And(mLowPrecisionValidRegion, mVisibleRegion);
+    }
+
+    nsIntRegion lowPrecisionInvalidRegion;
+    lowPrecisionInvalidRegion.Sub(mVisibleRegion, mLowPrecisionValidRegion);
+
+    if (!lowPrecisionInvalidRegion.IsEmpty()) {
+      updatedLowPrecision =
+        ProgressiveUpdate(mLowPrecisionTiledBuffer, mLowPrecisionValidRegion,
+                          lowPrecisionInvalidRegion, oldValidRegion, transform,
+                          scrollOffset, resolution, aCallback, aCallbackData);
+    }
+  } else if (!mLowPrecisionValidRegion.IsEmpty()) {
+    // Clear the low precision tiled buffer
+    clearedLowPrecision = 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 (clearedLowPrecision || updatedLowPrecision) {
+    mLowPrecisionTiledBuffer.ReadLock();
+    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;
 }
 
 } // mozilla
 } // layers
--- a/gfx/layers/basic/BasicTiledThebesLayer.h
+++ b/gfx/layers/basic/BasicTiledThebesLayer.h
@@ -7,16 +7,18 @@
 
 #include "TiledLayerBuffer.h"
 #include "gfxReusableSurfaceWrapper.h"
 #include "mozilla/layers/ShadowLayers.h"
 #include "BasicLayers.h"
 #include "BasicImplData.h"
 #include <algorithm>
 
+#define LOW_PRECISION_RESOLUTION 0.25
+
 namespace mozilla {
 namespace layers {
 
 /**
  * Represent a single tile in tiled buffer. It's backed
  * by a gfxReusableSurfaceWrapper that implements a
  * copy-on-write mechanism while locked. The tile should be
  * locked before being sent to the compositor and unlocked
@@ -161,29 +163,31 @@ class BasicTiledThebesLayer : public The
 
 public:
   BasicTiledThebesLayer(BasicShadowLayerManager* const aManager)
     : ThebesLayer(aManager, static_cast<BasicImplData*>(this))
     , mLastScrollOffset(0, 0)
     , mFirstPaint(true)
   {
     MOZ_COUNT_CTOR(BasicTiledThebesLayer);
+    mLowPrecisionTiledBuffer.SetResolution(LOW_PRECISION_RESOLUTION);
   }
 
   ~BasicTiledThebesLayer()
   {
     MOZ_COUNT_DTOR(BasicTiledThebesLayer);
   }
 
 
   // Thebes Layer
   virtual Layer* AsLayer() { return this; }
   virtual void InvalidateRegion(const nsIntRegion& aRegion) {
     mInvalidRegion.Or(mInvalidRegion, aRegion);
     mValidRegion.Sub(mValidRegion, aRegion);
+    mLowPrecisionValidRegion.Sub(mLowPrecisionValidRegion, aRegion);
   }
 
   // Shadow methods
   virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs);
   virtual ShadowableLayer* AsShadowableLayer() { return this; }
 
   virtual void Disconnect()
   {
@@ -215,40 +219,56 @@ private:
 
   /**
    * Calculates the region to update in a single progressive update transaction.
    * This employs some heuristics to update the most 'sensible' region to
    * update at this point in time, and how large an update should be performed
    * at once to maintain visual coherency.
    *
    * aInvalidRegion is the current invalid region.
-   * aOldValidRegion is the valid region of mTiledBuffer at the beginning of the
+   * aOldValidRegion is the valid region of aTiledBuffer at the beginning of the
    * current transaction.
    * aRegionToPaint will be filled with the region to update. This may be empty,
    * which indicates that there is no more work to do.
    * aTransform is the transform required to convert from screen-space to
    * layer-space.
    * aScrollOffset is the current scroll offset of the primary scrollable layer.
    * aResolution is the render resolution of the layer.
    * aIsRepeated should be true if this function has already been called during
    * this transaction.
    *
    * Returns true if it should be called again, false otherwise. In the case
    * that aRegionToPaint is empty, this will return aIsRepeated for convenience.
    */
-  bool ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion,
+  bool ComputeProgressiveUpdateRegion(BasicTiledLayerBuffer& aTiledBuffer,
+                                      const nsIntRegion& aInvalidRegion,
                                       const nsIntRegion& aOldValidRegion,
                                       nsIntRegion& aRegionToPaint,
                                       const gfx3DMatrix& aTransform,
                                       const gfx::Point& aScrollOffset,
                                       const gfxSize& aResolution,
                                       bool aIsRepeated);
 
+  /**
+   * Performs a progressive update of a given tiled buffer.
+   */
+  bool ProgressiveUpdate(BasicTiledLayerBuffer& aTiledBuffer,
+                         nsIntRegion& aValidRegion,
+                         nsIntRegion& aInvalidRegion,
+                         const nsIntRegion& aOldValidRegion,
+                         const gfx3DMatrix& aTransform,
+                         const gfx::Point& aScrollOffset,
+                         const gfxSize& aResolution,
+                         LayerManager::DrawThebesLayerCallback aCallback,
+                         void* aCallbackData);
+
   // Members
   BasicTiledLayerBuffer mTiledBuffer;
+  BasicTiledLayerBuffer mLowPrecisionTiledBuffer;
+  nsIntRegion mLowPrecisionValidRegion;
   gfx::Point mLastScrollOffset;
   bool mFirstPaint;
 };
 
 } // layers
 } // mozilla
 
 #endif
--- a/gfx/layers/opengl/TiledThebesLayerOGL.cpp
+++ b/gfx/layers/opengl/TiledThebesLayerOGL.cpp
@@ -111,52 +111,92 @@ TiledLayerBufferOGL::ValidateTile(TiledT
 #endif
   return aTile;
 }
 
 TiledThebesLayerOGL::TiledThebesLayerOGL(LayerManagerOGL *aManager)
   : ShadowThebesLayer(aManager, nullptr)
   , LayerOGL(aManager)
   , mVideoMemoryTiledBuffer(aManager->gl())
+  , mLowPrecisionVideoMemoryTiledBuffer(aManager->gl())
   , mReusableTileStore(nullptr)
+  , mPendingUpload(false)
+  , mPendingLowPrecisionUpload(false)
 {
   mImplData = static_cast<LayerOGL*>(this);
 }
 
 TiledThebesLayerOGL::~TiledThebesLayerOGL()
 {
   mMainMemoryTiledBuffer.ReadUnlock();
+  mLowPrecisionMainMemoryTiledBuffer.ReadUnlock();
   if (mReusableTileStore)
     delete mReusableTileStore;
 }
 
 void
 TiledThebesLayerOGL::MemoryPressure()
 {
   if (mReusableTileStore) {
     delete mReusableTileStore;
     mReusableTileStore = new ReusableTileStoreOGL(gl(), 1);
   }
 }
 
 void
 TiledThebesLayerOGL::PaintedTiledLayerBuffer(const BasicTiledLayerBuffer* mTiledBuffer)
 {
-  mMainMemoryTiledBuffer.ReadUnlock();
-  mMainMemoryTiledBuffer = *mTiledBuffer;
+  if (mTiledBuffer->IsLowPrecision()) {
+    mLowPrecisionMainMemoryTiledBuffer.ReadUnlock();
+    mLowPrecisionMainMemoryTiledBuffer = *mTiledBuffer;
+    mLowPrecisionRegionToUpload.Or(mLowPrecisionRegionToUpload,
+                                   mLowPrecisionMainMemoryTiledBuffer.GetPaintedRegion());
+    mLowPrecisionMainMemoryTiledBuffer.ClearPaintedRegion();
+    mPendingLowPrecisionUpload = true;
+  } else {
+    mMainMemoryTiledBuffer.ReadUnlock();
+    mMainMemoryTiledBuffer = *mTiledBuffer;
+    mRegionToUpload.Or(mRegionToUpload, mMainMemoryTiledBuffer.GetPaintedRegion());
+    mMainMemoryTiledBuffer.ClearPaintedRegion();
+    mPendingUpload = true;
+  }
+
   // TODO: Remove me once Bug 747811 lands.
   delete mTiledBuffer;
-  mRegionToUpload.Or(mRegionToUpload, mMainMemoryTiledBuffer.GetPaintedRegion());
-  mMainMemoryTiledBuffer.ClearPaintedRegion();
+}
+
+void
+TiledThebesLayerOGL::ProcessLowPrecisionUploadQueue()
+{
+  if (!mPendingLowPrecisionUpload)
+    return;
+
+  mLowPrecisionRegionToUpload.And(mLowPrecisionRegionToUpload,
+                                  mLowPrecisionMainMemoryTiledBuffer.GetValidRegion());
+  // XXX The aResolution parameter of Upload is unused here - this is normally
+  //     set so that ReusableTileStoreOGL knows the frame resolution of tiles
+  //     it's harvesting.
+  mLowPrecisionVideoMemoryTiledBuffer.SetResolution(
+    mLowPrecisionMainMemoryTiledBuffer.GetResolution());
+  mLowPrecisionVideoMemoryTiledBuffer.Upload(&mLowPrecisionMainMemoryTiledBuffer,
+                                 mLowPrecisionMainMemoryTiledBuffer.GetValidRegion(),
+                                 mLowPrecisionRegionToUpload, gfxSize(1, 1));
+  nsIntRegion validRegion = mLowPrecisionVideoMemoryTiledBuffer.GetValidRegion();
+
+  mLowPrecisionMainMemoryTiledBuffer.ReadUnlock();
+
+  mLowPrecisionMainMemoryTiledBuffer = BasicTiledLayerBuffer();
+  mLowPrecisionRegionToUpload = nsIntRegion();
+  mPendingLowPrecisionUpload = false;
 }
 
 void
 TiledThebesLayerOGL::ProcessUploadQueue()
 {
-  if (mRegionToUpload.IsEmpty())
+  if (!mPendingUpload)
     return;
 
   // We should only be retaining old tiles if we're not fixed position.
   // Fixed position layers don't/shouldn't move on the screen, so retaining
   // tiles is not useful and often results in rendering artifacts.
   if (mReusableTileStore && mIsFixedPosition) {
     delete mReusableTileStore;
     mReusableTileStore = nullptr;
@@ -189,28 +229,28 @@ TiledThebesLayerOGL::ProcessUploadQueue(
 
   // If we coalesce uploads while the layers' valid region is changing we will
   // end up trying to upload area outside of the valid region. (bug 756555)
   mRegionToUpload.And(mRegionToUpload, mMainMemoryTiledBuffer.GetValidRegion());
 
   mVideoMemoryTiledBuffer.Upload(&mMainMemoryTiledBuffer,
                                  mMainMemoryTiledBuffer.GetValidRegion(),
                                  mRegionToUpload, resolution);
-  mVideoMemoryTiledBuffer.SetResolution(mMainMemoryTiledBuffer.GetResolution());
+
   mValidRegion = mVideoMemoryTiledBuffer.GetValidRegion();
 
   mMainMemoryTiledBuffer.ReadUnlock();
   // Release all the tiles by replacing the tile buffer with an empty
   // tiled buffer. This will prevent us from doing a double unlock when
   // calling  ~TiledThebesLayerOGL.
   // FIXME: This wont be needed when we do progressive upload and lock
   // tile by tile.
   mMainMemoryTiledBuffer = BasicTiledLayerBuffer();
   mRegionToUpload = nsIntRegion();
-
+  mPendingUpload = false;
 }
 
 void
 TiledThebesLayerOGL::RenderTile(const TiledTexture& aTile,
                                 const gfx3DMatrix& aTransform,
                                 const nsIntPoint& aOffset,
                                 const nsIntRegion& aScreenRegion,
                                 const nsIntPoint& aTextureOffset,
@@ -238,70 +278,86 @@ TiledThebesLayerOGL::RenderTile(const Ti
       program->SetLayerQuadRect(*rect);
       mOGLManager->BindAndDrawQuadWithTextureRect(program,
                                                   textureRect,
                                                   aTextureBounds);
     }
 }
 
 void
-TiledThebesLayerOGL::RenderLayer(int aPreviousFrameBuffer, const nsIntPoint& aOffset)
+TiledThebesLayerOGL::RenderLayerBuffer(TiledLayerBufferOGL& aLayerBuffer,
+                                       const nsIntRegion& aValidRegion,
+                                       const nsIntPoint& aOffset,
+                                       const nsIntRegion& aMaskRegion)
 {
-  gl()->MakeCurrent();
-  gl()->fActiveTexture(LOCAL_GL_TEXTURE0);
-  ProcessUploadQueue();
-
   Layer* maskLayer = GetMaskLayer();
-
-  // Render old tiles to fill in gaps we haven't had the time to render yet.
-  if (mReusableTileStore) {
-    mReusableTileStore->DrawTiles(this,
-                                  mVideoMemoryTiledBuffer.GetValidRegion(),
-                                  mVideoMemoryTiledBuffer.GetFrameResolution(),
-                                  GetEffectiveTransform(), aOffset, maskLayer);
-  }
-
-  // Render valid tiles.
   const nsIntRegion& visibleRegion = GetEffectiveVisibleRegion();
   const nsIntRect visibleRect = visibleRegion.GetBounds();
-  float resolution = mVideoMemoryTiledBuffer.GetResolution();
+  float resolution = aLayerBuffer.GetResolution();
   gfx3DMatrix transform = GetEffectiveTransform();
   transform.Scale(1/resolution, 1/resolution, 1);
 
   uint32_t rowCount = 0;
   uint32_t tileX = 0;
   for (int32_t x = visibleRect.x; x < visibleRect.x + visibleRect.width;) {
     rowCount++;
-    int32_t tileStartX = mVideoMemoryTiledBuffer.GetTileStart(x);
-    int32_t w = mVideoMemoryTiledBuffer.GetScaledTileLength() - tileStartX;
+    int32_t tileStartX = aLayerBuffer.GetTileStart(x);
+    int32_t w = aLayerBuffer.GetScaledTileLength() - tileStartX;
     if (x + w > visibleRect.x + visibleRect.width)
       w = visibleRect.x + visibleRect.width - x;
     int tileY = 0;
     for (int32_t y = visibleRect.y; y < visibleRect.y + visibleRect.height;) {
-      int32_t tileStartY = mVideoMemoryTiledBuffer.GetTileStart(y);
-      int32_t h = mVideoMemoryTiledBuffer.GetScaledTileLength() - tileStartY;
+      int32_t tileStartY = aLayerBuffer.GetTileStart(y);
+      int32_t h = aLayerBuffer.GetScaledTileLength() - tileStartY;
       if (y + h > visibleRect.y + visibleRect.height)
         h = visibleRect.y + visibleRect.height - y;
 
-      TiledTexture tileTexture = mVideoMemoryTiledBuffer.
-        GetTile(nsIntPoint(mVideoMemoryTiledBuffer.RoundDownToTileEdge(x),
-                           mVideoMemoryTiledBuffer.RoundDownToTileEdge(y)));
-      if (tileTexture != mVideoMemoryTiledBuffer.GetPlaceholderTile()) {
+      TiledTexture tileTexture = aLayerBuffer.
+        GetTile(nsIntPoint(aLayerBuffer.RoundDownToTileEdge(x),
+                           aLayerBuffer.RoundDownToTileEdge(y)));
+      if (tileTexture != aLayerBuffer.GetPlaceholderTile()) {
         nsIntRegion tileDrawRegion = nsIntRegion(nsIntRect(x, y, w, h));
-        tileDrawRegion.And(tileDrawRegion, mValidRegion);
-        tileDrawRegion.ScaleRoundOut(resolution, resolution);
+        tileDrawRegion.And(tileDrawRegion, aValidRegion);
+        tileDrawRegion.Sub(tileDrawRegion, aMaskRegion);
+
+        if (!tileDrawRegion.IsEmpty()) {
+          tileDrawRegion.ScaleRoundOut(resolution, resolution);
 
-        nsIntPoint tileOffset((x - tileStartX) * resolution,
-                              (y - tileStartY) * resolution);
-        uint32_t tileSize = mVideoMemoryTiledBuffer.GetTileLength();
-        RenderTile(tileTexture, transform, aOffset, tileDrawRegion,
-                   tileOffset, nsIntSize(tileSize, tileSize), maskLayer);
+          nsIntPoint tileOffset((x - tileStartX) * resolution,
+                                (y - tileStartY) * resolution);
+          uint32_t tileSize = aLayerBuffer.GetTileLength();
+          RenderTile(tileTexture, transform, aOffset, tileDrawRegion,
+                     tileOffset, nsIntSize(tileSize, tileSize), maskLayer);
+        }
       }
       tileY++;
       y += h;
     }
     tileX++;
     x += w;
   }
 }
 
+void
+TiledThebesLayerOGL::RenderLayer(int aPreviousFrameBuffer, const nsIntPoint& aOffset)
+{
+  gl()->MakeCurrent();
+  gl()->fActiveTexture(LOCAL_GL_TEXTURE0);
+  ProcessUploadQueue();
+  ProcessLowPrecisionUploadQueue();
+
+  // Render old tiles to fill in gaps we haven't had the time to render yet.
+  if (mReusableTileStore) {
+    mReusableTileStore->DrawTiles(this,
+                                  mVideoMemoryTiledBuffer.GetValidRegion(),
+                                  mVideoMemoryTiledBuffer.GetFrameResolution(),
+                                  GetEffectiveTransform(), aOffset, GetMaskLayer());
+  }
+
+  // Render valid tiles.
+  RenderLayerBuffer(mLowPrecisionVideoMemoryTiledBuffer,
+                    mLowPrecisionVideoMemoryTiledBuffer.GetValidRegion(),
+                    aOffset, mValidRegion);
+  RenderLayerBuffer(mVideoMemoryTiledBuffer, mValidRegion, aOffset, nsIntRegion());
+}
+
 } // mozilla
 } // layers
--- a/gfx/layers/opengl/TiledThebesLayerOGL.h
+++ b/gfx/layers/opengl/TiledThebesLayerOGL.h
@@ -126,31 +126,42 @@ public:
   void Swap(const ThebesBuffer& aNewFront, const nsIntRegion& aUpdatedRegion,
        OptionalThebesBuffer* aNewBack, nsIntRegion* aNewBackValidRegion,
        OptionalThebesBuffer* aReadOnlyFront, nsIntRegion* aFrontUpdatedRegion)
   {
     NS_ABORT_IF_FALSE(false, "Not supported");
   }
   void PaintedTiledLayerBuffer(const BasicTiledLayerBuffer* mTiledBuffer);
   void ProcessUploadQueue();
+  void ProcessLowPrecisionUploadQueue();
 
   void MemoryPressure();
 
   // Renders a single given tile.
   void RenderTile(const TiledTexture& aTile,
                   const gfx3DMatrix& aTransform,
                   const nsIntPoint& aOffset,
                   const nsIntRegion& aScreenRegion,
                   const nsIntPoint& aTextureOffset,
                   const nsIntSize& aTextureBounds,
                   Layer* aMaskLayer);
 
 private:
+  void RenderLayerBuffer(TiledLayerBufferOGL& aLayerBuffer,
+                         const nsIntRegion& aValidRegion,
+                         const nsIntPoint& aOffset,
+                         const nsIntRegion& aMaskRegion);
+
   nsIntRegion                  mRegionToUpload;
+  nsIntRegion                  mLowPrecisionRegionToUpload;
   BasicTiledLayerBuffer        mMainMemoryTiledBuffer;
+  BasicTiledLayerBuffer        mLowPrecisionMainMemoryTiledBuffer;
   TiledLayerBufferOGL          mVideoMemoryTiledBuffer;
+  TiledLayerBufferOGL          mLowPrecisionVideoMemoryTiledBuffer;
   ReusableTileStoreOGL*        mReusableTileStore;
+  bool                         mPendingUpload : 1;
+  bool                         mPendingLowPrecisionUpload : 1;
 };
 
 } // layers
 } // mozilla
 
 #endif
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -2740,17 +2740,36 @@ Tab.prototype = {
     };
 
     let epsilon = 0.001;
     if (this._oldDisplayPort == null ||
         Math.abs(displayPort.x - this._oldDisplayPort.x) > epsilon ||
         Math.abs(displayPort.y - this._oldDisplayPort.y) > epsilon ||
         Math.abs(displayPort.width - this._oldDisplayPort.width) > epsilon ||
         Math.abs(displayPort.height - this._oldDisplayPort.height) > epsilon) {
-      cwu.setDisplayPortForElement(displayPort.x, displayPort.y, displayPort.width, displayPort.height, element);
+      // Set the display-port to be 4x the size of the critical display-port,
+      // on each dimension, giving us a 0.25x lower precision buffer around the
+      // critical display-port. Spare area is *not* redistributed to the other
+      // axis, as display-list building and invalidation cost scales with the
+      // size of the display-port.
+      let pageRect = cwu.getRootBounds();
+      let pageXMost = pageRect.right - geckoScrollX;
+      let pageYMost = pageRect.bottom - geckoScrollY;
+
+      let dpW = Math.min(pageRect.right - pageRect.left, displayPort.width * 4);
+      let dpH = Math.min(pageRect.bottom - pageRect.top, displayPort.height * 4);
+
+      let dpX = Math.min(Math.max(displayPort.x - displayPort.width * 1.5,
+                                  pageRect.left - geckoScrollX), pageXMost - dpW);
+      let dpY = Math.min(Math.max(displayPort.y - displayPort.height * 1.5,
+                                  pageRect.top - geckoScrollY), pageYMost - dpH);
+      cwu.setDisplayPortForElement(dpX, dpY, dpW, dpH, element);
+      cwu.setCriticalDisplayPortForElement(displayPort.x, displayPort.y,
+                                           displayPort.width, displayPort.height,
+                                           element);
     }
 
     this._oldDisplayPort = displayPort;
   },
 
   /*
    * Yes, this is ugly. But it's currently the safest way to account for the rounding errors that occur
    * when we pump the displayport coordinates through gecko and they pop out in the compositor.