Bug 745177 - Retain and re-use uploaded GL tiles. r=ajuma a=blassey
authorChris Lord <chrislord.net@gmail.com>
Tue, 24 Apr 2012 22:48:33 -0400
changeset 94054 f4e462ad50a2
parent 94053 c5ac3bd1c831
child 94055 8fb4d0f15a18
push id1145
push userchrislord.net@gmail.com
push dateThu, 26 Apr 2012 16:32:52 +0000
treeherdermozilla-aurora@f4e462ad50a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersajuma, blassey
bugs745177
milestone14.0a2
Bug 745177 - Retain and re-use uploaded GL tiles. r=ajuma a=blassey Rather than discarding uploaded tiles once they fall outside of the valid region, shuffle them off into a separate tile-store with all the metadata they need to render them, and render them in the areas that our current valid region doesn't cover in TiledThebesLayerOGL::RenderLayer.
gfx/layers/Layers.h
gfx/layers/Makefile.in
gfx/layers/TiledLayerBuffer.h
gfx/layers/ipc/ShadowLayerUtils.h
gfx/layers/opengl/ReusableTileStoreOGL.cpp
gfx/layers/opengl/ReusableTileStoreOGL.h
gfx/layers/opengl/TiledThebesLayerOGL.cpp
gfx/layers/opengl/TiledThebesLayerOGL.h
ipc/glue/IPCMessageUtils.h
layout/base/nsDisplayList.cpp
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -105,16 +105,17 @@ public:
                                         // will begin at.
 
   FrameMetrics()
     : mViewport(0, 0, 0, 0)
     , mContentSize(0, 0)
     , mViewportScrollOffset(0, 0)
     , mScrollId(NULL_SCROLL_ID)
     , mCSSContentSize(0, 0)
