Bug 1285561 - Refactor surface blitting on X11. r=jrmuizel
authorAndrew Comminos <andrew@comminos.com>
Mon, 11 Jul 2016 16:08:43 -0400
changeset 305047 586815d86b50e44a1cc48c734c4da6efcd8ae8ff
parent 305046 f3f17d5b2389f83f4f23d817ea5a63147d3b8267
child 305048 2f9e69c982f1e67887a1834b36ff0af4ababb3af
child 305049 85bc42bbcf5c7e128f2b00b5a47ce5f005774981
push id30450
push usercbook@mozilla.com
push dateFri, 15 Jul 2016 14:15:45 +0000
treeherdermozilla-central@2f9e69c982f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1285561
milestone50.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 1285561 - Refactor surface blitting on X11. r=jrmuizel MozReview-Commit-ID: LHbVK8SYGSJ
widget/WindowSurface.h
widget/WindowSurfaceX11SHM.cpp
widget/WindowSurfaceX11SHM.h
widget/gtk/WindowSurfaceX11.cpp
widget/gtk/WindowSurfaceX11.h
widget/gtk/WindowSurfaceX11Image.cpp
widget/gtk/WindowSurfaceX11Image.h
widget/gtk/WindowSurfaceXRender.cpp
widget/gtk/WindowSurfaceXRender.h
widget/gtk/moz.build
widget/gtk/nsDeviceContextSpecG.cpp
widget/gtk/nsWindow.cpp
widget/gtk/nsWindow.h
widget/moz.build
new file mode 100644
--- /dev/null
+++ b/widget/WindowSurface.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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_WIDGET_WINDOW_SURFACE_H
+#define _MOZILLA_WIDGET_WINDOW_SURFACE_H
+
+#include "mozilla/gfx/2D.h"
+#include "Units.h"
+
+namespace mozilla {
+namespace widget {
+
+// A class for drawing to double-buffered windows.
+class WindowSurface {
+public:
+  virtual ~WindowSurface() {}
+
+  // Locks a region of the window for drawing, returning a draw target
+  // capturing the bounds of the provided region.
+  // Implementations must permit invocation from any thread.
+  virtual already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) = 0;
+
+  // Swaps the provided invalid region from the back buffer to the window.
+  // Implementations must permit invocation from any thread.
+  virtual void Commit(const LayoutDeviceIntRegion& aInvalidRegion) = 0;
+};
+
+}  // namespace widget
+}  // namespace mozilla
+
+#endif // _MOZILLA_WIDGET_WINDOW_SURFACE_H
new file mode 100644
--- /dev/null
+++ b/widget/WindowSurfaceX11SHM.cpp
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 "WindowSurfaceX11SHM.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowSurfaceX11SHM::WindowSurfaceX11SHM(Display* aDisplay, Drawable aWindow,
+                                         Visual* aVisual, unsigned int aDepth)
+{
+  mFrontImage = new nsShmImage(aDisplay, aWindow, aVisual, aDepth);
+  mBackImage = new nsShmImage(aDisplay, aWindow, aVisual, aDepth);
+}
+
+already_AddRefed<gfx::DrawTarget>
+WindowSurfaceX11SHM::Lock(const LayoutDeviceIntRegion& aRegion)
+{
+  mBackImage.swap(mFrontImage);
+  return mBackImage->CreateDrawTarget(aRegion);
+}
+
+void
+WindowSurfaceX11SHM::Commit(const LayoutDeviceIntRegion& aInvalidRegion)
+{
+  mBackImage->Put(aInvalidRegion);
+}
+
+} // namespace widget
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/WindowSurfaceX11SHM.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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_WIDGET_WINDOW_SURFACE_X11_SHM_H
+#define _MOZILLA_WIDGET_WINDOW_SURFACE_X11_SHM_H
+
+#ifdef MOZ_X11
+
+#include "mozilla/widget/WindowSurface.h"
+#include "nsShmImage.h"
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceX11SHM : public WindowSurface {
+public:
+  WindowSurfaceX11SHM(Display* aDisplay, Drawable aWindow, Visual* aVisual,
+                      unsigned int aDepth);
+
+  already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) override;
+  void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+private:
+  RefPtr<nsShmImage> mFrontImage;
+  RefPtr<nsShmImage> mBackImage;
+};
+
+}  // namespace widget
+}  // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_WINDOW_SURFACE_X11_SHM_H
new file mode 100644
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 "WindowSurfaceX11.h"
+#include "gfxPlatform.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowSurfaceX11::WindowSurfaceX11(Display* aDisplay,
+                                   Window aWindow,
+                                   Visual* aVisual,
+                                   unsigned int aDepth)
+  : mDisplay(aDisplay)
+  , mWindow(aWindow)
+  , mVisual(aVisual)
+  , mDepth(aDepth)
+  , mFormat(GetVisualFormat(aVisual, aDepth))
+  , mGC(None)
+{
+  MOZ_ASSERT(mFormat != gfx::SurfaceFormat::UNKNOWN,
+             "Could not find SurfaceFormat for visual!");
+}
+
+WindowSurfaceX11::~WindowSurfaceX11()
+{
+  if (mGC != None)
+    XFreeGC(mDisplay, mGC);
+}
+
+void
+WindowSurfaceX11::Commit(const LayoutDeviceIntRegion& aInvalidRegion)
+{
+  AutoTArray<XRectangle, 32> xrects;
+  xrects.SetCapacity(aInvalidRegion.GetNumRects());
+
+  for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+    const mozilla::LayoutDeviceIntRect &r = iter.Get();
+    XRectangle xrect = { (short)r.x, (short)r.y, (unsigned short)r.width, (unsigned short)r.height };
+    xrects.AppendElement(xrect);
+  }
+
+  if (!mGC) {
+    mGC = XCreateGC(mDisplay, mWindow, 0, nullptr);
+    if (!mGC) {
+      NS_WARNING("Couldn't create X11 graphics context for window!");
+      return;
+    }
+  }
+
+  XSetClipRectangles(mDisplay, mGC, 0, 0, xrects.Elements(), xrects.Length(), YXBanded);
+  CommitToDrawable(mWindow, mGC, aInvalidRegion);
+}
+
+/* static */
+gfx::SurfaceFormat
+WindowSurfaceX11::GetVisualFormat(const Visual* aVisual, unsigned int aDepth)
+{
+  switch (aDepth) {
+  case 32:
+    if (aVisual->red_mask == 0xff0000 &&
+        aVisual->green_mask == 0xff00 &&
+        aVisual->blue_mask == 0xff) {
+      return gfx::SurfaceFormat::B8G8R8A8;
+    }
+    break;
+  case 24:
+    // Only support the BGRX layout, and report it as BGRA to the compositor.
+    // The alpha channel will be discarded when we put the image.
+    // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so
+    // just report it as BGRX directly in that case.
+    if (aVisual->red_mask == 0xff0000 &&
+        aVisual->green_mask == 0xff00 &&
+        aVisual->blue_mask == 0xff) {
+      gfx::BackendType backend = gfxPlatform::GetPlatform()->GetDefaultContentBackend();
+      return backend == gfx::BackendType::CAIRO ? gfx::SurfaceFormat::B8G8R8X8
+                                                : gfx::SurfaceFormat::B8G8R8A8;
+    }
+    break;
+  case 16:
+    if (aVisual->red_mask == 0xf800 &&
+        aVisual->green_mask == 0x07e0 &&
+        aVisual->blue_mask == 0x1f) {
+      return gfx::SurfaceFormat::R5G6B5_UINT16;
+    }
+    break;
+  }
+
+  return gfx::SurfaceFormat::UNKNOWN;
+}
+
+}  // namespace widget
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.h
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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_WIDGET_GTK_WINDOW_SURFACE_X11_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H
+
+#ifdef MOZ_X11
+
+#include "mozilla/widget/WindowSurface.h"
+#include "mozilla/gfx/Types.h"
+
+#include <X11/Xlib.h>
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceX11 : public WindowSurface {
+public:
+  WindowSurfaceX11(Display* aDisplay, Window aWindow, Visual* aVisual,
+                   unsigned int aDepth);
+  ~WindowSurfaceX11();
+
+  void Commit(const LayoutDeviceIntRegion& aInvalidRegion) final;
+
+  virtual already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) override = 0;
+
+  // Draws the buffered image onto aDest using the given GC.
+  // The GC provided has been clipped to aInvalidRegion.
+  virtual void CommitToDrawable(Drawable aDest, GC aGC,
+                                const LayoutDeviceIntRegion& aInvalidRegion) = 0;
+
+protected:
+  static gfx::SurfaceFormat GetVisualFormat(const Visual* aVisual, unsigned int aDepth);
+
+  Display* const mDisplay;
+  const Window mWindow;
+  Visual* const mVisual;
+  const unsigned int mDepth;
+  const gfx::SurfaceFormat mFormat;
+
+  GC mGC;
+};
+
+}  // namespace widget
+}  // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H
new file mode 100644
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 "WindowSurfaceX11Image.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Tools.h"
+#include "gfxPlatform.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowSurfaceX11Image::WindowSurfaceX11Image(Display* aDisplay,
+                                             Window aWindow,
+                                             Visual* aVisual,
+                                             unsigned int aDepth)
+  : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth)
+  , mImage(nullptr)
+{
+}
+
+WindowSurfaceX11Image::~WindowSurfaceX11Image()
+{
+  if (mImage)
+    XDestroyImage(mImage);
+}
+
+already_AddRefed<gfx::DrawTarget>
+WindowSurfaceX11Image::Lock(const LayoutDeviceIntRegion& aRegion)
+{
+  gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+  gfx::IntSize size(bounds.XMost(), bounds.YMost());
+
+  if (!mImage || mImage->width < size.width || mImage->height < size.height) {
+    if (mImage)
+      XDestroyImage(mImage);
+
+    int stride = gfx::GetAlignedStride<16>(gfx::BytesPerPixel(mFormat) * size.width);
+    char* data = static_cast<char*>(malloc(stride * size.height));
+    if (!data)
+      return nullptr;
+
+    mImage = XCreateImage(mDisplay, mVisual, mDepth, ZPixmap, 0,
+                          data, size.width, size.height,
+                          8 * gfx::BytesPerPixel(mFormat), stride);
+  }
+
+  if (!mImage)
+    return nullptr;
+
+  unsigned char* data = (unsigned char*) mImage->data;
+  return gfxPlatform::GetPlatform()->CreateDrawTargetForData(data,
+                                                             size,
+                                                             mImage->bytes_per_line,
+                                                             mFormat);
+}
+
+void
+WindowSurfaceX11Image::CommitToDrawable(Drawable aDest, GC aGC,
+                                        const LayoutDeviceIntRegion& aInvalidRegion)
+{
+  MOZ_ASSERT(mImage, "Attempted to commit invalid surface!");
+
+  gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect();
+  gfx::IntSize size(bounds.XMost(), bounds.YMost());
+  XPutImage(mDisplay, aDest, aGC, mImage, bounds.x, bounds.y,
+            bounds.x, bounds.y, size.width, size.height);
+}
+
+}  // namespace widget
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
+
+#ifdef MOZ_X11
+
+#include "WindowSurfaceX11.h"
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceX11Image : public WindowSurfaceX11 {
+public:
+  WindowSurfaceX11Image(Display* aDisplay, Window aWindow, Visual* aVisual,
+                        unsigned int aDepth);
+  ~WindowSurfaceX11Image();
+
+  already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) override;
+  void CommitToDrawable(Drawable aDest, GC aGC,
+                        const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+private:
+  XImage* mImage;
+};
+
+}  // namespace widget
+}  // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
new file mode 100644
--- /dev/null
+++ b/widget/gtk/WindowSurfaceXRender.cpp
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 "WindowSurfaceXRender.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "gfxPlatform.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowSurfaceXRender::WindowSurfaceXRender(Display* aDisplay,
+                                           Window aWindow,
+                                           Visual* aVisual,
+                                           unsigned int aDepth)
+  : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth)
+  , mXlibSurface(nullptr)
+{
+}
+
+already_AddRefed<gfx::DrawTarget>
+WindowSurfaceXRender::Lock(const LayoutDeviceIntRegion& aRegion)
+{
+  gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+  gfx::IntSize size(bounds.XMost(), bounds.YMost());
+  if (!mXlibSurface || mXlibSurface->CairoStatus() != 0 ||
+      mXlibSurface->GetSize().width < size.width ||
+      mXlibSurface->GetSize().height < size.height)
+  {
+    mXlibSurface = gfxXlibSurface::Create(DefaultScreenOfDisplay(mDisplay),
+                                          mVisual,
+                                          size,
+                                          mWindow);
+  }
+
+  if (mXlibSurface && mXlibSurface->CairoStatus() == 0) {
+    return gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(mXlibSurface.get(), size);
+  }
+  return nullptr;
+}
+
+void
+WindowSurfaceXRender::CommitToDrawable(Drawable aDest, GC aGC,
+                                       const LayoutDeviceIntRegion& aInvalidRegion)
+{
+  MOZ_ASSERT(mXlibSurface && mXlibSurface->CairoStatus() == 0,
+             "Attempted to commit invalid surface!");
+  gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect();
+  gfx::IntSize size(bounds.XMost(), bounds.YMost());
+  XCopyArea(mDisplay, mXlibSurface->XDrawable(), aDest, aGC, bounds.x, bounds.y,
+            size.width, size.height, bounds.x, bounds.y);
+}
+
+}  // namespace widget
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/widget/gtk/WindowSurfaceXRender.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H
+
+#ifdef MOZ_X11
+
+#include "WindowSurfaceX11.h"
+#include "gfxXlibSurface.h"
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceXRender : public WindowSurfaceX11 {
+public:
+  WindowSurfaceXRender(Display* aDisplay, Window aWindow, Visual* aVisual,
+                       unsigned int aDepth);
+
+  already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) override;
+  void CommitToDrawable(Drawable aDest, GC aGC,
+                        const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+private:
+  RefPtr<gfxXlibSurface> mXlibSurface;
+};
+
+}  // namespace widget
+}  // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H
--- a/widget/gtk/moz.build
+++ b/widget/gtk/moz.build
@@ -58,16 +58,19 @@ if CONFIG['NS_PRINTING']:
         'nsPrintSettingsGTK.cpp',
         'nsPSPrinters.cpp',
     ]
 
 if CONFIG['MOZ_X11']:
     UNIFIED_SOURCES += [
         'nsClipboard.cpp',
         'nsDragService.cpp',
+        'WindowSurfaceX11.cpp',
+        'WindowSurfaceX11Image.cpp',
+        'WindowSurfaceXRender.cpp',
     ]
 
 if CONFIG['ACCESSIBILITY']:
     UNIFIED_SOURCES += [
         'maiRedundantObjectFactory.c',
     ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk2':
--- a/widget/gtk/nsDeviceContextSpecG.cpp
+++ b/widget/gtk/nsDeviceContextSpecG.cpp
@@ -20,16 +20,17 @@
 #include "nsPSPrinters.h"
 #include "nsPaperPS.h"  /* Paper size list */
 
 #include "nsPrintSettingsGTK.h"
 
 #include "nsIFileStreams.h"
 #include "nsIFile.h"
 #include "nsTArray.h"
+#include "nsThreadUtils.h"
 
 #include "mozilla/Preferences.h"
 
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 
 using namespace mozilla;
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -112,18 +112,21 @@ using namespace mozilla::widget;
 #include "Layers.h"
 #include "GLContextProvider.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/HelpersCairo.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 
 #ifdef MOZ_X11
 #include "gfxXlibSurface.h"
-#endif
-  
+#include "WindowSurfaceX11Image.h"
+#include "WindowSurfaceX11SHM.h"
+#include "WindowSurfaceXRender.h"
+#endif // MOZ_X11
+
 #include "nsShmImage.h"
 
 #include "nsIDOMWheelEvent.h"
 
 #include "NativeKeyBindings.h"
 
 #include <dlfcn.h>
 
@@ -471,18 +474,16 @@ nsWindow::nsWindow()
     mTransparencyBitmap = nullptr;
 
     mTransparencyBitmapWidth  = 0;
     mTransparencyBitmapHeight = 0;
 
 #if GTK_CHECK_VERSION(3,4,0)
     mLastScrollEventTime = GDK_CURRENT_TIME;
 #endif
-
-    mFallbackSurface = nullptr;
     mPendingConfigures = 0;
 }
 
 nsWindow::~nsWindow()
 {
     LOG(("nsWindow::~nsWindow() [%p]\n", (void *)this));
 
     delete[] mTransparencyBitmap;
@@ -795,21 +796,16 @@ nsWindow::Destroy(void)
     }
 
 #ifdef ACCESSIBILITY
      if (mRootAccessible) {
          mRootAccessible = nullptr;
      }
 #endif
 
