Bug 857895 - Run canvas rendering asynchronously on OSX. r=Bas,bholley
☠☠ backed out by 36f972c7df01 ☠ ☠
authorMatt Woodrow <mwoodrow@mozilla.com>
Tue, 09 Apr 2013 16:51:44 +1200
changeset 245642 45998275f42373a83a7014618c5a52a856446a54
parent 245641 af2af798ad8fcdaaefcc85a995f1d73689dcb922
child 245643 3ceaaa0aec9020d7d4687fa75afded8ac1477114
push id13177
push userkwierso@gmail.com
push dateTue, 26 May 2015 23:26:28 +0000
treeherderfx-team@b991cd5a0ad1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersBas, bholley
bugs857895
milestone41.0a1
Bug 857895 - Run canvas rendering asynchronously on OSX. r=Bas,bholley
dom/canvas/CanvasRenderingContext2D.cpp
dom/canvas/CanvasRenderingContext2D.h
dom/media/MediaTaskQueue.cpp
gfx/2d/2D.h
gfx/2d/DrawCommand.h
gfx/2d/DrawTargetCapture.cpp
gfx/2d/DrawTargetCapture.h
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -14,16 +14,18 @@
 
 #include "nsContentUtils.h"
 
 #include "nsIDocument.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "nsSVGEffects.h"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
+#include "nsWidgetsCID.h"
+#include "nsIAppShell.h"
 
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIFrame.h"
 #include "nsError.h"
 
 #include "nsCSSParser.h"
 #include "mozilla/css/StyleRule.h"
 #include "mozilla/css/Declaration.h"
@@ -109,16 +111,17 @@
 #include "SVGContentUtils.h"
 #include "SVGImageContext.h"
 #include "nsIScreenManager.h"
 #include "nsFilterInstance.h"
 #include "nsSVGLength2.h"
 #include "nsDeviceContext.h"
 #include "nsFontMetrics.h"
 #include "Units.h"
+#include "mozilla/Services.h"
 
 #undef free // apparently defined by some windows header, clashing with a free()
             // method in SkTypes.h
 #include "SkiaGLGlue.h"
 #ifdef USE_SKIA
 #include "SurfaceTypes.h"
 #include "GLBlitHelper.h"
 #endif
@@ -174,16 +177,74 @@ public:
       gCanvasAzureMemoryUsed,
       "Memory used by 2D canvases. Each canvas requires "
       "(width * height * 4) bytes.");
   }
 };
 
 NS_IMPL_ISUPPORTS(Canvas2dPixelsReporter, nsIMemoryReporter)
 
+class CanvasShutdownObserver : public nsIObserver
+{
+  virtual ~CanvasShutdownObserver() {}
+
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit CanvasShutdownObserver(CanvasRenderingContext2D* aCanvas)
+    : mCanvas(aCanvas)
+  {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    observerService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
+  }
+
+  void Shutdown() {
+    nsCOMPtr<nsIObserverService> observerService =
+      mozilla::services::GetObserverService();
+    observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+  }
+
+  NS_IMETHOD Observe(nsISupports* aSubject,
+                     const char* aTopic,
+                     const char16_t* aData) override
+  {
+    mCanvas->ShutdownTaskQueue();
+    return NS_OK;
+  }
+
+private:
+  CanvasRenderingContext2D* mCanvas;
+};
+
+NS_IMPL_ISUPPORTS(CanvasShutdownObserver, nsIObserver);
+
+
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+
+void
+CanvasRenderingContext2D::RecordCommand()
+{
+  static uint32_t kBatchSize = 5;
+  if (++mPendingCommands > kBatchSize) {
+    mPendingCommands = 0;
+    FlushDelayedTarget();
+    return;
+  }
+
+  if (mScheduledFlush) {
+    return;
+  }
+
+  mScheduledFlush = true;
+  nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &CanvasRenderingContext2D::StableStateReached);
+  appShell->RunInStableState(r);
+}
+
 class CanvasRadialGradient : public CanvasGradient
 {
 public:
   CanvasRadialGradient(CanvasRenderingContext2D* aContext,
                        const Point &aBeginOrigin, Float aBeginRadius,
                        const Point &aEndOrigin, Float aEndRadius)
     : CanvasGradient(aContext, Type::RADIAL)
     , mCenter1(aBeginOrigin)
@@ -388,16 +449,21 @@ public:
       mFinalTarget, mCtx->CurrentState().filter,
       mgfx::Rect(mPostFilterBounds),
       snapshot, mSourceGraphicRect,
       fillPaint, mFillPaintRect,
       strokePaint, mStrokePaintRect,
       mCtx->CurrentState().filterAdditionalImages,
       mPostFilterBounds.TopLeft() - mOffset,
       DrawOptions(1.0f, mCompositionOp));
+
+    // DrawTargetCapture doesn't properly support filter nodes because they are
+    // mutable. Block until drawing is done to avoid races.
+    mCtx->FlushDelayedTarget();
+    mCtx->FinishDelayedRendering();
   }
 
   DrawTarget* DT()
   {
     return mTarget;
   }
 
 private:
@@ -812,16 +878,19 @@ public:
   static void PreTransactionCallback(void* aData)
   {
     CanvasRenderingContext2DUserData* self =
       static_cast<CanvasRenderingContext2DUserData*>(aData);
     CanvasRenderingContext2D* context = self->mContext;
     if (!context || !context->mTarget)
       return;
 
+    context->FlushDelayedTarget();
+    context->FinishDelayedRendering();
+
     // Since SkiaGL default to store drawing command until flush
     // We will have to flush it before present.
     context->mTarget->Flush();
   }
 
   static void DidTransactionCallback(void* aData)
   {
     CanvasRenderingContext2DUserData* self =
@@ -933,35 +1002,48 @@ CanvasRenderingContext2D::CanvasRenderin
 #ifdef USE_SKIA_GPU
   , mVideoTexture(0)
 #endif
   // these are the default values from the Canvas spec
   , mWidth(0), mHeight(0)
   , mZero(false), mOpaque(false)
   , mResetLayer(true)
   , mIPC(false)
+  , mPendingCommands(0)
+  , mScheduledFlush(false)
   , mDrawObserver(nullptr)
   , mIsEntireFrameInvalid(false)
   , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false)
   , mInvalidateCount(0)
 {
   sNumLivingContexts++;
 
+#ifdef XP_MACOSX
+  // Restrict async rendering to OSX for now until the failures on other
+  // platforms get resolved.
+  mTaskQueue = new MediaTaskQueue(SharedThreadPool::Get(NS_LITERAL_CSTRING("Canvas Rendering"),
+                                                        4));
+  mShutdownObserver = new CanvasShutdownObserver(this);
+#endif
+
   // The default is to use OpenGL mode
   if (!gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas()) {
     mRenderingMode = RenderingMode::SoftwareBackendMode;
   }
 
   if (gfxPlatform::GetPlatform()->HaveChoiceOfHWAndSWCanvas()) {
     mDrawObserver = new CanvasDrawObserver(this);
   }
 }
 
 CanvasRenderingContext2D::~CanvasRenderingContext2D()
 {
+  if (mTaskQueue) {
+    ShutdownTaskQueue();
+  }
   RemoveDrawObserver();
   RemovePostRefreshObserver();
   Reset();
   // Drop references from all CanvasRenderingContext2DUserData to this context
   for (uint32_t i = 0; i < mUserDatas.Length(); ++i) {
     mUserDatas[i]->Forget();
   }
   sNumLivingContexts--;
@@ -974,16 +1056,29 @@ CanvasRenderingContext2D::~CanvasRenderi
     gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->MakeCurrent();
     gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->fDeleteTextures(1, &mVideoTexture);
   }
 #endif
 
   RemoveDemotableContext(this);
 }
 
+void
+CanvasRenderingContext2D::ShutdownTaskQueue()
+{
+  mShutdownObserver->Shutdown();
+  mShutdownObserver = nullptr;
+  FlushDelayedTarget();
+  FinishDelayedRendering();
+  mTaskQueue->BeginShutdown();
+  mTaskQueue = nullptr;
+  mDelayedTarget = nullptr;
+}
+
+
 JSObject*
 CanvasRenderingContext2D::WrapObject(JSContext *cx, JS::Handle<JSObject*> aGivenProto)
 {
   return CanvasRenderingContext2DBinding::Wrap(cx, this, aGivenProto);
 }
 
 bool
 CanvasRenderingContext2D::ParseColor(const nsAString& aString,
@@ -1029,17 +1124,20 @@ CanvasRenderingContext2D::Reset()
   }
 
   // only do this for non-docshell created contexts,
   // since those are the ones that we created a surface for
   if (mTarget && IsTargetValid() && !mDocShell) {
     gCanvasAzureMemoryUsed -= mWidth * mHeight * 4;
   }
 
+  FinishDelayedRendering();
   mTarget = nullptr;
+  mDelayedTarget = nullptr;
+  mFinalTarget = nullptr;
 
   // reset hit regions
   mHitRegionsOptions.ClearAndRetainStorage();
 
   // Since the target changes the backing texture will change, and this will
   // no longer be valid.
   mIsEntireFrameInvalid = false;
   mPredictManyRedrawCalls = false;
