Bug 1471639 - Move edge padding to the paint thread. r=nical draft
authorRyan Hunt <rhunt@eqrion.net>
Tue, 26 Jun 2018 17:12:56 -0500
changeset 819449 bcbab66c16c5cc2b917f12b4481bbbb8fe3eb097
parent 819215 547144f5596c1a146b208d68d93950a6313080ca
child 819450 a6aacf58c4a6dc5621c3faa0387f775d699aa6f1
push id116556
push userbmo:rhunt@eqrion.net
push dateTue, 17 Jul 2018 21:35:26 +0000
reviewersnical
bugs1471639
milestone63.0a1
Bug 1471639 - Move edge padding to the paint thread. r=nical This commit ports over the last remaining operation for tiling that doesn't work on the paint thread. The difficult part for edge padding is that it is done outside of ValidateTile so it doesn't have an associated CapturedTilePaintState to be added to as an operation. We need it to be in the same paint state so that it's guaranteed to be run after painting has finished. This commit changes edge padding to instead be decided inside of ValidateTile and either sent to the paint thread if there is OMTP or executed right away. MozReview-Commit-ID: JDD4rH1fVwW
gfx/layers/BufferEdgePad.cpp
gfx/layers/BufferEdgePad.h
gfx/layers/PaintThread.cpp
gfx/layers/PaintThread.h
gfx/layers/SourceSurfaceSharedData.cpp
gfx/layers/client/MultiTiledContentClient.cpp
gfx/layers/moz.build
new file mode 100644
--- /dev/null
+++ b/gfx/layers/BufferEdgePad.cpp
@@ -0,0 +1,93 @@
+#include "BufferEdgePad.h"
+
+#include "mozilla/gfx/Point.h" // for IntSize
+#include "mozilla/gfx/Types.h" // for SurfaceFormat
+
+namespace mozilla {
+namespace layers {
+
+using namespace gfx;
+
+void
+PadDrawTargetOutFromRegion(RefPtr<DrawTarget> aDrawTarget, nsIntRegion &aRegion)
+{
+  struct LockedBits {
+    uint8_t *data;
+    IntSize size;
+    int32_t stride;
+    SurfaceFormat format;
+    static int clamp(int x, int min, int max)
+    {
+      if (x < min)
+        x = min;
+      if (x > max)
+        x = max;
+      return x;
+    }
+
+    static void ensure_memcpy(uint8_t *dst, uint8_t *src, size_t n, uint8_t *bitmap, int stride, int height)
+    {
+        if (src + n > bitmap + stride*height) {
+            MOZ_CRASH("GFX: long src memcpy");
+        }
+        if (src < bitmap) {
+            MOZ_CRASH("GFX: short src memcpy");
+        }
+        if (dst + n > bitmap + stride*height) {
+            MOZ_CRASH("GFX: long dst mempcy");
+        }
+        if (dst < bitmap) {
+            MOZ_CRASH("GFX: short dst mempcy");
+        }
+    }
+
+    static void visitor(void *closure, VisitSide side, int x1, int y1, int x2, int y2) {
+      LockedBits *lb = static_cast<LockedBits*>(closure);
+      uint8_t *bitmap = lb->data;
+      const int bpp = gfx::BytesPerPixel(lb->format);
+      const int stride = lb->stride;
+      const int width = lb->size.width;
+      const int height = lb->size.height;
+
+      if (side == VisitSide::TOP) {
+        if (y1 > 0) {
+          x1 = clamp(x1, 0, width - 1);
+          x2 = clamp(x2, 0, width - 1);
+          ensure_memcpy(&bitmap[x1*bpp + (y1-1) * stride], &bitmap[x1*bpp + y1 * stride], (x2 - x1) * bpp, bitmap, stride, height);
+          memcpy(&bitmap[x1*bpp + (y1-1) * stride], &bitmap[x1*bpp + y1 * stride], (x2 - x1) * bpp);
+        }
+      } else if (side == VisitSide::BOTTOM) {
+        if (y1 < height) {
+          x1 = clamp(x1, 0, width - 1);
+          x2 = clamp(x2, 0, width - 1);
+          ensure_memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[x1*bpp + (y1-1) * stride], (x2 - x1) * bpp, bitmap, stride, height);
+          memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[x1*bpp + (y1-1) * stride], (x2 - x1) * bpp);
+        }
+      } else if (side == VisitSide::LEFT) {
+        if (x1 > 0) {
+          while (y1 != y2) {
+            memcpy(&bitmap[(x1-1)*bpp + y1 * stride], &bitmap[x1*bpp + y1*stride], bpp);
+            y1++;
+          }
+        }
+      } else if (side == VisitSide::RIGHT) {
+        if (x1 < width) {
+          while (y1 != y2) {
+            memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[(x1-1)*bpp + y1*stride], bpp);
+            y1++;
+          }
+        }
+      }
+
+    }
+  } lb;
+
+  if (aDrawTarget->LockBits(&lb.data, &lb.size, &lb.stride, &lb.format)) {
+    // we can only pad software targets so if we can't lock the bits don't pad
+    aRegion.VisitEdges(lb.visitor, &lb);
+    aDrawTarget->ReleaseBits(lb.data);
+  }
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/BufferEdgePad.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 MOZILLA_LAYERS_BUFFER_EDGE_PAD_H
+#define MOZILLA_LAYERS_BUFFER_EDGE_PAD_H
+
+#include "mozilla/gfx/2D.h"
+#include "nsRegion.h"
+
+namespace mozilla {
+namespace layers {
+
+void PadDrawTargetOutFromRegion(RefPtr<gfx::DrawTarget> aDrawTarget, nsIntRegion &aRegion);
+
+} // namespace layers
+} // namespace mozilla
+
+#endif // MOZILLA_LAYERS_BUFFER_EDGE_PAD_H
--- a/gfx/layers/PaintThread.cpp
+++ b/gfx/layers/PaintThread.cpp
@@ -7,16 +7,17 @@
 #include "PaintThread.h"
 
 #include <algorithm>
 
 #include "base/task.h"
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "GeckoProfiler.h"
+#include "mozilla/layers/BufferEdgePad.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/ShadowLayers.h"
 #include "mozilla/layers/SyncObject.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/SyncRunnable.h"
 #include "nsIPropertyBag2.h"
