Bug 745177 - Retain and re-use uploaded GL tiles. r=ajuma
authorChris Lord <chrislord.net@gmail.com>
Tue, 24 Apr 2012 22:48:33 -0400
changeset 92422 b98060a2d11c
parent 92421 9500706da6f6
child 92423 1aadea66098c
push id22531
push userkhuey@mozilla.com
push dateThu, 26 Apr 2012 03:23:06 +0000
treeherdermozilla-central@b893f852fe7b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersajuma
bugs745177
milestone15.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 745177 - Retain and re-use uploaded GL tiles. r=ajuma 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
@@ -245,16 +245,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");