-    if (mFallbackSurface) {
-        cairo_surface_destroy(mFallbackSurface);
-        mFallbackSurface = nullptr;
-    }
-
     // Save until last because OnDestroy() may cause us to be deleted.
     OnDestroy();
 
     return NS_OK;
 }
 
 nsIWidget *
 nsWindow::GetParent(void)
@@ -6560,143 +6556,46 @@ nsWindow::GetDrawTargetForGdkDrawable(Gd
         return nullptr;
     }
 
     return dt.forget();
 }
 #endif
 
 already_AddRefed<DrawTarget>
-nsWindow::GetDrawTarget(const LayoutDeviceIntRegion& aRegion, BufferMode* aBufferMode)
-{
-  if (!mGdkWindow || aRegion.IsEmpty()) {
+nsWindow::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode)
+{
+  if (aInvalidRegion.IsEmpty())
     return nullptr;
+
+  if (!mWindowSurface) {
+    mWindowSurface = CreateWindowSurface();
+    if (!mWindowSurface)
+      return nullptr;
   }
 
-  RefPtr<DrawTarget> dt;
-
+  *aBufferMode = BufferMode::BUFFER_NONE;
+  RefPtr<DrawTarget> dt = nullptr;
+  if (!(dt = mWindowSurface->Lock(aInvalidRegion))) {
 #ifdef MOZ_X11
-  bool useXRender = false;
-#ifdef MOZ_WIDGET_GTK
-  useXRender = gfxPlatformGtk::GetPlatform()->UseXRender();
-#endif
-
-  if (useXRender) {
-    LayoutDeviceIntRect bounds = aRegion.GetBounds();
-    LayoutDeviceIntSize size(bounds.XMost(), bounds.YMost());
-    RefPtr<gfxXlibSurface> surf = new gfxXlibSurface(mXDisplay, mXWindow, mXVisual, size.ToUnknownSize());
-    if (!surf->CairoStatus()) {
-      dt = gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surf.get(), surf->GetSize());
-      *aBufferMode = BufferMode::BUFFERED;
-    }
-  }
-
-#  ifdef MOZ_HAVE_SHMIMAGE
-  if (!dt && mIsX11Display && nsShmImage::UseShm()) {
-    mBackShmImage.swap(mFrontShmImage);
-    if (!mBackShmImage) {
-      mBackShmImage = new nsShmImage(mXDisplay, mXWindow, mXVisual, mXDepth);
-    }
-    dt = mBackShmImage->CreateDrawTarget(aRegion);
-    *aBufferMode = BufferMode::BUFFER_NONE;
-    if (!dt) {
-      mBackShmImage = nullptr;
-    }
-  }
-#  endif  // MOZ_HAVE_SHMIMAGE
+    if (mIsX11Display) {
+      gfxWarningOnce() << "Failed to lock WindowSurface, falling back to XPutImage backend.";
+      mWindowSurface = MakeUnique<WindowSurfaceX11Image>(mXDisplay, mXWindow, mXVisual, mXDepth);
+    }
 #endif // MOZ_X11
-
-  // If MIT-SHM and XRender are unavailable, buffer to an image surface.
-  if (!dt) {
-    IntRect bounds = aRegion.GetBounds().ToUnknownRect();
-    IntSize size(bounds.XMost(), bounds.YMost());
-
-    // Recreate the fallback surface if there is unsufficient space to render.
-    if (!mFallbackSurface ||
-        cairo_image_surface_get_width(mFallbackSurface) < size.width ||
-        cairo_image_surface_get_height(mFallbackSurface) < size.height)
-    {
-      if (mFallbackSurface)
-        cairo_surface_destroy(mFallbackSurface);
-
-      GdkScreen* screen = gdk_screen_get_default();
-      bool argb = gdk_window_get_visual(mGdkWindow) ==
-                  gdk_screen_get_rgba_visual(screen);
-      cairo_format_t cairo_format = argb ? CAIRO_FORMAT_ARGB32
-                                         : CAIRO_FORMAT_RGB24;
-      mFallbackSurface = cairo_image_surface_create(cairo_format,
-                                                    bounds.XMost(),
-                                                    bounds.YMost());
-
-      // Set the appropriate device scale so that our surface can be used
-      // as a source without transforming to the GDK window's scale.
-      static auto sCairoSurfaceSetDeviceScale =
-          (void (*)(cairo_surface_t*, double, double))
-          dlsym(RTLD_DEFAULT, "cairo_surface_set_device_scale");
-      if (sCairoSurfaceSetDeviceScale) {
-        gint scale = GdkScaleFactor();
-        sCairoSurfaceSetDeviceScale(mFallbackSurface, scale, scale);
-      }
-
-      cairo_surface_flush(mFallbackSurface);
-    }
-
-    SurfaceFormat format = CairoFormatToGfxFormat(
-            cairo_image_surface_get_format(mFallbackSurface));
-    dt = gfxPlatform::GetPlatform()->CreateDrawTargetForData(
-            cairo_image_surface_get_data(mFallbackSurface)
-            + bounds.x * BytesPerPixel(format)
-            + bounds.y * cairo_image_surface_get_stride(mFallbackSurface),
-            bounds.Size(),
-            cairo_image_surface_get_stride(mFallbackSurface),
-            format);
-    *aBufferMode = BufferMode::BUFFER_NONE;
   }
-
   return dt.forget();
 }
 
-already_AddRefed<DrawTarget>
-nsWindow::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode)
-{
-  return GetDrawTarget(aInvalidRegion, aBufferMode);
-}
-
 void
 nsWindow::EndRemoteDrawingInRegion(DrawTarget* aDrawTarget,
                                    LayoutDeviceIntRegion& aInvalidRegion)
 {
-#ifdef MOZ_X11
-#  ifdef MOZ_HAVE_SHMIMAGE
-  if (!mGdkWindow) {
-    return;
-  }
-
-  if (mBackShmImage) {
-    mBackShmImage->Put(aInvalidRegion);
-  }
-#  endif // MOZ_HAVE_SHMIMAGE
-#endif // MOZ_X11
-
-  if (mFallbackSurface) {
-    cairo_t* cr = gdk_cairo_create(mGdkWindow);
-    cairo_surface_mark_dirty(mFallbackSurface);
-    cairo_set_source_surface(cr, mFallbackSurface, 0, 0);
-
-    for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
-      // Transform our path into the window's coordinate system.
-      const IntRectTyped<LayoutDevicePixel> & r = iter.Get();
-      cairo_rectangle(cr, DevicePixelsToGdkCoordRoundDown(r.x),
-                          DevicePixelsToGdkCoordRoundDown(r.y),
-                          DevicePixelsToGdkCoordRoundUp(r.width),
-                          DevicePixelsToGdkCoordRoundUp(r.height));
-    }
-    cairo_fill(cr);
-    cairo_destroy(cr);
-  }
+  if (mWindowSurface)
+    mWindowSurface->Commit(aInvalidRegion);
 }
 
 // Code shared begin BeginMoveDrag and BeginResizeDrag
 bool
 nsWindow::GetDragInfo(WidgetMouseEvent* aMouseEvent,
                       GdkWindow** aWindow, gint* aButton,
                       gint* aRootX, gint* aRootY)
 {
@@ -7122,8 +7021,44 @@ nsWindow::SynthesizeNativeTouchPoint(uin
 }
 #endif
 
 int32_t
 nsWindow::RoundsWidgetCoordinatesTo()
 {
     return GdkScaleFactor();
 }
+
+UniquePtr<WindowSurface>
+nsWindow::CreateWindowSurface()
+{
+  if (!mGdkWindow)
+    return nullptr;
+
+  // TODO: Add path for Wayland. We can't use gdk_cairo_create as it's not
+  //       threadsafe.
+  if (!mIsX11Display)
+    return nullptr;
+
+#ifdef MOZ_X11
+  // Blit to the window with the following priority:
+  // 1. XRender (iff XRender is enabled)
+  // 2. MIT-SHM
+  // 3. XPutImage
+
+#ifdef MOZ_WIDGET_GTK
+  if (gfxPlatformGtk::GetPlatform()->UseXRender()) {
+    LOGDRAW(("Drawing to nsWindow %p using XRender\n", (void*)this));
+    return MakeUnique<WindowSurfaceXRender>(mXDisplay, mXWindow, mXVisual, mXDepth);
+  }
+#endif // MOZ_WIDGET_GTK
+
+#ifdef MOZ_HAVE_SHMIMAGE
+  if (nsShmImage::UseShm()) {
+    LOGDRAW(("Drawing to nsWindow %p using MIT-SHM\n", (void*)this));
+    return MakeUnique<WindowSurfaceX11SHM>(mXDisplay, mXWindow, mXVisual, mXDepth);
+  }
+#endif // MOZ_HAVE_SHMIMAGE
+
+  LOGDRAW(("Drawing to nsWindow %p using XPutImage\n", (void*)this));
+  return MakeUnique<WindowSurfaceX11Image>(mXDisplay, mXWindow, mXVisual, mXDepth);
+#endif // MOZ_X11
+}
--- a/widget/gtk/nsWindow.h
+++ b/widget/gtk/nsWindow.h
@@ -19,17 +19,17 @@
 #include "nsBaseWidget.h"
 #include <gdk/gdk.h>
 #include <gtk/gtk.h>
 
 #ifdef MOZ_X11
 #include <gdk/gdkx.h>
 #endif /* MOZ_X11 */
 
-#include "nsShmImage.h"
+#include "mozilla/widget/WindowSurface.h"
 
 #ifdef ACCESSIBILITY
 #include "mozilla/a11y/Accessible.h"
 #endif
 #include "mozilla/EventForwards.h"
 #include "mozilla/TouchEvents.h"
 
 #include "IMContextWrapper.h"
@@ -312,19 +312,16 @@ public:
     void               ClearTransparencyBitmap();
 
    virtual void        SetTransparencyMode(nsTransparencyMode aMode) override;
    virtual nsTransparencyMode GetTransparencyMode() override;
    virtual nsresult    ConfigureChildren(const nsTArray<Configuration>& aConfigurations) override;
    nsresult            UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
                                                             uint8_t* aAlphas, int32_t aStride);
 