@@ -96,16 +97,49 @@ CapturedTiledPaintState::Clear::ClearBuf
                                iter.Get().Width(), iter.Get().Height());
       mTarget->ClearRect(drawRect);
     }
   }
 
   mTarget->SetTransform(oldTransform);
 }
 
+void
+CapturedTiledPaintState::EdgePad::EdgePadBuffer()
+{
+  PadDrawTargetOutFromRegion(mTarget, mValidRegion);
+}
+
+void
+CapturedTiledPaintState::PrePaint()
+{
+  for (auto& copy : mCopies) {
+    copy.CopyBuffer();
+  }
+
+  for (auto& clear : mClears) {
+    clear.ClearBuffer();
+  }
+}
+
+void
+CapturedTiledPaintState::Paint()
+{
+  mTarget->DrawCapturedDT(mCapture, Matrix());
+  mTarget->Flush();
+}
+
+void
+CapturedTiledPaintState::PostPaint()
+{
+  if (mEdgePad) {
+    mEdgePad->EdgePadBuffer();
+  }
+}
+
 StaticAutoPtr<PaintThread> PaintThread::sSingleton;
 StaticRefPtr<nsIThread> PaintThread::sThread;
 PlatformThreadId PaintThread::sThreadId;
 
 PaintThread::PaintThread()
 {
 }
 
@@ -414,30 +448,19 @@ void
 PaintThread::AsyncPaintTiledContents(CompositorBridgeChild* aBridge,
                                      CapturedTiledPaintState* aState)
 {
   AUTO_PROFILER_LABEL("PaintThread::AsyncPaintTiledContents", GRAPHICS);
   
   MOZ_ASSERT(IsOnPaintWorkerThread());
   MOZ_ASSERT(aState);
 
-  for (auto& copy : aState->mCopies) {
-    copy.CopyBuffer();
-  }
-
-  for (auto& clear : aState->mClears) {
-    clear.ClearBuffer();
-  }
-
-  DrawTarget* target = aState->mTarget;
-  DrawTargetCapture* capture = aState->mCapture;
-
-  // Draw all the things into the actual dest target.
-  target->DrawCapturedDT(capture, Matrix());
-  target->Flush();
+  aState->PrePaint();
+  aState->Paint();
+  aState->PostPaint();
 
   if (gfxPrefs::LayersOMTPReleaseCaptureOnMainThread()) {
     // This should ensure the capture drawtarget, which may hold on to UnscaledFont objects,
     // gets destroyed on the main thread (See bug 1404742). This assumes (unflushed) target
     // DrawTargets do not themselves hold on to UnscaledFonts.
     NS_ReleaseOnMainThreadSystemGroup("CapturePaintState::DrawTargetCapture", aState->mCapture.forget());
   }
 
--- a/gfx/layers/PaintThread.h
+++ b/gfx/layers/PaintThread.h
@@ -218,16 +218,29 @@ public:
 
     void ClearBuffer();
 
     RefPtr<gfx::DrawTarget> mTarget;
     RefPtr<gfx::DrawTarget> mTargetOnWhite;
     nsIntRegion mDirtyRegion;
   };
 