@@ -1096,16 +1194,18 @@ CanvasRenderingContext2D::StyleColorToSt
     aStr.AppendFloat(nsStyleUtil::ColorComponentToFloat(NS_GET_A(aColor)));
     aStr.Append(')');
   }
 }
 
 nsresult
 CanvasRenderingContext2D::Redraw()
 {
+  RecordCommand();
+
   if (mIsEntireFrameInvalid) {
     return NS_OK;
   }
 
   mIsEntireFrameInvalid = true;
 
   if (!mCanvasElement) {
     NS_ASSERTION(mDocShell, "Redraw with no canvas element or docshell!");
@@ -1117,16 +1217,17 @@ CanvasRenderingContext2D::Redraw()
   mCanvasElement->InvalidateCanvasContent(nullptr);
 
   return NS_OK;
 }
 
 void
 CanvasRenderingContext2D::Redraw(const mgfx::Rect &r)
 {
+  RecordCommand();
   ++mInvalidateCount;
 
   if (mIsEntireFrameInvalid) {
     return;
   }
 
   if (mPredictManyRedrawCalls ||
     mInvalidateCount > kCanvasMaxInvalidateCount) {
@@ -1139,16 +1240,28 @@ CanvasRenderingContext2D::Redraw(const m
     return;
   }
 
   nsSVGEffects::InvalidateDirectRenderingObservers(mCanvasElement);
 
   mCanvasElement->InvalidateCanvasContent(&r);
 }
 
+TemporaryRef<SourceSurface>
+CanvasRenderingContext2D::GetSurfaceSnapshot(bool* aPremultAlpha /* = nullptr */)
+{
+  EnsureTarget();
+  if (aPremultAlpha) {
+    *aPremultAlpha = true;
+  }
+  FlushDelayedTarget();
+  FinishDelayedRendering();
+  return mFinalTarget->Snapshot();
+}
+
 void
 CanvasRenderingContext2D::DidRefresh()
 {
   if (IsTargetValid() && SkiaGLTex()) {
     SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
     MOZ_ASSERT(glue);
 
     auto gl = glue->GetGLContext();
@@ -1156,16 +1269,17 @@ CanvasRenderingContext2D::DidRefresh()
   }
 }
 
 void
 CanvasRenderingContext2D::RedrawUser(const gfxRect& r)
 {
   if (mIsEntireFrameInvalid) {
     ++mInvalidateCount;
+    RecordCommand();
     return;
   }
 
   mgfx::Rect newr =
     mTarget->GetTransform().TransformBounds(ToRect(r));
   Redraw(newr);
 }
 
@@ -1181,19 +1295,20 @@ bool CanvasRenderingContext2D::SwitchRen
       gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->MakeCurrent();
       gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->fDeleteTextures(1, &mVideoTexture);
     }
 	  mCurrentVideoSize.width = 0;
 	  mCurrentVideoSize.height = 0;
   }
 #endif
 
-  RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
+  RefPtr<SourceSurface> snapshot = GetSurfaceSnapshot();
   RefPtr<DrawTarget> oldTarget = mTarget;
   mTarget = nullptr;
+  mFinalTarget = nullptr;
   mResetLayer = true;
 
   // Recreate target using the new rendering mode
   RenderingMode attemptedMode = EnsureTarget(aRenderingMode);
   if (!IsTargetValid())
     return false;
 
   // We succeeded, so update mRenderingMode to reflect reality
@@ -1355,38 +1470,50 @@ CanvasRenderingContext2D::EnsureTarget(R
           gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas() &&
           CheckSizeForSkiaGL(size)) {
         DemoteOldestContextIfNecessary();
 
 #if USE_SKIA_GPU
         SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
 
         if (glue && glue->GetGrContext() && glue->GetGLContext()) {
-          mTarget = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), size, format);
-          if (mTarget) {
+          mFinalTarget = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), size, format);
+          if (mFinalTarget) {
             AddDemotableContext(this);
           } else {
             printf_stderr("Failed to create a SkiaGL DrawTarget, falling back to software\n");
             mode = RenderingMode::SoftwareBackendMode;
           }
         }
 #endif
-        if (!mTarget) {
-          mTarget = layerManager->CreateDrawTarget(size, format);
+        if (!mFinalTarget) {
+          mFinalTarget = layerManager->CreateDrawTarget(size, format);
         }
       } else {
-        mTarget = layerManager->CreateDrawTarget(size, format);
+        mFinalTarget = layerManager->CreateDrawTarget(size, format);
         mode = RenderingMode::SoftwareBackendMode;
       }
      } else {
-        mTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(size, format);
+        mFinalTarget = gfxPlatform::GetPlatform()->CreateOffscreenCanvasDrawTarget(size, format);
         mode = RenderingMode::SoftwareBackendMode;
      }
   }
 
+  // Restrict async canvas drawing to OSX for now since we get test failures
+  // on other platforms.
+  if (mFinalTarget) {
+#ifdef XP_MACOSX
+    mTarget = mDelayedTarget = mFinalTarget->CreateCaptureDT(size);
+#else
+    mTarget = mFinalTarget;
+#endif
+  }
+
+  mPendingCommands = 0;
+
   if (mTarget) {
     static bool registered = false;
     if (!registered) {
       registered = true;
       RegisterStrongMemoryReporter(new Canvas2dPixelsReporter());
     }
 
     gCanvasAzureMemoryUsed += mWidth * mHeight * 4;
@@ -1410,17 +1537,17 @@ CanvasRenderingContext2D::EnsureTarget(R
     if (mCanvasElement) {
       mCanvasElement->InvalidateCanvas();
     }
     // Calling Redraw() tells our invalidation machinery that the entire
     // canvas is already invalid, which can speed up future drawing.
     Redraw();
   } else {
     EnsureErrorTarget();
-    mTarget = sErrorTarget;
+    mTarget = mFinalTarget = sErrorTarget;
   }
 
   return mode;
 }
 
 #ifdef DEBUG
 int32_t
 CanvasRenderingContext2D::GetWidth() const
@@ -1430,16 +1557,61 @@ CanvasRenderingContext2D::GetWidth() con
 
 int32_t
 CanvasRenderingContext2D::GetHeight() const
 {
   return mHeight;
 }
 #endif
 