-    already_AddRefed<mozilla::gfx::DrawTarget> GetDrawTarget(const LayoutDeviceIntRegion& aRegion,
-                                                             mozilla::layers::BufferMode* aBufferMode);
-
 #if (MOZ_WIDGET_GTK == 2)
     static already_AddRefed<DrawTarget> GetDrawTargetForGdkDrawable(GdkDrawable* aDrawable,
                                                                     const mozilla::gfx::IntSize& aSize);
 #endif
     NS_IMETHOD         ReparentNativeWidget(nsIWidget* aNewParent) override;
 
     virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
                                                 uint32_t aNativeMessage,
@@ -453,32 +450,25 @@ private:
 #if GTK_CHECK_VERSION(3,4,0)
     // This field omits duplicate scroll events caused by GNOME bug 726878.
     guint32             mLastScrollEventTime;
 
     // for touch event handling
     nsRefPtrHashtable<nsPtrHashKey<GdkEventSequence>, mozilla::dom::Touch> mTouches;
 #endif
 
+    mozilla::UniquePtr<mozilla::widget::WindowSurface> mWindowSurface;
+
 #ifdef MOZ_X11
     Display*            mXDisplay;
     Window              mXWindow;
     Visual*             mXVisual;
     int                 mXDepth;
 #endif
 