+  struct EdgePad {
+    EdgePad(RefPtr<gfx::DrawTarget> aTarget,
+            nsIntRegion&& aValidRegion)
+      : mTarget(aTarget)
+      , mValidRegion(aValidRegion)
+    {}
+
+    void EdgePadBuffer();
+
+    RefPtr<gfx::DrawTarget> mTarget;
+    nsIntRegion mValidRegion;
+  };
+
   CapturedTiledPaintState()
   {}
   CapturedTiledPaintState(gfx::DrawTarget* aTarget,
                           gfx::DrawTargetCapture* aCapture)
   : mTarget(aTarget)
   , mCapture(aCapture)
   {}
 
@@ -239,20 +252,25 @@ public:
     }
   }
 
   void DropTextureClients()
   {
     mClients.clear();
   }
 
+  void PrePaint();
+  void Paint();
+  void PostPaint();
+
   RefPtr<gfx::DrawTarget> mTarget;
   RefPtr<gfx::DrawTargetCapture> mCapture;
   std::vector<Copy> mCopies;
   std::vector<Clear> mClears;
+  Maybe<EdgePad> mEdgePad;
 
   std::vector<RefPtr<TextureClient>> mClients;
 
 protected:
   virtual ~CapturedTiledPaintState() {}
 };
 
 class CompositorBridgeChild;
--- a/gfx/layers/SourceSurfaceSharedData.cpp
+++ b/gfx/layers/SourceSurfaceSharedData.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "SourceSurfaceSharedData.h"
 
 #include "mozilla/Likely.h"
 #include "mozilla/Types.h" // for decltype
 #include "mozilla/layers/SharedSurfacesChild.h"
 
+#include "base/process_util.h"
+
 #ifdef DEBUG
 /**
  * If defined, this makes SourceSurfaceSharedData::Finalize memory protect the
  * underlying shared buffer in the producing process (the content or UI
  * process). Given flushing the page table is expensive, and its utility is
  * predominantly diagnostic (in case of overrun), turn it off by default.
  */
 #define SHARED_SURFACE_PROTECT_FINALIZED