+    , mResolution(1, 1)
   {}
 
   // Default copy ctor and operator= are fine
 
   bool operator==(const FrameMetrics& aOther) const
   {
     return (mViewport.IsEqualEdges(aOther.mViewport) &&
             mViewportScrollOffset == aOther.mViewportScrollOffset &&
@@ -146,16 +147,20 @@ public:
   nsIntSize mContentSize;
   nsIntPoint mViewportScrollOffset;
   nsIntRect mDisplayPort;
   ViewID mScrollId;
 
   // Consumers often want to know the size before scaling to pixels
   // so we record this size as well.
   gfx::Size mCSSContentSize;
+
+  // This represents the resolution at which the associated layer
+  // will been rendered.
+  gfxSize mResolution;
 };
 
 #define MOZ_LAYER_DECL_NAME(n, e)                           \
   virtual const char* Name() const { return n; }            \
   virtual LayerType GetType() const { return e; }
 
 /**
  * Base class for userdata objects attached to layers and layer managers.
--- a/gfx/layers/Makefile.in
+++ b/gfx/layers/Makefile.in
@@ -81,16 +81,17 @@ CPPSRCS = \
         ThebesLayerBuffer.cpp \
         CanvasLayerOGL.cpp \
         ColorLayerOGL.cpp \
         ContainerLayerOGL.cpp \
         ImageLayerOGL.cpp \
         LayerManagerOGL.cpp \
         ThebesLayerOGL.cpp \
         TiledThebesLayerOGL.cpp \
+        ReusableTileStoreOGL.cpp \
         LayerSorter.cpp \
         ImageLayers.cpp \
         $(NULL)
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
 ifdef MOZ_ENABLE_D3D9_LAYER
 EXPORTS += \
         LayerManagerD3D9.h \
--- a/gfx/layers/TiledLayerBuffer.h
+++ b/gfx/layers/TiledLayerBuffer.h
@@ -82,18 +82,30 @@ public:
   //       and GetValidRegion() to get the area of the tile that is valid.
   Tile GetTile(const nsIntPoint& aTileOrigin) const;
 
   // Given a tile x, y relative to the top left of the layer, this function
   // will return the tile for
   // (x*GetTileLength(), y*GetTileLength(), GetTileLength(), GetTileLength())
   Tile GetTile(int x, int y) const;
 
+  // This operates the same as GetTile(aTileOrigin), but will also replace the
+  // specified tile with the placeholder tile. This does not call ReleaseTile
+  // on the removed tile.
+  bool RemoveTile(const nsIntPoint& aTileOrigin, Tile& aRemovedTile);
+
+  // This operates the same as GetTile(x, y), but will also replace the
+  // specified tile with the placeholder tile. This does not call ReleaseTile
+  // on the removed tile.
+  bool RemoveTile(int x, int y, Tile& aRemovedTile);
+
   uint16_t GetTileLength() const { return TILEDLAYERBUFFER_TILE_SIZE; }
 
+  unsigned int GetTileCount() const { return mRetainedTiles.Length(); }
+
   const nsIntRegion& GetValidRegion() const { return mValidRegion; }
   const nsIntRegion& GetLastPaintRegion() const { return mLastPaintRegion; }
   void SetLastPaintRegion(const nsIntRegion& aLastPaintRegion) {
     mLastPaintRegion = aLastPaintRegion;
   }
 
   // Rounds the given coordinate down to the nearest tile boundary.
   int RoundDownToTileEdge(int aX) const { return aX - aX % GetTileLength(); }
@@ -156,16 +168,40 @@ TiledLayerBuffer<Derived, Tile>::GetTile
 
 template<typename Derived, typename Tile> Tile
 TiledLayerBuffer<Derived, Tile>::GetTile(int x, int y) const
 {
   int index = x * mRetainedHeight + y;
   return mRetainedTiles.SafeElementAt(index, AsDerived().GetPlaceholderTile());
 }
 
+template<typename Derived, typename Tile> bool
+TiledLayerBuffer<Derived, Tile>::RemoveTile(const nsIntPoint& aTileOrigin,
+                                            Tile& aRemovedTile)
+{
+  int firstTileX = mValidRegion.GetBounds().x / GetTileLength();
+  int firstTileY = mValidRegion.GetBounds().y / GetTileLength();
+  return RemoveTile(aTileOrigin.x / GetTileLength() - firstTileX,
+                    aTileOrigin.y / GetTileLength() - firstTileY,
+                    aRemovedTile);
+}
+
+template<typename Derived, typename Tile> bool
+TiledLayerBuffer<Derived, Tile>::RemoveTile(int x, int y, Tile& aRemovedTile)
+{
+  int index = x * mRetainedHeight + y;
+  const Tile& tileToRemove = mRetainedTiles.SafeElementAt(index, AsDerived().GetPlaceholderTile());
+  if (!IsPlaceholder(tileToRemove)) {
+    aRemovedTile = tileToRemove;
+    mRetainedTiles[index] = AsDerived().GetPlaceholderTile();
+    return true;
+  }
+  return false;
+}
+
 template<typename Derived, typename Tile> void
 TiledLayerBuffer<Derived, Tile>::Update(const nsIntRegion& aNewValidRegion,
                                         const nsIntRegion& aPaintRegion)
 {
   nsTArray<Tile>  newRetainedTiles;
   nsTArray<Tile>& oldRetainedTiles = mRetainedTiles;
   const nsIntRect oldBound = mValidRegion.GetBounds();
   const nsIntRect newBound = aNewValidRegion.GetBounds();
@@ -204,24 +240,26 @@ TiledLayerBuffer<Derived, Tile>::Update(
       if (oldValidRegion.Intersects(tileRect) && newValidRegion.Intersects(tileRect)) {
         // This old tiles contains some valid area so move it to the new tile
         // buffer. Replace the tile in the old buffer with a placeholder
         // to leave the old buffer index unaffected.
         int tileX = (x - oldBufferOrigin.x) / GetTileLength();
         int tileY = (y - oldBufferOrigin.y) / GetTileLength();
         int index = tileX * oldRetainedHeight + tileY;
 
-        NS_ABORT_IF_FALSE(!IsPlaceholder(oldRetainedTiles.
-                                         SafeElementAt(index, AsDerived().GetPlaceholderTile())),
-                          "Expected tile");
+        // The tile may have been removed, skip over it in this case.
+        if (IsPlaceholder(oldRetainedTiles.
+                          SafeElementAt(index, AsDerived().GetPlaceholderTile()))) {
+          newRetainedTiles.AppendElement(AsDerived().GetPlaceholderTile());
+        } else {
+          Tile tileWithPartialValidContent = oldRetainedTiles[index];
+          newRetainedTiles.AppendElement(tileWithPartialValidContent);
+          oldRetainedTiles[index] = AsDerived().GetPlaceholderTile();
+        }
 
-        Tile tileWithPartialValidContent = oldRetainedTiles[index];
-        newRetainedTiles.AppendElement(tileWithPartialValidContent);
-
-        oldRetainedTiles[index] = AsDerived().GetPlaceholderTile();
       } else {
         // This tile is either:
         // 1) Outside the new valid region and will simply be an empty
         // placeholder forever.
         // 2) The old buffer didn't have any data for this tile. We postpone
         // the allocation of this tile after we've reused any tile with
         // valid content because then we know we can safely recycle
         // with taking from a tile that has recyclable content.
--- a/gfx/layers/ipc/ShadowLayerUtils.h
+++ b/gfx/layers/ipc/ShadowLayerUtils.h
@@ -68,26 +68,28 @@ struct ParamTraits<mozilla::layers::Fram
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.mCSSContentSize);
     WriteParam(aMsg, aParam.mViewport);
     WriteParam(aMsg, aParam.mContentSize);
     WriteParam(aMsg, aParam.mViewportScrollOffset);
     WriteParam(aMsg, aParam.mDisplayPort);
     WriteParam(aMsg, aParam.mScrollId);
+    WriteParam(aMsg, aParam.mResolution);
   }
 
   static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
   {
     return (ReadParam(aMsg, aIter, &aResult->mCSSContentSize) &&
             ReadParam(aMsg, aIter, &aResult->mViewport) &&
             ReadParam(aMsg, aIter, &aResult->mContentSize) &&
             ReadParam(aMsg, aIter, &aResult->mViewportScrollOffset) &&
             ReadParam(aMsg, aIter, &aResult->mDisplayPort) &&
-            ReadParam(aMsg, aIter, &aResult->mScrollId));
+            ReadParam(aMsg, aIter, &aResult->mScrollId) &&
+            ReadParam(aMsg, aIter, &aResult->mResolution));
   }
 };
 
 #if !defined(MOZ_HAVE_SURFACEDESCRIPTORX11)
 template <>
 struct ParamTraits<mozilla::layers::SurfaceDescriptorX11> {
   typedef mozilla::layers::SurfaceDescriptorX11 paramType;
   static void Write(Message*, const paramType&) {}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/opengl/ReusableTileStoreOGL.cpp
@@ -0,0 +1,188 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ReusableTileStoreOGL.h"
+
+namespace mozilla {
+namespace layers {
+
+ReusableTileStoreOGL::~ReusableTileStoreOGL()
+{
+  if (mTiles.Length() == 0)
+    return;
+
+  mContext->MakeCurrent();
+  for (int i = 0; i < mTiles.Length(); i++)
+    mContext->fDeleteTextures(1, &mTiles[i]->mTexture.mTextureHandle);
+  mTiles.Clear();
+}
+
+void
+ReusableTileStoreOGL::HarvestTiles(TiledLayerBufferOGL* aVideoMemoryTiledBuffer,
+                                   const nsIntRegion& aOldValidRegion,
+                                   const nsIntRegion& aNewValidRegion,
+                                   const gfxSize& aOldResolution,
+                                   const gfxSize& aNewResolution)
+{
+  gfxSize scaleFactor = gfxSize(aNewResolution.width / aOldResolution.width,
+                                aNewResolution.height / aOldResolution.height);
+
+#ifdef GFX_TILEDLAYER_PREF_WARNINGS
+  printf_stderr("Seeing if there are any tiles we can reuse\n");
+#endif
+
+  // Iterate over existing harvested tiles and release any that are contained
+  // within the new valid region.
+  mContext->MakeCurrent();
+  for (int i = 0; i < mTiles.Length();) {
+    ReusableTiledTextureOGL* tile = mTiles[i];
+
+    bool release = false;
+    if (tile->mResolution == aNewResolution) {
+      if (aNewValidRegion.Contains(tile->mTileRegion))
+        release = true;
+    } else {
+      nsIntRegion transformedTileRegion(tile->mTileRegion);
+      transformedTileRegion.ScaleRoundOut(tile->mResolution.width / aNewResolution.width,
+                                          tile->mResolution.height / aNewResolution.height);
+      if (aNewValidRegion.Contains(transformedTileRegion))
+        release = true;
+    }
+
+    if (release) {
+#ifdef GFX_TILEDLAYER_PREF_WARNINGS
+      nsIntRect tileBounds = tile->mTileRegion.GetBounds();
+      printf_stderr("Releasing obsolete reused tile at %d,%d, x%f\n",
+                    tileBounds.x, tileBounds.y, tile->mResolution.width);
+#endif
+      mContext->fDeleteTextures(1, &tile->mTexture.mTextureHandle);
+      mTiles.RemoveElementAt(i);
+      continue;
+    }
+
+    i++;
+  }
+
+  // Iterate over the tiles and decide which ones we're going to harvest.
+  // We harvest any tile that is entirely outside of the new valid region, or
+  // any tile that is partially outside of the valid region and whose
+  // resolution has changed.
+  // XXX Tile iteration needs to be abstracted, or have some utility functions
+  //     to make it simpler.
+  uint16_t tileSize = aVideoMemoryTiledBuffer->GetTileLength();
+  nsIntRect validBounds = aOldValidRegion.GetBounds();
+  for (int x = validBounds.x; x < validBounds.XMost();) {
+    int w = tileSize - x % tileSize;
+    if (x + w > validBounds.x + validBounds.width)
+      w = validBounds.x + validBounds.width - x;
+
+    for (int y = validBounds.y; y < validBounds.YMost();) {
+      int h = tileSize - y % tileSize;
+      if (y + h > validBounds.y + validBounds.height)
+        h = validBounds.y + validBounds.height - y;
+
+      // If the new valid region doesn't contain this tile region,
+      // harvest the tile.
+      nsIntRegion tileRegion;
+      tileRegion.And(aOldValidRegion, nsIntRect(x, y, w, h));
+
+      nsIntRegion intersectingRegion;
+      bool retainTile = false;
+      if (aNewResolution != aOldResolution) {
+        // Reconcile resolution changes.
+        // If the resolution changes, we know the backing layer will have been
+        // invalidated, so retain tiles that are partially encompassed by the
+        // new valid area, instead of just tiles that don't intersect at all.
+        nsIntRegion transformedTileRegion(tileRegion);
+        transformedTileRegion.ScaleRoundOut(scaleFactor.width, scaleFactor.height);
+        if (!aNewValidRegion.Contains(transformedTileRegion))
+          retainTile = true;
+      } else if (intersectingRegion.And(tileRegion, aNewValidRegion).IsEmpty()) {
+        retainTile = true;
+      }
+
+      if (retainTile) {
+#ifdef GFX_TILEDLAYER_PREF_WARNINGS
+        printf_stderr("Retaining tile at %d,%d, x%f for reuse\n", x, y, aOldResolution.width);
+#endif
+        TiledTexture removedTile;
+        if (aVideoMemoryTiledBuffer->RemoveTile(nsIntPoint(x, y), removedTile)) {
+          ReusableTiledTextureOGL* reusedTile =
+            new ReusableTiledTextureOGL(removedTile, tileRegion, tileSize,
+                                        aOldResolution);
+          mTiles.AppendElement(reusedTile);
+        }
+#ifdef GFX_TILEDLAYER_PREF_WARNINGS
+        else
+          printf_stderr("Failed to retain tile for reuse\n");
+#endif
+      }
+
+      y += h;
+    }
+
+    x += w;
+  }
+
+  // Now prune our reused tile store of its oldest tiles if it gets too large.
+  while (mTiles.Length() > aVideoMemoryTiledBuffer->GetTileCount() * mSizeLimit) {
+#ifdef GFX_TILEDLAYER_PREF_WARNINGS
+    nsIntRect tileBounds = mTiles[0]->mTileRegion.GetBounds();
+    printf_stderr("Releasing old reused tile at %d,%d, x%f\n",
+                  tileBounds.x, tileBounds.y, mTiles[0]->mResolution.width);
+#endif
+    mContext->fDeleteTextures(1, &mTiles[0]->mTexture.mTextureHandle);
+    mTiles.RemoveElementAt(0);
+  }
+
+#ifdef GFX_TILEDLAYER_PREF_WARNINGS
+  printf_stderr("Retained %d tiles\n", mTiles.Length());
+#endif
+}
+
+void
+ReusableTileStoreOGL::DrawTiles(TiledThebesLayerOGL* aLayer,
+                                const nsIntRegion& aValidRegion,
+                                const gfxSize& aResolution,
+                                const gfx3DMatrix& aTransform,
+                                const nsIntPoint& aRenderOffset)
+{
+  // Render old tiles to fill in gaps we haven't had the time to render yet.
+  for (size_t i = 0; i < mTiles.Length(); i++) {
+    ReusableTiledTextureOGL* tile = mTiles[i];
+
+    // Work out the scaling factor in case of resolution differences.
+    gfxSize scaleFactor = gfxSize(aResolution.width / tile->mResolution.width,
+                                  aResolution.height / tile->mResolution.height);
+
+    // Get the valid tile region, in the given coordinate space.
+    nsIntRegion transformedTileRegion(tile->mTileRegion);
+    if (aResolution != tile->mResolution)
+      transformedTileRegion.ScaleRoundOut(scaleFactor.width, scaleFactor.height);
+
+    // Skip drawing tiles that will be completely drawn over.
+    if (aValidRegion.Contains(transformedTileRegion))
+      continue;
+
+    // Reconcile the resolution difference by adjusting the transform.
+    gfx3DMatrix transform = aTransform;
+    if (aResolution != tile->mResolution)
+      transform.Scale(scaleFactor.width, scaleFactor.height, 1);
+
+    // XXX We should clip here to make sure we don't overlap with the valid
+    //     region, otherwise we may end up with rendering artifacts on
+    //     semi-transparent layers.
+    //     Similarly, if we have multiple tiles covering the same area, we will
+    //     end up with rendering artifacts if the aLayer isn't opaque.
+    nsIntRect tileRect = tile->mTileRegion.GetBounds();
+    uint16_t tileStartX = tileRect.x % tile->mTileSize;
+    uint16_t tileStartY = tileRect.y % tile->mTileSize;
+    nsIntRect textureRect(tileStartX, tileStartY, tileRect.width, tileRect.height);
+    nsIntSize textureSize(tile->mTileSize, tile->mTileSize);
+    aLayer->RenderTile(tile->mTexture, transform, aRenderOffset, tileRect, textureRect, textureSize);
+  }
+}
+
+} // mozilla
+} // layers
new file mode 100644
--- /dev/null
+++ b/gfx/layers/opengl/ReusableTileStoreOGL.h
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_REUSABLETILESTOREOGL_H
+#define GFX_REUSABLETILESTOREOGL_H
+
+#include "TiledThebesLayerOGL.h"
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+
+namespace gl {
+class GLContext;
+}
+
+namespace layers {
+
+// A storage class for the information required to render a single tile from
+// a TiledLayerBufferOGL.
+class ReusableTiledTextureOGL
+{
+public:
+  ReusableTiledTextureOGL(TiledTexture aTexture,
+                          const nsIntRegion& aTileRegion,
+                          uint16_t aTileSize,
+                          gfxSize aResolution)
+    : mTexture(aTexture)
+    , mTileRegion(aTileRegion)
+    , mTileSize(aTileSize)
+    , mResolution(aResolution)
+  {}
+
+  ~ReusableTiledTextureOGL() {}
+
+  TiledTexture mTexture;
+  const nsIntRegion mTileRegion;
+  uint16_t mTileSize;
+  gfxSize mResolution;
+};
+
+// This class will operate on a TiledLayerBufferOGL to harvest tiles that have
+// rendered content that is about to become invalid. We do this so that in the
+// situation that we need to render an area of a TiledThebesLayerOGL that hasn't
+// been updated quickly enough, we can still display something (and hopefully
+// it'll be the same as the valid rendered content). While this may end up
+// showing invalid data, it should only be momentarily.
+class ReusableTileStoreOGL
+{
+public:
+  ReusableTileStoreOGL(gl::GLContext* aContext, float aSizeLimit)
+    : mContext(aContext)
+    , mSizeLimit(aSizeLimit)
+  {}
+
+  ~ReusableTileStoreOGL();
+
+  // Harvests tiles from a TiledLayerBufferOGL that are about to become
+  // invalid. aOldValidRegion and aOldResolution should be the valid region
+  // and resolution of the data currently in aVideoMemoryTiledBuffer, and
+  // aNewValidRegion and aNewResolution should be the valid region and
+  // resolution of the data that is about to update aVideoMemoryTiledBuffer.
+  void HarvestTiles(TiledLayerBufferOGL* aVideoMemoryTiledBuffer,
+                    const nsIntRegion& aOldValidRegion,
+                    const nsIntRegion& aNewValidRegion,
+                    const gfxSize& aOldResolution,
+                    const gfxSize& aNewResolution);
+
+  // Draws all harvested tiles that don't intersect with the given valid region.
+  // Differences in resolution will be reconciled via altering the given
+  // transformation.
+  void DrawTiles(TiledThebesLayerOGL* aLayer,
+                 const nsIntRegion& aValidRegion,
+                 const gfxSize& aResolution,
+                 const gfx3DMatrix& aTransform,
+                 const nsIntPoint& aRenderOffset);
+
+private:
+  // This GLContext should correspond to the one used in any TiledLayerBufferOGL
+  // that is passed into HarvestTiles and DrawTiles.
+  nsRefPtr<gl::GLContext> mContext;
+
+  // This determines the maximum number of tiles stored in this tile store,
+  // as a fraction of the amount of tiles stored in the TiledLayerBufferOGL
+  // given to HarvestTiles.
+  float mSizeLimit;
+
+  // This stores harvested tiles, in the order in which they were harvested.
+  nsTArray< nsAutoPtr<ReusableTiledTextureOGL> > mTiles;
+};
+
+} // layers
+} // mozilla
+
+#endif // GFX_REUSABLETILESTOREOGL_H
--- a/gfx/layers/opengl/TiledThebesLayerOGL.cpp
+++ b/gfx/layers/opengl/TiledThebesLayerOGL.cpp
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/PLayersChild.h"
 #include "TiledThebesLayerOGL.h"
+#include "ReusableTileStoreOGL.h"
 #include "BasicTiledThebesLayer.h"
 #include "gfxImageSurface.h"
 
 namespace mozilla {
 namespace layers {
 
 using mozilla::gl::GLContext;
 
@@ -32,22 +33,25 @@ TiledLayerBufferOGL::ReleaseTile(TiledTe
   if (aTile == GetPlaceholderTile())
     return;
   mContext->fDeleteTextures(1, &aTile.mTextureHandle);
 }
 
 void
 TiledLayerBufferOGL::Upload(const BasicTiledLayerBuffer* aMainMemoryTiledBuffer,
                             const nsIntRegion& aNewValidRegion,
-                            const nsIntRegion& aInvalidateRegion)
+                            const nsIntRegion& aInvalidateRegion,
+                            const gfxSize& aResolution)
 {
 #ifdef GFX_TILEDLAYER_PREF_WARNINGS
   printf_stderr("Upload %i, %i, %i, %i\n", aInvalidateRegion.GetBounds().x, aInvalidateRegion.GetBounds().y, aInvalidateRegion.GetBounds().width, aInvalidateRegion.GetBounds().height);
   long start = PR_IntervalNow();
 #endif
+
+  mResolution = aResolution;
   mMainMemoryTiledBuffer = aMainMemoryTiledBuffer;
   mContext->MakeCurrent();
   Update(aNewValidRegion, aInvalidateRegion);
   mMainMemoryTiledBuffer = nsnull;
 #ifdef GFX_TILEDLAYER_PREF_WARNINGS
   if (PR_IntervalNow() - start > 10) {
     printf_stderr("Time to upload %i\n", PR_IntervalNow() - start);
   }
@@ -108,16 +112,25 @@ TiledLayerBufferOGL::ValidateTile(TiledT
 }
 
 TiledThebesLayerOGL::TiledThebesLayerOGL(LayerManagerOGL *aManager)
   : ShadowThebesLayer(aManager, nsnull)
   , LayerOGL(aManager)
   , mVideoMemoryTiledBuffer(aManager->gl())
 {
   mImplData = static_cast<LayerOGL*>(this);
+  // XXX Add a pref for reusable tile store size
+  mReusableTileStore = new ReusableTileStoreOGL(aManager->gl(), 1);
+}
+
+TiledThebesLayerOGL::~TiledThebesLayerOGL()
+{
+  mMainMemoryTiledBuffer.ReadUnlock();
+  if (mReusableTileStore)
+    delete mReusableTileStore;
 }
 
 void
 TiledThebesLayerOGL::PaintedTiledLayerBuffer(const BasicTiledLayerBuffer* mTiledBuffer)
 {
   mMainMemoryTiledBuffer = *mTiledBuffer;
   mRegionToUpload.Or(mRegionToUpload, mMainMemoryTiledBuffer.GetLastPaintRegion());
 
@@ -127,36 +140,92 @@ TiledThebesLayerOGL::PaintedTiledLayerBu
 }
 
 void
 TiledThebesLayerOGL::ProcessUploadQueue()
 {
   if (mRegionToUpload.IsEmpty())
     return;
 
-  mVideoMemoryTiledBuffer.Upload(&mMainMemoryTiledBuffer, mMainMemoryTiledBuffer.GetValidRegion(), mRegionToUpload);
+  gfxSize resolution(1, 1);
+  if (mReusableTileStore) {
+    // Work out render resolution by multiplying the resolution of our ancestors.
+    // Only container layers can have frame metrics, so we start off with a
+    // resolution of 1, 1.
+    // XXX For large layer trees, it would be faster to do this once from the
+    //     root node upwards and store the value on each layer.
+    for (ContainerLayer* parent = GetParent(); parent; parent = parent->GetParent()) {
+      const FrameMetrics& metrics = parent->GetFrameMetrics();
+      resolution.width *= metrics.mResolution.width;
+      resolution.height *= metrics.mResolution.height;
+    }
+
+    mReusableTileStore->HarvestTiles(&mVideoMemoryTiledBuffer,
+                                     mVideoMemoryTiledBuffer.GetValidRegion(),
+                                     mMainMemoryTiledBuffer.GetValidRegion(),
+                                     mVideoMemoryTiledBuffer.GetResolution(),
+                                     resolution);
+  }
+
+  mVideoMemoryTiledBuffer.Upload(&mMainMemoryTiledBuffer,
+                                 mMainMemoryTiledBuffer.GetValidRegion(),
+                                 mRegionToUpload, resolution);
   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();
 
 }
 
 void
+TiledThebesLayerOGL::RenderTile(TiledTexture aTile,
+                                const gfx3DMatrix& aTransform,
+                                const nsIntPoint& aOffset,
+                                nsIntRect aScreenRect,
+                                nsIntRect aTextureRect,
+                                nsIntSize aTextureBounds)
+{
+    gl()->fBindTexture(LOCAL_GL_TEXTURE_2D, aTile.mTextureHandle);
+    ColorTextureLayerProgram *program;
+    if (aTile.mFormat == LOCAL_GL_RGB) {
+      program = mOGLManager->GetRGBXLayerProgram();
+    } else {
+      program = mOGLManager->GetBGRALayerProgram();
+    }
+    program->Activate();
+    program->SetTextureUnit(0);
+    program->SetLayerOpacity(GetEffectiveOpacity());
+    program->SetLayerTransform(aTransform);
+    program->SetRenderOffset(aOffset);
+    program->SetLayerQuadRect(aScreenRect);
+
+    mOGLManager->BindAndDrawQuadWithTextureRect(program,
+                                                aTextureRect,
+                                                aTextureBounds);
+}
+
+void
 TiledThebesLayerOGL::RenderLayer(int aPreviousFrameBuffer, const nsIntPoint& aOffset)
 {
   gl()->MakeCurrent();
   ProcessUploadQueue();
 
+  // Render old tiles to fill in gaps we haven't had the time to render yet.
+  if (mReusableTileStore)
+    mReusableTileStore->DrawTiles(this, mVideoMemoryTiledBuffer.GetValidRegion(),
+                                  mVideoMemoryTiledBuffer.GetResolution(),
+                                  GetEffectiveTransform(), aOffset);
+
+  // Render valid tiles.
   const nsIntRegion& visibleRegion = GetEffectiveVisibleRegion();
   const nsIntRect visibleRect = visibleRegion.GetBounds();
   unsigned int rowCount = 0;
   int tileX = 0;
   for (size_t x = visibleRect.x; x < visibleRect.x + visibleRect.width;) {
     rowCount++;
     uint16_t tileStartX = x % mVideoMemoryTiledBuffer.GetTileLength();
     uint16_t w = mVideoMemoryTiledBuffer.GetTileLength() - tileStartX;
@@ -168,32 +237,19 @@ TiledThebesLayerOGL::RenderLayer(int aPr
       uint16_t h = mVideoMemoryTiledBuffer.GetTileLength() - 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()) {
-
-        gl()->fBindTexture(LOCAL_GL_TEXTURE_2D, tileTexture.mTextureHandle);
-        ColorTextureLayerProgram *program;
-        if (tileTexture.mFormat == LOCAL_GL_RGB) {
-          program = mOGLManager->GetRGBXLayerProgram();
-        } else {
-          program = mOGLManager->GetBGRALayerProgram();
-        }
-        program->Activate();
-        program->SetTextureUnit(0);
-        program->SetLayerOpacity(GetEffectiveOpacity());
-        program->SetLayerTransform(GetEffectiveTransform());
-        program->SetRenderOffset(aOffset);
-        program->SetLayerQuadRect(nsIntRect(x,y,w,h)); // screen
-        mOGLManager->BindAndDrawQuadWithTextureRect(program, nsIntRect(tileStartX, tileStartY, w, h), nsIntSize(mVideoMemoryTiledBuffer.GetTileLength(), mVideoMemoryTiledBuffer.GetTileLength())); // texture bounds
-
+        uint16_t tileSize = mVideoMemoryTiledBuffer.GetTileLength();
+        RenderTile(tileTexture, GetEffectiveTransform(), aOffset, nsIntRect(x,y,w,h),
+                   nsIntRect(tileStartX, tileStartY, w, h), nsIntSize(tileSize, tileSize));
       }
       tileY++;
       y += h;
     }
     tileX++;
     x += w;
   }
 
--- a/gfx/layers/opengl/TiledThebesLayerOGL.h
+++ b/gfx/layers/opengl/TiledThebesLayerOGL.h
@@ -15,16 +15,18 @@
 namespace mozilla {
 
 namespace gl {
 class GLContext;
 }
 
 namespace layers {
 
+class ReusableTileStoreOGL;
+
 class TiledTexture {
 public:
   // Constructs a placeholder TiledTexture. See the comments above
   // TiledLayerBuffer for more information on what this is used for;
   // essentially, this is a sentinel used to represent an invalid or blank
   // tile.
   //
   // Note that we assume that zero is not a valid GL texture handle here.
@@ -68,50 +70,51 @@ public:
   TiledLayerBufferOGL(gl::GLContext* aContext)
     : mContext(aContext)
   {}
 
   ~TiledLayerBufferOGL();
 
   void Upload(const BasicTiledLayerBuffer* aMainMemoryTiledBuffer,
               const nsIntRegion& aNewValidRegion,
-              const nsIntRegion& aInvalidateRegion);
+              const nsIntRegion& aInvalidateRegion,
+              const gfxSize& aResolution);
 
   TiledTexture GetPlaceholderTile() const { return TiledTexture(); }
 
+  const gfxSize& GetResolution() { return mResolution; }
+
 protected:
   TiledTexture ValidateTile(TiledTexture aTile,
                             const nsIntPoint& aTileRect,
                             const nsIntRegion& dirtyRect);
 
   void ReleaseTile(TiledTexture aTile);
 
   void SwapTiles(TiledTexture& aTileA, TiledTexture& aTileB) {
     std::swap(aTileA, aTileB);
   }
 
 private:
   nsRefPtr<gl::GLContext> mContext;
   const BasicTiledLayerBuffer* mMainMemoryTiledBuffer;
+  gfxSize mResolution;
 
   void GetFormatAndTileForImageFormat(gfxASurface::gfxImageFormat aFormat,
                                       GLenum& aOutFormat,
                                       GLenum& aOutType);
 };
 
 class TiledThebesLayerOGL : public ShadowThebesLayer,
                             public LayerOGL,
                             public TiledLayerComposer
 {
 public:
   TiledThebesLayerOGL(LayerManagerOGL *aManager);
-  virtual ~TiledThebesLayerOGL()
-  {
-    mMainMemoryTiledBuffer.ReadUnlock();
-  }
+  virtual ~TiledThebesLayerOGL();
 
   // LayerOGL impl
   void Destroy() {}
   Layer* GetLayer() { return this; }
   virtual void RenderLayer(int aPreviousFrameBuffer,
                            const nsIntPoint& aOffset);
   virtual void CleanupResources() { }
 
@@ -121,18 +124,30 @@ 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();
+
+  // Renders a single given tile.
+  // XXX This currently takes an nsIntRect, but should actually take an
+  //     nsIntRegion and iterate over each rectangle in the region.
+  void RenderTile(TiledTexture aTile,
+                  const gfx3DMatrix& aTransform,
+                  const nsIntPoint& aOffset,
+                  nsIntRect aScreenRect,
+                  nsIntRect aTextureRect,
+                  nsIntSize aTextureBounds);
+
 private:
   nsIntRegion                  mRegionToUpload;
   BasicTiledLayerBuffer        mMainMemoryTiledBuffer;
   TiledLayerBufferOGL          mVideoMemoryTiledBuffer;
+  ReusableTileStoreOGL*        mReusableTileStore;
 };
 
 } // layers
 } // mozilla
 
 #endif
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -48,16 +48,17 @@
 #include "nsID.h"
 #include "nsMemory.h"
 #include "nsStringGlue.h"
 #include "nsTArray.h"
 #include "gfx3DMatrix.h"
 #include "gfxColor.h"
 #include "gfxMatrix.h"
 #include "gfxPattern.h"
+#include "gfxPoint.h"
 #include "nsRect.h"
 #include "nsRegion.h"
 #include "gfxASurface.h"
 #include "Layers.h"
 
 #ifdef _MSC_VER
 #pragma warning( disable : 4800 )
 #endif
@@ -454,16 +455,37 @@ struct ParamTraits<gfxMatrix>
   static void Log(const paramType& aParam, std::wstring* aLog)
   {
     aLog->append(StringPrintf(L"[[%g %g] [%g %g] [%g %g]]", aParam.xx, aParam.xy, aParam.yx, aParam.yy,
 	  						    aParam.x0, aParam.y0));
   }
 };
 
 template<>
+struct ParamTraits<gfxSize>
+{
+  typedef gfxSize paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.width);
+    WriteParam(aMsg, aParam.height);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (ReadParam(aMsg, aIter, &aResult->width) &&
+        ReadParam(aMsg, aIter, &aResult->height))
+      return true;
+
+    return false;
+  }
+};
+
+template<>
 struct ParamTraits<gfx3DMatrix>
 {
   typedef gfx3DMatrix paramType;
 
   static void Write(Message* msg, const paramType& param)
   {
 #define Wr(_f)  WriteParam(msg, param. _f)
     Wr(_11); Wr(_12); Wr(_13); Wr(_14);
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -244,16 +244,20 @@ static void RecordFrameMetrics(nsIFrame*
     nsSize contentSize = aForFrame->GetSize();
     metrics.mCSSContentSize = gfx::Size(nsPresContext::AppUnitsToFloatCSSPixels(contentSize.width),
                                         nsPresContext::AppUnitsToFloatCSSPixels(contentSize.height));
     metrics.mContentSize = contentSize.ScaleToNearestPixels(
       aContainerParameters.mXScale, aContainerParameters.mYScale, auPerDevPixel);
   }
 
   metrics.mScrollId = aScrollId;
+
+  nsIPresShell* presShell = presContext->GetPresShell();
+  metrics.mResolution = gfxSize(presShell->GetXResolution(), presShell->GetYResolution());
+
   aRoot->SetFrameMetrics(metrics);
 }
 
 nsDisplayListBuilder::~nsDisplayListBuilder() {
   NS_ASSERTION(mFramesMarkedForDisplay.Length() == 0,
                "All frames should have been unmarked");
   NS_ASSERTION(mPresShellStates.Length() == 0,
                "All presshells should have been exited");