-#ifdef MOZ_HAVE_SHMIMAGE
-    // If we're using xshm rendering
-    RefPtr<nsShmImage>  mFrontShmImage;
-    RefPtr<nsShmImage>  mBackShmImage;
-#endif
-
-    // A fallback image surface when a SHM surface is unavailable.
-    cairo_surface_t* mFallbackSurface;
-
     // Upper bound on pending ConfigureNotify events to be dispatched to the
     // window. See bug 1225044.
     int mPendingConfigures;
 
 #ifdef ACCESSIBILITY
     RefPtr<mozilla::a11y::Accessible> mRootAccessible;
 
     /**
@@ -557,16 +547,18 @@ private:
     virtual LayerManager* GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
                                           LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
                                           LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
 
     void CleanLayerManagerRecursive();
 
     virtual int32_t RoundsWidgetCoordinatesTo() override;
 
+    mozilla::UniquePtr<mozilla::widget::WindowSurface> CreateWindowSurface();
+
     /**
      * |mIMContext| takes all IME related stuff.
      *
      * This is owned by the top-level nsWindow or the topmost child
      * nsWindow embedded in a non-Gecko widget.
      *
      * The instance is created when the top level widget is created.  And when
      * the widget is destroyed, it's released.  All child windows refer its
--- a/widget/moz.build
+++ b/widget/moz.build
@@ -128,16 +128,17 @@ EXPORTS.mozilla += [
 ]
 
 EXPORTS.mozilla.widget += [
     'CompositorWidget.h',
     'IMEData.h',
     'InProcessCompositorWidget.h',
     'PuppetBidiKeyboard.h',
     'WidgetMessageUtils.h',
+    'WindowSurface.h'
 ]
 
 UNIFIED_SOURCES += [
     'CompositorWidget.cpp',
     'ContentCache.cpp',
     'GfxDriverInfo.cpp',
     'GfxInfoBase.cpp',
     'GfxInfoCollector.cpp',
@@ -198,17 +199,18 @@ if CONFIG['MOZ_INSTRUMENT_EVENT_LOOP']:
 EXPORTS.ipc = ['nsGUIEventIPC.h']
 
 if CONFIG['MOZ_X11']:
     DIRS += ['x11']
     UNIFIED_SOURCES += [
         'GfxInfoX11.cpp'
     ]
     SOURCES += [
-        'nsShmImage.cpp'
+        'nsShmImage.cpp',
+        'WindowSurfaceX11SHM.cpp',
     ]
 
 if toolkit in ('cocoa', 'windows'):
     UNIFIED_SOURCES += [
         'nsBaseClipboard.cpp',
     ]
 
 if toolkit in {'gtk2', 'gtk3', 'cocoa', 'windows',