--- a/gfx/layers/client/MultiTiledContentClient.cpp
+++ b/gfx/layers/client/MultiTiledContentClient.cpp
@@ -3,16 +3,17 @@
 /* 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/MultiTiledContentClient.h"
 
 #include "ClientTiledPaintedLayer.h"
 #include "mozilla/layers/LayerMetricsWrapper.h"
+#include "mozilla/layers/BufferEdgePad.h"
 
 namespace mozilla {
 
 using namespace gfx;
 
 namespace layers {
 
 MultiTiledContentClient::MultiTiledContentClient(ClientTiledPaintedLayer& aPaintedLayer,
@@ -134,96 +135,16 @@ ClientMultiTiledLayerBuffer::PaintThebes
   }
 #endif
 
   mLastPaintContentType = GetContentType(&mLastPaintSurfaceMode);
   mCallback = nullptr;
   mCallbackData = nullptr;
 }
 
-void PadDrawTargetOutFromRegion(RefPtr<DrawTarget> drawTarget, nsIntRegion &region)
-{
-  struct LockedBits {
-    uint8_t *data;
-    IntSize size;
-    int32_t stride;
-    SurfaceFormat format;
-    static int clamp(int x, int min, int max)
-    {
-      if (x < min)
-        x = min;
-      if (x > max)
-        x = max;
-      return x;
-    }
-
-    static void ensure_memcpy(uint8_t *dst, uint8_t *src, size_t n, uint8_t *bitmap, int stride, int height)
-    {
-        if (src + n > bitmap + stride*height) {
-            MOZ_CRASH("GFX: long src memcpy");
-        }
-        if (src < bitmap) {
-            MOZ_CRASH("GFX: short src memcpy");
-        }
-        if (dst + n > bitmap + stride*height) {
-            MOZ_CRASH("GFX: long dst mempcy");
-        }
-        if (dst < bitmap) {
-            MOZ_CRASH("GFX: short dst mempcy");
-        }
-    }
-
-    static void visitor(void *closure, VisitSide side, int x1, int y1, int x2, int y2) {
-      LockedBits *lb = static_cast<LockedBits*>(closure);
-      uint8_t *bitmap = lb->data;
-      const int bpp = gfx::BytesPerPixel(lb->format);
-      const int stride = lb->stride;
-      const int width = lb->size.width;
-      const int height = lb->size.height;
-
-      if (side == VisitSide::TOP) {
-        if (y1 > 0) {
-          x1 = clamp(x1, 0, width - 1);
-          x2 = clamp(x2, 0, width - 1);
-          ensure_memcpy(&bitmap[x1*bpp + (y1-1) * stride], &bitmap[x1*bpp + y1 * stride], (x2 - x1) * bpp, bitmap, stride, height);
-          memcpy(&bitmap[x1*bpp + (y1-1) * stride], &bitmap[x1*bpp + y1 * stride], (x2 - x1) * bpp);
-        }
-      } else if (side == VisitSide::BOTTOM) {
-        if (y1 < height) {
-          x1 = clamp(x1, 0, width - 1);
-          x2 = clamp(x2, 0, width - 1);
-          ensure_memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[x1*bpp + (y1-1) * stride], (x2 - x1) * bpp, bitmap, stride, height);
-          memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[x1*bpp + (y1-1) * stride], (x2 - x1) * bpp);
-        }
-      } else if (side == VisitSide::LEFT) {
-        if (x1 > 0) {
-          while (y1 != y2) {
-            memcpy(&bitmap[(x1-1)*bpp + y1 * stride], &bitmap[x1*bpp + y1*stride], bpp);
-            y1++;
-          }
-        }
-      } else if (side == VisitSide::RIGHT) {
-        if (x1 < width) {
-          while (y1 != y2) {
-            memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[(x1-1)*bpp + y1*stride], bpp);
-            y1++;
-          }
-        }
-      }
-
-    }
-  } lb;
-
-  if (drawTarget->LockBits(&lb.data, &lb.size, &lb.stride, &lb.format)) {
-    // we can only pad software targets so if we can't lock the bits don't pad
-    region.VisitEdges(lb.visitor, &lb);
-    drawTarget->ReleaseBits(lb.data);
-  }
-}
-
 void ClientMultiTiledLayerBuffer::Update(const nsIntRegion& newValidRegion,
                                          const nsIntRegion& aPaintRegion,
                                          const nsIntRegion& aDirtyRegion,
                                          TilePaintFlags aFlags)
 {
   const IntSize scaledTileSize = GetScaledTileSize();
   const gfx::IntRect newBounds = newValidRegion.GetBounds();
 
@@ -279,16 +200,23 @@ void ClientMultiTiledLayerBuffer::Update
       }
 
       // Validating the tile may have required more to be painted.
       paintRegion.OrWith(tileDrawRegion);
       dirtyRegion.OrWith(tileDrawRegion);
     }
 
     if (!mPaintTiles.empty()) {
+      // Perform buffer copies and clears if we don't have the paint thread
+      if (!(aFlags & TilePaintFlags::Async)) {
+        for (const auto& state : mPaintStates) {
+          state->PrePaint();
+        }
+      }
+
       // Create a tiled draw target
       gfx::TileSet tileset;
       for (size_t i = 0; i < mPaintTiles.size(); ++i) {
         mPaintTiles[i].mTileOrigin -= mTilingOrigin;
       }
       tileset.mTiles = &mPaintTiles[0];
       tileset.mTileCount = mPaintTiles.size();
       RefPtr<DrawTarget> drawTarget = gfx::Factory::CreateTiledDrawTarget(tileset);
@@ -303,65 +231,37 @@ void ClientMultiTiledLayerBuffer::Update
       MOZ_ASSERT(ctx); // already checked the draw target above
       ctx->SetMatrix(
         ctx->CurrentMatrix().PreScale(mResolution, mResolution).PreTranslate(-mTilingOrigin));
 
       mCallback(&mPaintedLayer, ctx, paintRegion, dirtyRegion,
                 DrawRegionClip::DRAW, nsIntRegion(), mCallbackData);
       ctx = nullptr;
 
+      // Dispatch to the paint thread or do any work left over
       if (aFlags & TilePaintFlags::Async) {
         for (const auto& state : mPaintStates) {
           PaintThread::Get()->PaintTiledContents(state);
         }
         mManager->SetQueuedAsyncPaints();
-        MOZ_ASSERT(mPaintStates.size() > 0);
-        mPaintStates.clear();
       } else {
-        MOZ_ASSERT(mPaintStates.size() == 0);
+        for (const auto& state : mPaintStates) {
+          state->PostPaint();
+        }
       }
+      mPaintStates.clear();
 
       // Reset
       mPaintTiles.clear();
       mTilingOrigin = IntPoint(std::numeric_limits<int32_t>::max(),
                                std::numeric_limits<int32_t>::max());
     }
 
-    bool edgePaddingEnabled = gfxPrefs::TileEdgePaddingEnabled();
     for (uint32_t i = 0; i < mRetainedTiles.Length(); ++i) {
       TileClient& tile = mRetainedTiles[i];
-
-      // Only worry about padding when not doing low-res because it simplifies
-      // the math and the artifacts won't be noticable
-      // Edge padding prevents sampling artifacts when compositing.
-      if (edgePaddingEnabled && mResolution == 1 &&
-          tile.mFrontBuffer && tile.mFrontBuffer->IsLocked()) {
-
-        const TileCoordIntPoint tileCoord = newTiles.TileCoord(i);
-        IntPoint tileOffset = GetTileOffset(tileCoord);
-        // Strictly speakig we want the unscaled rect here, but it doesn't matter
-        // because we only run this code when the resolution is equal to 1.
-        IntRect tileRect = IntRect(tileOffset.x, tileOffset.y,
-                                   GetTileSize().width, GetTileSize().height);
-
-        nsIntRegion tileDrawRegion = IntRect(tileOffset, scaledTileSize);
-        tileDrawRegion.AndWith(paintRegion);
-
-        nsIntRegion tileValidRegion = mValidRegion;
-        tileValidRegion.OrWith(tileDrawRegion);
-
-        // We only need to pad out if the tile has area that's not valid
-        if (!tileValidRegion.Contains(tileRect)) {
-          tileValidRegion = tileValidRegion.Intersect(tileRect);
-          // translate the region into tile space and pad
-          tileValidRegion.MoveBy(-IntPoint(tileOffset.x, tileOffset.y));
-          RefPtr<DrawTarget> drawTarget = tile.mFrontBuffer->BorrowDrawTarget();
-          PadDrawTargetOutFromRegion(drawTarget, tileValidRegion);
-        }
-      }
       UnlockTile(tile);
     }
   }
 
   mTiles = newTiles;
   mValidRegion = newValidRegion;
 }
 
@@ -456,61 +356,74 @@ ClientMultiTiledLayerBuffer::ValidateTil
 
   RefPtr<DrawTarget> drawTarget;
   if (dtOnWhite) {
     drawTarget = Factory::CreateDualDrawTarget(dt, dtOnWhite);
   } else {
     drawTarget = dt;
   }
 
+  // Create the paint operation that will be either dispatched to the paint
+  // thread, or executed synchronously
+  RefPtr<CapturedTiledPaintState> paintState = new CapturedTiledPaintState();
+
+  paintState->mCopies = std::move(asyncPaintCopies);
+
   // We need to clear the dirty region of the tile before painting
   // if we are painting non-opaque content
-  Maybe<CapturedTiledPaintState::Clear> clear = Nothing();
   if (mode != SurfaceMode::SURFACE_OPAQUE) {
-    clear = Some(CapturedTiledPaintState::Clear{
+    auto clear = CapturedTiledPaintState::Clear{
       dt,
       dtOnWhite,
       tileDirtyRegion
-    });
+    };
+    paintState->mClears.push_back(clear);
   }
 
-  // Queue or execute the paint operation
+  // Only worry about padding when not doing low-res because it simplifies
+  // the math and the artifacts won't be noticable
+  // Edge padding prevents sampling artifacts when compositing.
+  if (gfxPrefs::TileEdgePaddingEnabled() && mResolution == 1) {
+    IntRect tileRect = IntRect(0, 0,
+                               GetScaledTileSize().width,
+                               GetScaledTileSize().height);
+
+    // We only need to pad out if the tile has area that's not valid
+    if (!tileVisibleRegion.Contains(tileRect)) {
+      tileVisibleRegion = tileVisibleRegion.Intersect(tileRect);
+
+      paintState->mEdgePad = Some(CapturedTiledPaintState::EdgePad{
+        drawTarget,
+        std::move(tileVisibleRegion),
+      });
+    }
+  }
+
+  paintState->mClients = std::move(asyncPaintClients);
+  paintState->mClients.push_back(backBuffer);
+  if (backBufferOnWhite) {
+    paintState->mClients.push_back(backBufferOnWhite);
+  }
+
   gfx::Tile paintTile;
   paintTile.mTileOrigin = gfx::IntPoint(aTileOrigin.x, aTileOrigin.y);
 
   if (aFlags & TilePaintFlags::Async) {
-    RefPtr<CapturedTiledPaintState> asyncPaint = new CapturedTiledPaintState();
-
     RefPtr<DrawTargetCapture> captureDT =
       Factory::CreateCaptureDrawTarget(drawTarget->GetBackendType(),
                                        drawTarget->GetSize(),
                                        drawTarget->GetFormat());
     paintTile.mDrawTarget = captureDT;
-    asyncPaint->mTarget = drawTarget;
-    asyncPaint->mCapture = captureDT;
-
-    asyncPaint->mCopies = std::move(asyncPaintCopies);
-    if (clear) {
-      asyncPaint->mClears.push_back(*clear);
-    }
-
-    asyncPaint->mClients = std::move(asyncPaintClients);
-    asyncPaint->mClients.push_back(backBuffer);
-    if (backBufferOnWhite) {
-      asyncPaint->mClients.push_back(backBufferOnWhite);
-    }
-
-    mPaintStates.push_back(asyncPaint);
+    paintState->mTarget = drawTarget;
+    paintState->mCapture = captureDT;
   } else {
     paintTile.mDrawTarget = drawTarget;
-    if (clear) {
-      clear->ClearBuffer();
-    }
   }
 
+  mPaintStates.push_back(paintState);
   mPaintTiles.push_back(paintTile);
 
   mTilingOrigin.x = std::min(mTilingOrigin.x, paintTile.mTileOrigin.x);
   mTilingOrigin.y = std::min(mTilingOrigin.y, paintTile.mTileOrigin.y);
 
   // The new buffer is now validated, remove the dirty region from it.
   aTile.mInvalidBack.SubOut(tileDirtyRegion);
 
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -120,16 +120,17 @@ EXPORTS.mozilla.layers += [
     'AsyncCanvasRenderer.h',
     'AtomicRefCountedWithFinalize.h',
     'AxisPhysicsModel.h',
     'AxisPhysicsMSDModel.h',
     'basic/BasicCompositor.h',
     'basic/MacIOSurfaceTextureHostBasic.h',
     'basic/TextureHostBasic.h',
     'BSPTree.h',
+    'BufferEdgePad.h',
     'BufferTexture.h',
     'CanvasRenderer.h',
     'client/CanvasClient.h',
     'client/CompositableClient.h',
     'client/ContentClient.h',
     'client/GPUVideoTextureClient.h',
     'client/ImageClient.h',
     'client/MultiTiledContentClient.h',
@@ -345,16 +346,17 @@ UNIFIED_SOURCES += [
     'basic/BasicCompositor.cpp',
     'basic/BasicContainerLayer.cpp',
     'basic/BasicImages.cpp',
     'basic/BasicLayerManager.cpp',
     'basic/BasicLayersImpl.cpp',
     'basic/BasicPaintedLayer.cpp',
     'basic/TextureHostBasic.cpp',
     'BSPTree.cpp',
+    'BufferEdgePad.cpp',
     'BufferTexture.cpp',
     'BufferUnrotate.cpp',
     'CanvasRenderer.cpp',
     'client/CanvasClient.cpp',
     'client/ClientCanvasLayer.cpp',
     'client/ClientCanvasRenderer.cpp',
     'client/ClientColorLayer.cpp',
     'client/ClientContainerLayer.cpp',