Record buffer operations to a struct for replaying on paint thread (bug 1399692 part 6, r=bas)
authorRyan Hunt <rhunt@eqrion.net>
Wed, 25 Oct 2017 10:20:49 -0400
changeset 444463 a327b4cb9dca49cba23262b6fcf6d6ec0aa56ad2
parent 444462 dec1d0b6db699a157b60f358e3d69d7ae53647ea
child 444464 f9ffbdc01598a8b14bbee691ff25cfe4bc5ed95d
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbas
bugs1399692
milestone58.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
Record buffer operations to a struct for replaying on paint thread (bug 1399692 part 6, r=bas) This commit adds a CapturedBufferState which is used to record all the operations that are necessary for preparing the buffers. The commands are then instantly executed to preserve the same behavior, but in the following commit they will be dispatched to the paint thread. Note: RotatedBuffer's aren't thread safe and so a shallow copy needs to be made for sending to the paint thread. This complicates the code for AdjustTo as it can fail naturally and the buffer parameter changes are needed later in BeginPaint. So the code for AdjustTo is split up a bit to accomodate that. MozReview-Commit-ID: FwSwFay887o
gfx/layers/PaintThread.cpp
gfx/layers/PaintThread.h
gfx/layers/RotatedBuffer.cpp
gfx/layers/RotatedBuffer.h
gfx/layers/client/ContentClient.cpp
gfx/layers/client/ContentClient.h
gfx/layers/moz.build
--- a/gfx/layers/PaintThread.cpp
+++ b/gfx/layers/PaintThread.cpp
@@ -16,16 +16,40 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/SyncRunnable.h"
 
 namespace mozilla {
 namespace layers {
 
 using namespace gfx;
 
+bool
+CapturedBufferState::Copy::CopyBuffer()
+{
+  if (mSource->Lock(OpenMode::OPEN_READ_ONLY)) {
+    mDestination->UpdateDestinationFrom(*mSource, mBounds);
+    mSource->Unlock();
+    return true;
+  }
+  return false;
+}
+
+bool
+CapturedBufferState::Unrotate::UnrotateBuffer()
+{
+  return mBuffer->UnrotateBufferTo(mParameters);
+}
+
+bool
+CapturedBufferState::PrepareBuffer()
+{
+  return (!mBufferCopy || mBufferCopy->CopyBuffer()) &&
+         (!mBufferUnrotate || mBufferUnrotate->UnrotateBuffer());
+}
+
 StaticAutoPtr<PaintThread> PaintThread::sSingleton;
 StaticRefPtr<nsIThread> PaintThread::sThread;
 PlatformThreadId PaintThread::sThreadId;
 
 // RAII make sure we clean up and restore our draw targets
 // when we paint async.
 struct MOZ_STACK_CLASS AutoCapturedPaintSetup
 {
--- a/gfx/layers/PaintThread.h
+++ b/gfx/layers/PaintThread.h
@@ -7,16 +7,17 @@
 #ifndef MOZILLA_LAYERS_PAINTTHREAD_H
 #define MOZILLA_LAYERS_PAINTTHREAD_H
 
 #include "base/platform_thread.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/layers/TextureClient.h"
+#include "RotatedBuffer.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace gfx {
 class DrawTarget;
 class DrawTargetCapture;
 };
 
@@ -53,16 +54,65 @@ public:
   gfx::Matrix mTargetTransform;
   SurfaceMode mSurfaceMode;
   gfxContentType mContentType;
 
 protected:
   virtual ~CapturedPaintState() {}
 };
 
+// Holds the key operations for a ContentClient to prepare
+// its buffers for painting
+class CapturedBufferState final {
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CapturedBufferState)
+public:
+  struct Copy {
+    Copy(RefPtr<RotatedBuffer> aSource,
+         RefPtr<RotatedBuffer> aDestination,
+         gfx::IntRect aBounds)
+      : mSource(aSource)
+      , mDestination(aDestination)
+      , mBounds(aBounds)
+    {}
+
+    bool CopyBuffer();
+
+    RefPtr<RotatedBuffer> mSource;
+    RefPtr<RotatedBuffer> mDestination;
+    gfx::IntRect mBounds;
+  };
+
+  struct Unrotate {
+    Unrotate(RotatedBuffer::Parameters aParameters,
+             RefPtr<RotatedBuffer> aBuffer)
+      : mParameters(aParameters)
+      , mBuffer(aBuffer)
+    {}
+
+    bool UnrotateBuffer();
+
+    RotatedBuffer::Parameters mParameters;
+    RefPtr<RotatedBuffer> mBuffer;
+  };
+
+  /**
+   * Prepares the rotated buffers for painting by copying a previous frame
+   * into the buffer and/or unrotating the pixels and returns whether the
+   * operations were successful. If this fails a new buffer should be created
+   * for the frame.
+   */
+  bool PrepareBuffer();
+
+  Maybe<Copy> mBufferCopy;
+  Maybe<Unrotate> mBufferUnrotate;
+
+protected:
+  ~CapturedBufferState() {}
+};
+
 typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState* aPaintState);
 
 class CompositorBridgeChild;
 
 class PaintThread final
 {
   friend void DestroyPaintThread(UniquePtr<PaintThread>&& aPaintThread);
 
--- a/gfx/layers/RotatedBuffer.cpp
+++ b/gfx/layers/RotatedBuffer.cpp
@@ -286,112 +286,118 @@ WrapRotationAxis(int32_t* aRotationPoint
   if (*aRotationPoint < 0) {
     *aRotationPoint += aSize;
   } else if (*aRotationPoint >= aSize) {
     *aRotationPoint -= aSize;
   }
 }
 
 bool
-RotatedBuffer::AdjustTo(const gfx::IntRect& aDestBufferRect,
-                        const gfx::IntRect& aDrawBounds,
-                        bool aCanHaveRotation,
-                        bool aCanDrawRotated)
+RotatedBuffer::Parameters::IsRotated() const
+{
+  return mBufferRotation != IntPoint(0,0);
+}
+
+bool
+RotatedBuffer::Parameters::RectWrapsBuffer(const gfx::IntRect& aRect) const
+{
+  int32_t xBoundary = mBufferRect.XMost() - mBufferRotation.x;
+  int32_t yBoundary = mBufferRect.YMost() - mBufferRotation.y;
+  return (aRect.x < xBoundary && xBoundary < aRect.XMost()) ||
+         (aRect.y < yBoundary && yBoundary < aRect.YMost());
+}
+
+void
+RotatedBuffer::Parameters::SetUnrotated()
+{
+  mBufferRotation = IntPoint(0,0);
+  mDidSelfCopy = true;
+}
+
+RotatedBuffer::Parameters
+RotatedBuffer::AdjustedParameters(const gfx::IntRect& aDestBufferRect) const
 {
   IntRect keepArea;
   if (keepArea.IntersectRect(aDestBufferRect, mBufferRect)) {
     // Set mBufferRotation so that the pixels currently in mDTBuffer
     // will still be rendered in the right place when mBufferRect
     // changes to aDestBufferRect.
     IntPoint newRotation = mBufferRotation +
       (aDestBufferRect.TopLeft() - mBufferRect.TopLeft());
     WrapRotationAxis(&newRotation.x, mBufferRect.Width());
     WrapRotationAxis(&newRotation.y, mBufferRect.Height());
     NS_ASSERTION(gfx::IntRect(gfx::IntPoint(0,0), mBufferRect.Size()).Contains(newRotation),
                  "newRotation out of bounds");
 
-    int32_t xBoundary = aDestBufferRect.XMost() - newRotation.x;
-    int32_t yBoundary = aDestBufferRect.YMost() - newRotation.y;
-    bool drawWrapsBuffer = (aDrawBounds.x < xBoundary && xBoundary < aDrawBounds.XMost()) ||
-                           (aDrawBounds.y < yBoundary && yBoundary < aDrawBounds.YMost());
+    return Parameters{aDestBufferRect, newRotation};
+  }
+
+  // No pixels are going to be kept. The whole visible region
+  // will be redrawn, so we don't need to copy anything, so we don't
+  // set destBuffer.
+  return Parameters{aDestBufferRect, IntPoint(0,0)};
+}
 
-    if ((drawWrapsBuffer && !aCanDrawRotated) ||
-        (newRotation != IntPoint(0,0) && !aCanHaveRotation)) {
-      // The stuff we need to redraw will wrap around an edge of the
-      // buffer (and the caller doesn't know how to support that), so
-      // move the pixels we can keep into a position that lets us
-      // redraw in just one quadrant.
-      RefPtr<gfx::DrawTarget> dtBuffer = GetDTBuffer();
-      RefPtr<gfx::DrawTarget> dtBufferOnWhite = GetDTBufferOnWhite();
+bool
+RotatedBuffer::UnrotateBufferTo(const Parameters& aParameters)
+{
+  RefPtr<gfx::DrawTarget> dtBuffer = GetDTBuffer();
+  RefPtr<gfx::DrawTarget> dtBufferOnWhite = GetDTBufferOnWhite();
 
-      if (mBufferRotation == IntPoint(0,0)) {
-        IntRect srcRect(IntPoint(0, 0), mBufferRect.Size());
-        IntPoint dest = mBufferRect.TopLeft() - aDestBufferRect.TopLeft();
-
-        MOZ_ASSERT(dtBuffer && dtBuffer->IsValid());
-        dtBuffer->CopyRect(srcRect, dest);
-        if (HaveBufferOnWhite()) {
-          MOZ_ASSERT(dtBufferOnWhite && dtBufferOnWhite->IsValid());
-          dtBufferOnWhite->CopyRect(srcRect, dest);
-        }
+  if (mBufferRotation == IntPoint(0,0)) {
+    IntRect srcRect(IntPoint(0, 0), mBufferRect.Size());
+    IntPoint dest = mBufferRect.TopLeft() - aParameters.mBufferRect.TopLeft();
 
-        mDidSelfCopy = true;
-        mBufferRect = aDestBufferRect;
-      } else {
-        // With azure and a data surface perform an buffer unrotate
-        // (SelfCopy).
-        unsigned char* data;
-        IntSize size;
-        int32_t stride;
-        SurfaceFormat format;
-
-        if (dtBuffer->LockBits(&data, &size, &stride, &format)) {
-          uint8_t bytesPerPixel = BytesPerPixel(format);
-          BufferUnrotate(data,
-                         size.width * bytesPerPixel,
-                         size.height, stride,
-                         newRotation.x * bytesPerPixel, newRotation.y);
-          dtBuffer->ReleaseBits(data);
+    MOZ_ASSERT(dtBuffer && dtBuffer->IsValid());
+    dtBuffer->CopyRect(srcRect, dest);
+    if (HaveBufferOnWhite()) {
+      MOZ_ASSERT(dtBufferOnWhite && dtBufferOnWhite->IsValid());
+      dtBufferOnWhite->CopyRect(srcRect, dest);
+    }
+  } else {
+    // With azure and a data surface perform an buffer unrotate
+    // (SelfCopy).
+    unsigned char* data;
+    IntSize size;
+    int32_t stride;
+    SurfaceFormat format;
 
-          if (HaveBufferOnWhite()) {
-            MOZ_ASSERT(dtBufferOnWhite && dtBufferOnWhite->IsValid());
-            dtBufferOnWhite->LockBits(&data, &size, &stride, &format);
-            uint8_t bytesPerPixel = BytesPerPixel(format);
-            BufferUnrotate(data,
-                           size.width * bytesPerPixel,
-                           size.height, stride,
-                           newRotation.x * bytesPerPixel, newRotation.y);
-            dtBufferOnWhite->ReleaseBits(data);
-          }
+    if (dtBuffer->LockBits(&data, &size, &stride, &format)) {
+      uint8_t bytesPerPixel = BytesPerPixel(format);
+      BufferUnrotate(data,
+                     size.width * bytesPerPixel,
+                     size.height, stride,
+                     aParameters.mBufferRotation.x * bytesPerPixel,
+                     aParameters.mBufferRotation.y);
+      dtBuffer->ReleaseBits(data);
 
-          // Buffer unrotate moves all the pixels
-          mDidSelfCopy = true;
-          mBufferRect = aDestBufferRect;
-          mBufferRotation = IntPoint(0, 0);
-        }
-
-        if (!mDidSelfCopy) {
-          // We couldn't unrotate the buffer, so we need to create a
-          // new one and start from scratch
-          return false;
-        }
+      if (HaveBufferOnWhite()) {
+        MOZ_ASSERT(dtBufferOnWhite && dtBufferOnWhite->IsValid());
+        dtBufferOnWhite->LockBits(&data, &size, &stride, &format);
+        uint8_t bytesPerPixel = BytesPerPixel(format);
+        BufferUnrotate(data,
+                       size.width * bytesPerPixel,
+                       size.height, stride,
+                       aParameters.mBufferRotation.x * bytesPerPixel,
+                       aParameters.mBufferRotation.y);
+        dtBufferOnWhite->ReleaseBits(data);
       }
     } else {
-      mBufferRect = aDestBufferRect;
-      mBufferRotation = newRotation;
+      return false;
     }
-  } else {
-    // No pixels are going to be kept. The whole visible region
-    // will be redrawn, so we don't need to copy anything, so we don't
-    // set destBuffer.
-    mBufferRect = aDestBufferRect;
-    mBufferRotation = IntPoint(0,0);
   }
+  return true;
+}
 
-  return true;
+void
+RotatedBuffer::SetParameters(const RotatedBuffer::Parameters& aParameters)
+{
+  mBufferRect = aParameters.mBufferRect;
+  mBufferRotation = aParameters.mBufferRotation;
+  mDidSelfCopy = aParameters.mDidSelfCopy;
 }
 
 RotatedBuffer::ContentType
 RotatedBuffer::GetContentType() const
 {
   return ContentForFormat(GetFormat());
 }
 
--- a/gfx/layers/RotatedBuffer.h
+++ b/gfx/layers/RotatedBuffer.h
@@ -18,21 +18,19 @@
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsRegion.h"                   // for nsIntRegion
 #include "LayersTypes.h"
 
 namespace mozilla {
 namespace layers {
 
-class CapturedPaintState;
-
-typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState*);
-
 class PaintedLayer;
+class CapturedBufferState;
+class ContentClient;
 
 // Mixin class for classes which need logic for loaning out a draw target.
 // See comments on BorrowDrawTargetForQuadrantUpdate.
 class BorrowDrawTarget
 {
 public:
   void ReturnDrawTarget(gfx::DrawTarget*& aReturned);
 
@@ -152,25 +150,48 @@ public:
    */
   gfx::DrawTarget*
   BorrowDrawTargetForQuadrantUpdate(const gfx::IntRect& aBounds,
                                     ContextSource aSource,
                                     DrawIterator* aIter,
                                     bool aSetTransform = true,
                                     gfx::Matrix* aOutTransform = nullptr);
 
+  struct Parameters {
+    Parameters(const gfx::IntRect& aBufferRect,
+               const gfx::IntPoint& aBufferRotation)
+      : mBufferRect(aBufferRect)
+      , mBufferRotation(aBufferRotation)
+      , mDidSelfCopy(false)
+    {
+    }
+
+    bool IsRotated() const;
+    bool RectWrapsBuffer(const gfx::IntRect& aRect) const;
+
+    void SetUnrotated();
+
+    gfx::IntRect  mBufferRect;
+    gfx::IntPoint mBufferRotation;
+    bool mDidSelfCopy;
+  };
+
   /**
-   * Adjusts the buffer to be centered on the destination buffer rect,
-   * and ready to draw the specified bounds. Returns whether a new buffer
-   * needs to be created.
+   * Returns the new buffer parameters for rotating to a
+   * destination buffer rect.
    */
-  bool AdjustTo(const gfx::IntRect& aDestBufferRect,
-                const gfx::IntRect& aDrawBounds,
-                bool aCanHaveRotation,
-                bool aCanDrawRotated);
+  Parameters AdjustedParameters(const gfx::IntRect& aDestBufferRect) const;
+
+  /**
+   * Unrotates the pixels of the rotated buffer for the specified
+   * new buffer parameters.
+   */
+  bool UnrotateBufferTo(const Parameters& aParameters);
+
+  void SetParameters(const Parameters& aParameters);
 
   /**
    * |BufferRect()| is the rect of device pixels that this
    * RotatedBuffer covers.  That is what DrawBufferWithRotation()
    * will paint when it's called.
    */
   const gfx::IntRect& BufferRect() const { return mBufferRect; }
   const gfx::IntPoint& BufferRotation() const { return mBufferRotation; }
@@ -217,16 +238,23 @@ public:
 
   virtual gfx::SurfaceFormat GetFormat() const = 0;
 
   virtual already_AddRefed<gfx::SourceSurface> GetSourceSurface(ContextSource aSource) const = 0;
 
   virtual gfx::DrawTarget* GetDTBuffer() const = 0;
   virtual gfx::DrawTarget* GetDTBufferOnWhite() const = 0;
 
+  /**
+   * Creates a shallow copy of the rotated buffer with the same underlying
+   * texture clients and draw targets. Rotated buffers are not thread safe,
+   * so a copy needs to be sent for off main thread painting.
+   */
+  virtual RefPtr<RotatedBuffer> ShallowCopy() const = 0;
+
 protected:
   virtual ~RotatedBuffer() {}
 
   enum XSide {
     LEFT, RIGHT
   };
   enum YSide {
     TOP, BOTTOM
@@ -294,23 +322,45 @@ public:
 
   virtual gfx::SurfaceFormat GetFormat() const override;
 
   virtual already_AddRefed<gfx::SourceSurface> GetSourceSurface(ContextSource aSource) const override;
 
   virtual gfx::DrawTarget* GetDTBuffer() const override;
   virtual gfx::DrawTarget* GetDTBufferOnWhite() const override;
 
+  virtual TextureClient* GetClient() const override { return mClient; }
+  virtual TextureClient* GetClientOnWhite() const override { return mClientOnWhite; }
+
+  virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
+    return new RemoteRotatedBuffer {
+      mClient, mClientOnWhite,
+      mTarget, mTargetOnWhite,
+      mBufferRect, mBufferRotation
+    };
+  }
+
   TextureClient* GetClient() const { return mClient; }
   TextureClient* GetClientOnWhite() const { return mClientOnWhite; }
 
   void SyncWithObject(SyncObjectClient* aSyncObject);
   void Clear();
 
 private:
+  RemoteRotatedBuffer(TextureClient* aClient, TextureClient* aClientOnWhite,
+                      gfx::DrawTarget* aTarget, gfx::DrawTarget* aTargetOnWhite,
+                      const gfx::IntRect& aBufferRect,
+                      const gfx::IntPoint& aBufferRotation)
+    : RotatedBuffer(aBufferRect, aBufferRotation)
+    , mClient(aClient)
+    , mClientOnWhite(aClientOnWhite)
+    , mTarget(aTarget)
+    , mTargetOnWhite(aTargetOnWhite)
+  { }
+
   RefPtr<TextureClient> mClient;
   RefPtr<TextureClient> mClientOnWhite;
 
   RefPtr<gfx::DrawTarget> mTarget;
   RefPtr<gfx::DrawTarget> mTargetOnWhite;
 };
 
 /**
@@ -337,16 +387,23 @@ public:
 
   virtual gfx::SurfaceFormat GetFormat() const override;
 
   virtual already_AddRefed<gfx::SourceSurface> GetSourceSurface(ContextSource aSource) const override;
 
   virtual gfx::DrawTarget* GetDTBuffer() const override;
   virtual gfx::DrawTarget* GetDTBufferOnWhite() const override;
 
+  virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
+    return new DrawTargetRotatedBuffer {
+        mTarget, mTargetOnWhite,
+        mBufferRect, mBufferRotation
+      };
+  }
+
 private:
   RefPtr<gfx::DrawTarget> mTarget;
   RefPtr<gfx::DrawTarget> mTargetOnWhite;
 };
 
 /**
  * SourceRotatedBuffer is a rotated buffer that is backed by source surfaces,
  * and may only be used to draw into other buffers or be read directly.
@@ -371,16 +428,20 @@ public:
   virtual gfx::SurfaceFormat GetFormat() const override;
 
   virtual bool HaveBuffer() const override { return !!mSource; }
   virtual bool HaveBufferOnWhite() const override { return !!mSourceOnWhite; }
 
   virtual gfx::DrawTarget* GetDTBuffer() const override { return nullptr; }
   virtual gfx::DrawTarget* GetDTBufferOnWhite() const override { return nullptr; }
 
+  virtual RefPtr<RotatedBuffer> ShallowCopy() const override {
+    return nullptr;
+  }
+
 private:
   RefPtr<gfx::SourceSurface> mSource;
   RefPtr<gfx::SourceSurface> mSourceOnWhite;
 };
 
 } // namespace layers
 } // namespace mozilla
 
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -18,16 +18,17 @@
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/gfx/Rect.h"           // for Rect
 #include "mozilla/gfx/Types.h"
 #include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayersMessages.h"  // for ThebesBufferData
 #include "mozilla/layers/LayersTypes.h"
+#include "mozilla/layers/PaintThread.h"
 #include "nsDebug.h"                    // for NS_ASSERTION, NS_WARNING, etc
 #include "nsISupportsImpl.h"            // for gfxContext::Release, etc
 #include "nsIWidget.h"                  // for nsIWidget
 #include "nsLayoutUtils.h"
 #ifdef XP_WIN
 #include "gfxWindowsPlatform.h"
 #endif
 #ifdef MOZ_WIDGET_GTK
@@ -141,45 +142,63 @@ ContentClient::BeginPaint(PaintedLayer* 
   }
 
   result.mRegionToDraw.Sub(dest.mNeededRegion,
                            dest.mValidRegion);
 
   if (result.mRegionToDraw.IsEmpty())
     return result;
 
-  OpenMode lockMode = aFlags & PAINT_ASYNC ? OpenMode::OPEN_READ_ASYNC_WRITE
-                                           : OpenMode::OPEN_READ_WRITE;
-
   // We need to disable rotation if we're going to be resampled when
   // drawing, because we might sample across the rotation boundary.
   // Also disable buffer rotation when using webrender.
   bool canHaveRotation = gfxPlatform::BufferRotationEnabled() &&
                          !(aFlags & (PAINT_WILL_RESAMPLE | PAINT_NO_ROTATION)) &&
                          !(aLayer->Manager()->AsWebRenderLayerManager());
   bool canDrawRotated = aFlags & PAINT_CAN_DRAW_ROTATED;
+  bool asyncPaint = (aFlags & PAINT_ASYNC);
+
   IntRect drawBounds = result.mRegionToDraw.GetBounds();
+  OpenMode lockMode = asyncPaint ? OpenMode::OPEN_READ_ASYNC_WRITE
+                                 : OpenMode::OPEN_READ_WRITE;
 
   if (dest.mCanReuseBuffer) {
     MOZ_ASSERT(mBuffer);
 
+    bool canReuseBuffer = false;
+
     if (mBuffer->Lock(lockMode)) {
+      RefPtr<CapturedBufferState> bufferState = new CapturedBufferState();
+
       // Do not modify result.mRegionToDraw or result.mContentType after this call.
-      FinalizeFrame(result.mRegionToDraw);
+      FinalizeFrame(result.mRegionToDraw, bufferState);
+
+      auto newParameters = mBuffer->AdjustedParameters(dest.mBufferRect);
 
-      if (!mBuffer->AdjustTo(dest.mBufferRect,
-                             drawBounds,
-                             canHaveRotation,
-                             canDrawRotated)) {
-        dest.mBufferRect = ComputeBufferRect(dest.mNeededRegion.GetBounds());
-        dest.mCanReuseBuffer = false;
-        dest.mMustRemoveFrontBuffer = true;
+      if ((!canHaveRotation && newParameters.IsRotated()) ||
+          (!canDrawRotated && newParameters.RectWrapsBuffer(drawBounds))) {
+        bufferState->mBufferUnrotate = Some(CapturedBufferState::Unrotate {
+          newParameters,
+          mBuffer->ShallowCopy(),
+        });
+      }
+
+      if (bufferState->PrepareBuffer()) {
+        if (bufferState->mBufferUnrotate) {
+          newParameters.SetUnrotated();
+        }
+        mBuffer->SetParameters(newParameters);
+        canReuseBuffer = true;
+      }
+    }
+
+    if (!canReuseBuffer) {
+      if (mBuffer->IsLocked()) {
         mBuffer->Unlock();
       }
-    } else {
       dest.mBufferRect = ComputeBufferRect(dest.mNeededRegion.GetBounds());
       dest.mCanReuseBuffer = false;
       dest.mMustRemoveFrontBuffer = true;
     }
   }
 
   NS_ASSERTION(!(aFlags & PAINT_WILL_RESAMPLE) || dest.mBufferRect == dest.mNeededRegion.GetBounds(),
                "If we're resampling, we need to validate the entire buffer");
@@ -210,20 +229,25 @@ ContentClient::BeginPaint(PaintedLayer* 
 
     if (!newBuffer->Lock(lockMode)) {
       gfxCriticalNote << "Failed to lock new back buffer.";
       return result;
     }
 
     // If we have an existing front buffer, copy it into the new back buffer
     if (RefPtr<RotatedBuffer> frontBuffer = GetFrontBuffer()) {
-      if (frontBuffer->Lock(OpenMode::OPEN_READ_ONLY)) {
-        newBuffer->UpdateDestinationFrom(*frontBuffer, newBuffer->BufferRect());
-        frontBuffer->Unlock();
-      } else {
+      RefPtr<CapturedBufferState> bufferState = new CapturedBufferState();
+
+      bufferState->mBufferCopy = Some(CapturedBufferState::Copy {
+        frontBuffer->ShallowCopy(),
+        newBuffer->ShallowCopy(),
+        newBuffer->BufferRect(),
+      });
+
+      if (!bufferState->PrepareBuffer()) {
         gfxCriticalNote << "Failed to copy front buffer to back buffer.";
         return result;
       }
 
       if (dest.mMustRemoveFrontBuffer) {
         Clear();
       }
     }
@@ -865,17 +889,18 @@ ContentClientDoubleBuffered::GetFrontBuf
   return mFrontBuffer;
 }
 
 // Sync front/back buffers content
 // After executing, the new back buffer has the same (interesting) pixels as
 // the new front buffer, and mValidRegion et al. are correct wrt the new
 // back buffer (i.e. as they were for the old back buffer)
 void
-ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw)
+ContentClientDoubleBuffered::FinalizeFrame(const nsIntRegion& aRegionToDraw,
+                                           CapturedBufferState* aPrepareState)
 {
   if (!mFrontAndBackBufferDiffer) {
     MOZ_ASSERT(!mFrontBuffer || !mFrontBuffer->DidSelfCopy(),
                "If the front buffer did a self copy then our front and back buffer must be different.");
     return;
   }
 
   MOZ_ASSERT(mFrontBuffer && mBuffer);
@@ -912,16 +937,18 @@ ContentClientDoubleBuffered::FinalizeFra
 
   // No point in sync'ing what we are going to draw over anyway. And if there is
   // nothing to sync at all, there is nothing to do and we can go home early.
   updateRegion.Sub(updateRegion, aRegionToDraw);
   if (updateRegion.IsEmpty()) {
     return;
   }
 
-  if (mFrontBuffer->Lock(OpenMode::OPEN_READ_ONLY)) {
-    mBuffer->UpdateDestinationFrom(*mFrontBuffer, updateRegion.GetBounds());
-    mFrontBuffer->Unlock();
-  }
+  MOZ_ASSERT(!aPrepareState->mBufferCopy);
+  aPrepareState->mBufferCopy = Some(CapturedBufferState::Copy {
+    mFrontBuffer->ShallowCopy(),
+    mBuffer->ShallowCopy(),
+    updateRegion.GetBounds(),
+  });
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/client/ContentClient.h
+++ b/gfx/layers/client/ContentClient.h
@@ -17,16 +17,17 @@
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/layers/CompositableClient.h"  // for CompositableClient
 #include "mozilla/layers/CompositableForwarder.h"
 #include "mozilla/layers/CompositorTypes.h"  // for TextureInfo, etc
 #include "mozilla/layers/ISurfaceAllocator.h"
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor
 #include "mozilla/layers/LayersTypes.h"  // for TextureDumpMode
 #include "mozilla/layers/TextureClient.h"  // for TextureClient
+#include "mozilla/layers/PaintThread.h"  // for CapturedBufferState
 #include "mozilla/Maybe.h"              // for Maybe
 #include "mozilla/mozalloc.h"           // for operator delete
 #include "ReadbackProcessor.h"          // For ReadbackProcessor::Update
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTArray.h"                   // for nsTArray
@@ -35,16 +36,19 @@ namespace mozilla {
 namespace gfx {
 class DrawTarget;
 } // namespace gfx
 
 namespace layers {
 
 class PaintedLayer;
 class CapturedPaintState;
+class CapturedBufferState;
+
+typedef bool (*PrepDrawTargetForPaintingCallback)(CapturedPaintState*);
 
 /**
  * A compositable client for PaintedLayers. These are different to Image/Canvas
  * clients due to sending a valid region across IPC and because we do a lot more
  * optimisation work, encapsulated in RotatedBuffers.
  *
  * We use content clients for OMTC and non-OMTC, basic rendering so that
  * BasicPaintedLayer has only one interface to deal with. We support single and
@@ -212,17 +216,18 @@ protected:
 
   /**
    * Any actions that should be performed at the last moment before we begin
    * rendering the next frame. I.e., after we calculate what we will draw,
    * but before we rotate the buffer and possibly create new buffers.
    * aRegionToDraw is the region which is guaranteed to be overwritten when
    * drawing the next frame.
    */
-  virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw) {}
+  virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw,
+                             CapturedBufferState* aState) {}
 
   /**
    * Create a new rotated buffer for the specified content type, buffer rect,
    * and buffer flags.
    */
   virtual RefPtr<RotatedBuffer> CreateBuffer(gfxContentType aType,
                                              const gfx::IntRect& aRect,
                                              uint32_t aFlags) = 0;
@@ -357,17 +362,18 @@ public:
   virtual void Clear() override;
 
   virtual void SwapBuffers(const nsIntRegion& aFrontUpdatedRegion) override;
 
   virtual PaintState BeginPaint(PaintedLayer* aLayer, uint32_t aFlags) override;
 
   virtual RefPtr<RotatedBuffer> GetFrontBuffer() const override;
 
-  virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw) override;
+  virtual void FinalizeFrame(const nsIntRegion& aRegionToDraw,
+                             CapturedBufferState* aState) override;
 
   virtual TextureInfo GetTextureInfo() const override
   {
     return TextureInfo(CompositableType::CONTENT_DOUBLE, mTextureFlags);
   }
 
 private:
   RefPtr<RemoteRotatedBuffer> mFrontBuffer;
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -218,16 +218,17 @@ EXPORTS.mozilla.layers += [
     'opengl/CompositorOGL.h',
     'opengl/MacIOSurfaceTextureClientOGL.h',
     'opengl/MacIOSurfaceTextureHostOGL.h',
     'opengl/TextureClientOGL.h',
     'opengl/TextureHostOGL.h',
     'PaintThread.h',
     'PersistentBufferProvider.h',
     'RenderTrace.h',
+    'RotatedBuffer.h',
     'ShareableCanvasRenderer.h',
     'SourceSurfaceSharedData.h',
     'SourceSurfaceVolatileData.h',
     'SyncObject.h',
     'TextureSourceProvider.h',
     'TextureWrapperImage.h',
     'TransactionIdAllocator.h',
     'UpdateImageHelper.h',