+class DrawCaptureTask : public nsRunnable
+{
+public:
+  DrawCaptureTask(DrawTargetCapture *aReplay, DrawTarget* aDest)
+    : mReplay(aReplay)
+    , mDest(aDest)
+  {
+  }
+
+  NS_IMETHOD Run()
+  {
+    mDest->DrawCapturedDT(mReplay, Matrix());
+    return NS_OK;
+  }
+
+private:
+  RefPtr<DrawTargetCapture> mReplay;
+  RefPtr<DrawTarget> mDest;
+};
+
+void
+CanvasRenderingContext2D::FlushDelayedTarget()
+{
+  if (!mDelayedTarget) {
+    return;
+  }
+  mPendingCommands = 0;
+
+  nsCOMPtr<nsIRunnable> task = new DrawCaptureTask(mDelayedTarget, mFinalTarget);
+  mTaskQueue->Dispatch(task.forget());
+
+  mDelayedTarget = mFinalTarget->CreateCaptureDT(IntSize(mWidth, mHeight));
+
+  mDelayedTarget->SetTransform(mTarget->GetTransform());
+  mTarget = mDelayedTarget;
+}
+
+void
+CanvasRenderingContext2D::FinishDelayedRendering()
+{
+  if (mTaskQueue) {
+    mTaskQueue->AwaitIdle();
+  }
+}
+
 NS_IMETHODIMP
 CanvasRenderingContext2D::SetDimensions(int32_t width, int32_t height)
 {
   ClearTarget();
 
   // Zero sized surfaces can cause problems.
   mZero = false;
   if (height == 0) {
@@ -1504,20 +1676,21 @@ CanvasRenderingContext2D::InitializeWith
 {
   RemovePostRefreshObserver();
   mDocShell = shell;
   AddPostRefreshObserverIfNecessary();
 
   SetDimensions(width, height);
   mTarget = gfxPlatform::GetPlatform()->
     CreateDrawTargetForSurface(surface, IntSize(width, height));
+  mFinalTarget = mTarget;
 
   if (!mTarget) {
     EnsureErrorTarget();
-    mTarget = sErrorTarget;
+    mTarget = mFinalTarget = sErrorTarget;
   }
 
   if (mTarget->GetBackendType() == mgfx::BackendType::CAIRO) {
     // Cf comment in EnsureTarget
     mTarget->PushClipRect(mgfx::Rect(Point(0, 0), Size(mWidth, mHeight)));
   }
 
   return NS_OK;
@@ -1579,17 +1752,17 @@ CanvasRenderingContext2D::SetContextOpti
 void
 CanvasRenderingContext2D::GetImageBuffer(uint8_t** aImageBuffer,
                                          int32_t* aFormat)
 {
   *aImageBuffer = nullptr;
   *aFormat = 0;
 
   EnsureTarget();
-  RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
+  RefPtr<SourceSurface> snapshot = GetSurfaceSnapshot();
   if (!snapshot) {
     return;
   }
 
   RefPtr<DataSourceSurface> data = snapshot->GetDataSurface();
   if (!data || data->GetSize() != IntSize(mWidth, mHeight)) {
     return;
   }
@@ -1998,17 +2171,17 @@ CanvasRenderingContext2D::CreatePattern(
   }
 
   EnsureTarget();
 
   // The canvas spec says that createPattern should use the first frame
   // of animated images
   nsLayoutUtils::SurfaceFromElementResult res =
     nsLayoutUtils::SurfaceFromElement(htmlElement,
-      nsLayoutUtils::SFE_WANT_FIRST_FRAME, mTarget);
+      nsLayoutUtils::SFE_WANT_FIRST_FRAME, mFinalTarget);
 
   if (!res.mSourceSurface) {
     error.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   nsRefPtr<CanvasPattern> pat =
     new CanvasPattern(this, res.mSourceSurface, repeatMode, res.mPrincipal,
@@ -4324,17 +4497,17 @@ CanvasRenderingContext2D::DrawImage(cons
     // The cache lookup can miss even if the image is already in the cache
     // if the image is coming from a different element or cached for a
     // different canvas. This covers the case when we miss due to caching
     // for a different canvas, but CanvasImageCache should be fixed if we
     // see misses due to different elements drawing the same image.
     nsLayoutUtils::SurfaceFromElementResult res =
       CachedSurfaceFromElement(element);
     if (!res.mSourceSurface)
-      res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mTarget);
+      res = nsLayoutUtils::SurfaceFromElement(element, sfeFlags, mFinalTarget);
 
     if (!res.mSourceSurface && !res.mDrawInfo.mImgContainer) {
       // The spec says to silently do nothing in the following cases:
       //   - The element is still loading.
       //   - The image is bad, but it's not in the broken state (i.e., we could
       //     decode the headers and get the size).
       if (!res.mIsStillLoading && !res.mHasSize) {
         error.Throw(NS_ERROR_NOT_AVAILABLE);
@@ -4668,17 +4841,22 @@ CanvasRenderingContext2D::DrawWindow(nsG
   }
   nsRefPtr<gfxContext> thebes;
   RefPtr<DrawTarget> drawDT;
   // Rendering directly is faster and can be done if mTarget supports Azure
   // and does not need alpha blending.
   if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget) &&
       GlobalAlpha() == 1.0f)
   {
-    thebes = new gfxContext(mTarget);
+    // Complete any async rendering and use synchronous rendering for DrawWindow
+    // until we're confident it works for all content.
+    FlushDelayedTarget();
+    FinishDelayedRendering();
+
+    thebes = new gfxContext(mFinalTarget);
     thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21,
                                 matrix._22, matrix._31, matrix._32));
   } else {
     drawDT =
       gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(IntSize(ceil(sw), ceil(sh)),
                                                                    SurfaceFormat::B8G8R8A8);
     if (!drawDT) {
       error.Throw(NS_ERROR_FAILURE);
@@ -4925,17 +5103,17 @@ CanvasRenderingContext2D::GetImageDataAr
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
 
   IntRect srcRect(0, 0, mWidth, mHeight);
   IntRect destRect(aX, aY, aWidth, aHeight);
   IntRect srcReadRect = srcRect.Intersect(destRect);
   RefPtr<DataSourceSurface> readback;
   if (!srcReadRect.IsEmpty() && !mZero) {
-    RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
+    RefPtr<SourceSurface> snapshot = GetSurfaceSnapshot();
     if (snapshot) {
       readback = snapshot->GetDataSurface();
     }
     if (!readback || !readback->GetData()) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
@@ -5311,17 +5489,17 @@ CanvasRenderingContext2D::GetCanvasLayer
   // layer manager which must NOT happen during a paint.
   if (!mTarget || !IsTargetValid()) {
     // No DidTransactionCallback will be received, so mark the context clean
     // now so future invalidations will be dispatched.
     MarkContextClean();
     return nullptr;
   }
 
-  mTarget->Flush();
+  FlushDelayedTarget();
 
   if (!mResetLayer && aOldLayer) {
     CanvasRenderingContext2DUserData* userData =
       static_cast<CanvasRenderingContext2DUserData*>(
         aOldLayer->GetUserData(&g2DContextLayerUserData));
 
     CanvasLayer::Data data;
 
@@ -5360,34 +5538,33 @@ CanvasRenderingContext2D::GetCanvasLayer
   // The layer will be destroyed when we tear down the presentation
   // (at the latest), at which time this userData will be destroyed,
   // releasing the reference to the element.
   // The userData will receive DidTransactionCallbacks, which flush the
   // the invalidation state to indicate that the canvas is up to date.
   userData = new CanvasRenderingContext2DUserData(this);
   canvasLayer->SetDidTransactionCallback(
           CanvasRenderingContext2DUserData::DidTransactionCallback, userData);
+  canvasLayer->SetPreTransactionCallback(
+          CanvasRenderingContext2DUserData::PreTransactionCallback, userData);
   canvasLayer->SetUserData(&g2DContextLayerUserData, userData);
 
   CanvasLayer::Data data;
   data.mSize = nsIntSize(mWidth, mHeight);
   data.mHasAlpha = !mOpaque;
 
   GLuint skiaGLTex = SkiaGLTex();
   if (skiaGLTex) {
-    canvasLayer->SetPreTransactionCallback(
-            CanvasRenderingContext2DUserData::PreTransactionCallback, userData);
-
     SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
     MOZ_ASSERT(glue);
 
     data.mGLContext = glue->GetGLContext();
     data.mFrontbufferGLTex = skiaGLTex;
   } else {
-    data.mDrawTarget = mTarget;
+    data.mDrawTarget = mFinalTarget;
   }
 
   canvasLayer->Initialize(data);
   uint32_t flags = mOpaque ? Layer::CONTENT_OPAQUE : 0;
   canvasLayer->SetContentFlags(flags);
   canvasLayer->Updated();
 
   mResetLayer = false;
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -5,16 +5,17 @@
 #ifndef CanvasRenderingContext2D_h
 #define CanvasRenderingContext2D_h
 
 #include "mozilla/Attributes.h"
 #include <vector>
 #include "nsIDOMCanvasRenderingContext2D.h"
 #include "nsICanvasRenderingContextInternal.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/Monitor.h"
 #include "nsColor.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "CanvasUtils.h"
 #include "gfxTextRun.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/CanvasGradient.h"
 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
@@ -22,16 +23,17 @@
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/gfx/2D.h"
 #include "gfx2DGlue.h"
 #include "imgIEncoder.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/EnumeratedArray.h"
 #include "FilterSupport.h"
 #include "nsSVGEffects.h"
+#include "MediaTaskQueue.h"
 
 class nsGlobalWindow;
 class nsXULElement;
 
 namespace mozilla {
 namespace gl {
 class SourceSurface;
 }
@@ -47,16 +49,17 @@ class CanvasPath;
 
 extern const mozilla::gfx::Float SIGMA_MAX;
 
 template<typename T> class Optional;
 
 struct CanvasBidiProcessor;
 class CanvasRenderingContext2DUserData;
 class CanvasDrawObserver;
+class CanvasShutdownObserver;
 
 /**
  ** CanvasRenderingContext2D
  **/
 class CanvasRenderingContext2D final :
   public nsICanvasRenderingContextInternal,
   public nsWrapperCache
 {
@@ -438,24 +441,17 @@ public:
   }
   NS_IMETHOD SetDimensions(int32_t width, int32_t height) override;
   NS_IMETHOD InitializeWithSurface(nsIDocShell *shell, gfxASurface *surface, int32_t width, int32_t height) override;
 
   NS_IMETHOD GetInputStream(const char* aMimeType,
                             const char16_t* aEncoderOptions,
                             nsIInputStream **aStream) override;
 
-  mozilla::TemporaryRef<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr) override
-  {
-    EnsureTarget();
-    if (aPremultAlpha) {
-      *aPremultAlpha = true;
-    }
-    return mTarget->Snapshot();
-  }
+  mozilla::TemporaryRef<mozilla::gfx::SourceSurface> GetSurfaceSnapshot(bool* aPremultAlpha = nullptr) override;
 
   NS_IMETHOD SetIsOpaque(bool isOpaque) override;
   bool GetIsOpaque() override { return mOpaque; }
   NS_IMETHOD Reset() override;
   already_AddRefed<CanvasLayer> GetCanvasLayer(nsDisplayListBuilder* aBuilder,
                                                CanvasLayer *aOldLayer,
                                                LayerManager *aManager) override;
   virtual bool ShouldForceInactiveLayer(LayerManager *aManager) override;
@@ -517,27 +513,43 @@ public:
       mozilla::gfx::Matrix transform = mTarget->GetTransform();
       mDSPathBuilder->BezierTo(transform * aCP1,
                                 transform * aCP2,
                                 transform * aCP3);
     }
   }
 
   friend class CanvasRenderingContext2DUserData;
+  friend class CanvasShutdownObserver;
 
   virtual void GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat) override;
 
 
   // Given a point, return hit region ID if it exists
   nsString GetHitRegion(const mozilla::gfx::Point& aPoint) override;
 
 
   // return true and fills in the bound rect if element has a hit region.
   bool GetHitRegionRect(Element* aElement, nsRect& aRect) override;
 
+  /**
+   * Deferred rendering functions
+   */
+
+  /**
+   * Called when the event loop reaches a stable
+   * state, and trigger us to flush any outstanding
+   * commands to the rendering thread.
+   */
+  void StableStateReached()
+  {
+    mScheduledFlush = false;
+    FlushDelayedTarget();
+  }
+
 protected:
   nsresult GetImageDataArray(JSContext* aCx, int32_t aX, int32_t aY,
                              uint32_t aWidth, uint32_t aHeight,
                              JSObject** aRetval);
 
   nsresult PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h,
                                  dom::Uint8ClampedArray* aArray,
                                  bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY,
@@ -546,16 +558,18 @@ protected:
   /**
    * Internal method to complete initialisation, expects mTarget to have been set
    */
   nsresult Initialize(int32_t width, int32_t height);
 
   nsresult InitializeWithTarget(mozilla::gfx::DrawTarget *surface,
                                 int32_t width, int32_t height);
 
+  void ShutdownTaskQueue();
+
   /**
     * The number of living nsCanvasRenderingContexts.  When this goes down to
     * 0, we free the premultiply and unpremultiply tables, if they exist.
     */
   static uint32_t sNumLivingContexts;
 
   /**
     * Lookup table used to speed up GetImageData().
@@ -709,16 +723,64 @@ protected:
   // If mCanvasElement is not provided, then a docshell is
   nsCOMPtr<nsIDocShell> mDocShell;
 
   // This is created lazily so it is necessary to call EnsureTarget before
   // accessing it. In the event of an error it will be equal to
   // sErrorTarget.
   mozilla::RefPtr<mozilla::gfx::DrawTarget> mTarget;
 
+  /**
+   * Deferred rendering implementation
+   */
+
+  // If we are using deferred rendering, then this is the current
+  // deferred rendering target. It is the same pointer as mTarget.
+  mozilla::RefPtr<mozilla::gfx::DrawTargetCapture> mDelayedTarget;
+
+  // If we are using deferred rendering, then this is the actual destination
+  // buffer.
+  mozilla::RefPtr<mozilla::gfx::DrawTarget> mFinalTarget;
+
+  /**
+   * Add the current DelayedDrawTarget to the rendering queue,
+   * schedule a rendering job if required, and create a new
+   * DelayedDrawTarget.
+   */
+  void FlushDelayedTarget();
+
+  /**
+   * Make sure all commands have been flushed to
+   * the rendering thread, and block until they
+   * are completed.
+   */
+  void FinishDelayedRendering();
+
+  /**
+   * Called when a command is added to the current
+   * delayed draw target.
+   *
+   * Either flushes the current batch of commands to
+   * the rendering thread, or ensures that this happens
+   * the next time the event loop reaches a stable state.
+   */
+  void RecordCommand();
+
+  // The number of commands currently waiting to be sent
+  // to the rendering thread.
+  uint32_t mPendingCommands;
+
+  // True if we have scheduled FlushDelayedTarget to be
+  // called in the next browser stable state.
+  bool mScheduledFlush;
+
+  nsRefPtr<MediaTaskQueue> mTaskQueue;
+
+  nsRefPtr<CanvasShutdownObserver> mShutdownObserver;
+
   uint32_t SkiaGLTex() const;
 
   // This observes our draw calls at the beginning of the canvas
   // lifetime and switches to software or GPU mode depending on
   // what it thinks is best
   CanvasDrawObserver* mDrawObserver;
   void RemoveDrawObserver();
 
--- a/dom/media/MediaTaskQueue.cpp
+++ b/dom/media/MediaTaskQueue.cpp
@@ -22,16 +22,17 @@ MediaTaskQueue::MediaTaskQueue(Temporary
 {
   MOZ_COUNT_CTOR(MediaTaskQueue);
 }
 
 MediaTaskQueue::~MediaTaskQueue()
 {
   MonitorAutoLock mon(mQueueMonitor);
   MOZ_ASSERT(mIsShutdown);
+  MOZ_DIAGNOSTIC_ASSERT(mTasks.empty());
   MOZ_COUNT_DTOR(MediaTaskQueue);
 }
 
 TaskDispatcher&
 MediaTaskQueue::TailDispatcher()
 {
   MOZ_ASSERT(IsCurrentThreadIn());
   MOZ_ASSERT(mTailDispatcher);
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -150,17 +150,17 @@ struct DrawSurfaceOptions {
 
 };
 
 /**
  * This class is used to store gradient stops, it can only be used with a
  * matching DrawTarget. Not adhering to this condition will make a draw call
  * fail.
  */
-class GradientStops : public RefCounted<GradientStops>
+class GradientStops : public external::AtomicRefCounted<GradientStops>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStops)
   virtual ~GradientStops() {}
 
   virtual BackendType GetBackendType() const = 0;
   virtual bool IsValid() const { return true; }
 
@@ -313,17 +313,17 @@ public:
 class StoredPattern;
 class DrawTargetCaptureImpl;
 
 /**
  * This is the base class for source surfaces. These objects are surfaces
  * which may be used as a source in a SurfacePattern or a DrawSurface call.
  * They cannot be drawn to directly.
  */
-class SourceSurface : public RefCounted<SourceSurface>
+class SourceSurface : public external::AtomicRefCounted<SourceSurface>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurface)
   virtual ~SourceSurface() {}
 
   virtual SurfaceType GetType() const = 0;
   virtual IntSize GetSize() const = 0;
   virtual SurfaceFormat GetFormat() const = 0;
@@ -471,17 +471,17 @@ public:
 };
 
 class PathBuilder;
 class FlattenedPath;
 
 /** The path class is used to create (sets of) figures of any shape that can be
  * filled or stroked to a DrawTarget
  */
-class Path : public RefCounted<Path>
+class Path : public external::AtomicRefCounted<Path>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(Path)
   virtual ~Path();
   
   virtual BackendType GetBackendType() const = 0;
 
   /** This returns a PathBuilder object that contains a copy of the contents of
@@ -572,17 +572,17 @@ struct GlyphBuffer
   const Glyph *mGlyphs; //!< A pointer to a buffer of glyphs. Managed by the caller.
   uint32_t mNumGlyphs;  //!< Number of glyphs mGlyphs points to.
 };
 
 /** This class is an abstraction of a backend/platform specific font object
  * at a particular size. It is passed into text drawing calls to describe
  * the font used for the drawing call.
  */
-class ScaledFont : public RefCounted<ScaledFont>
+class ScaledFont : public external::AtomicRefCounted<ScaledFont>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(ScaledFont)
   virtual ~ScaledFont() {}
 
   typedef void (*FontFileDataOutput)(const uint8_t *aData, uint32_t aLength, uint32_t aIndex, Float aGlyphSize, void *aBaton);
 
   virtual FontType GetType() const = 0;
@@ -617,17 +617,17 @@ protected:
 };
 
 /** This class is designed to allow passing additional glyph rendering
  * parameters to the glyph drawing functions. This is an empty wrapper class
  * merely used to allow holding on to and passing around platform specific
  * parameters. This is because different platforms have unique rendering
  * parameters.
  */
-class GlyphRenderingOptions : public RefCounted<GlyphRenderingOptions>
+class GlyphRenderingOptions : public external::AtomicRefCounted<GlyphRenderingOptions>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GlyphRenderingOptions)
   virtual ~GlyphRenderingOptions() {}
 
   virtual FontType GetType() const = 0;
 
 protected:
@@ -636,17 +636,17 @@ protected:
 
 class DrawTargetCapture;
 
 /** This is the main class used for all the drawing. It is created through the
  * factory and accepts drawing commands. The results of drawing to a target
  * may be used either through a Snapshot or by flushing the target and directly
  * accessing the backing store a DrawTarget was created with.
  */
-class DrawTarget : public RefCounted<DrawTarget>
+class DrawTarget : public external::AtomicRefCounted<DrawTarget>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(DrawTarget)
   DrawTarget() : mTransformDirty(false), mPermitSubpixelAA(false) {}
   virtual ~DrawTarget() {}
 
   virtual DrawTargetType GetType() const = 0;
 
--- a/gfx/2d/DrawCommand.h
+++ b/gfx/2d/DrawCommand.h
@@ -143,17 +143,17 @@ private:
   DrawOptions mOptions;
 };
 
 class DrawFilterCommand : public DrawingCommand
 {
 public:
   DrawFilterCommand(FilterNode* aFilter, const Rect& aSourceRect,
                     const Point& aDestPoint, const DrawOptions& aOptions)
-    : DrawingCommand(CommandType::DRAWSURFACE)
+    : DrawingCommand(CommandType::DRAWFILTER)
     , mFilter(aFilter), mSourceRect(aSourceRect)
     , mDestPoint(aDestPoint), mOptions(aOptions)
   {
   }
 
   virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
   {
     aDT->DrawFilter(mFilter, mSourceRect, mDestPoint, mOptions);
@@ -161,16 +161,46 @@ public:
 
 private:
   RefPtr<FilterNode> mFilter;
   Rect mSourceRect;
   Point mDestPoint;
   DrawOptions mOptions;
 };
 
+class DrawSurfaceWithShadowCommand : public DrawingCommand
+{
+public:
+  DrawSurfaceWithShadowCommand(SourceSurface* aSurface, const Point& aDest,
+                               const Color& aColor, const Point& aOffset,
+                               Float aSigma, CompositionOp aOperator)
+    : DrawingCommand(CommandType::DRAWSURFACEWITHSHADOW)
+    , mSurface(aSurface)
+    , mDest(aDest)
+    , mColor(aColor)
+    , mOffset(aOffset)
+    , mSigma(aSigma)
+    , mOperator(aOperator)
+  {
+  }
+
+  virtual void ExecuteOnDT(DrawTarget* aDT, const Matrix&)
+  {
+    aDT->DrawSurfaceWithShadow(mSurface, mDest, mColor, mOffset, mSigma, mOperator);
+  }
+
+private:
+  RefPtr<SourceSurface> mSurface;
+  Point mDest;
+  Color mColor;
+  Point mOffset;
+  Float mSigma;
+  CompositionOp mOperator;
+};
+
 class ClearRectCommand : public DrawingCommand
 {
 public:
   explicit ClearRectCommand(const Rect& aRect)
     : DrawingCommand(CommandType::CLEARRECT)
     , mRect(aRect)
   {
   }
--- a/gfx/2d/DrawTargetCapture.cpp
+++ b/gfx/2d/DrawTargetCapture.cpp
@@ -25,16 +25,17 @@ DrawTargetCaptureImpl::~DrawTargetCaptur
 bool
 DrawTargetCaptureImpl::Init(const IntSize& aSize, DrawTarget* aRefDT)
 {
   if (!aRefDT) {
     return false;
   }
 
   mRefDT = aRefDT;
+  mFormat = mRefDT->GetFormat();
 
   mSize = aSize;
   return true;
 }
 
 TemporaryRef<SourceSurface>
 DrawTargetCaptureImpl::Snapshot()
 {
@@ -65,16 +66,28 @@ DrawTargetCaptureImpl::DrawFilter(Filter
                                   const DrawOptions &aOptions)
 {
   // @todo XXX - this won't work properly long term yet due to filternodes not
   // being immutable.
   AppendCommand(DrawFilterCommand)(aNode, aSourceRect, aDestPoint, aOptions);
 }
 
 void
+DrawTargetCaptureImpl::DrawSurfaceWithShadow(SourceSurface *aSurface,
+                                             const Point &aDest,
+                                             const Color &aColor,
+                                             const Point &aOffset,
+                                             Float aSigma,
+                                             CompositionOp aOperator)
+{
+  aSurface->GuaranteePersistance();
+  AppendCommand(DrawSurfaceWithShadowCommand)(aSurface, aDest, aColor, aOffset, aSigma, aOperator);
+}
+
+void
 DrawTargetCaptureImpl::ClearRect(const Rect &aRect)
 {
   AppendCommand(ClearRectCommand)(aRect);
 }
 
 void
 DrawTargetCaptureImpl::MaskSurface(const Pattern &aSource,
                                    SourceSurface *aMask,
@@ -173,16 +186,17 @@ DrawTargetCaptureImpl::PopClip()
 {
   AppendCommand(PopClipCommand)();
 }
 
 void
 DrawTargetCaptureImpl::SetTransform(const Matrix& aTransform)
 {
   AppendCommand(SetTransformCommand)(aTransform);
+  mTransform = aTransform;
 }
 
 void
 DrawTargetCaptureImpl::ReplayToDrawTarget(DrawTarget* aDT, const Matrix& aTransform)
 {
   uint8_t* start = &mDrawCommandStorage.front();
 
   uint8_t* current = start;
--- a/gfx/2d/DrawTargetCapture.h
+++ b/gfx/2d/DrawTargetCapture.h
@@ -40,17 +40,17 @@ public:
                           const Rect &aSourceRect,
                           const Point &aDestPoint,
                           const DrawOptions &aOptions = DrawOptions());
   virtual void DrawSurfaceWithShadow(SourceSurface *aSurface,
                                      const Point &aDest,
                                      const Color &aColor,
                                      const Point &aOffset,
                                      Float aSigma,
-                                     CompositionOp aOperator) { /* Not implemented */ }
+                                     CompositionOp aOperator);
 
   virtual void ClearRect(const Rect &aRect);
   virtual void MaskSurface(const Pattern &aSource,
                            SourceSurface *aMask,
                            Point aOffset,
                            const DrawOptions &aOptions = DrawOptions());
 
   virtual void CopySurface(SourceSurface *aSurface,