Bug 1708416 - Implement n-buffering for the Wayland software backend, r=stransky
☠☠ backed out by d7166b8e97a9 ☠ ☠
authorRobert Mader <robert.mader@posteo.de>
Tue, 18 May 2021 12:34:27 +0000
changeset 579906 ee3c15c9dba52306591473b3261e3bfce266cd71
parent 579905 bced45f54b4681ea9fcb6417dfff168423698e37
child 579907 84eb62a713ed2d8405ba800a9c930bac26d6cc0f
push id143279
push userrobert.mader@posteo.de
push dateTue, 18 May 2021 12:36:54 +0000
treeherderautoland@ee3c15c9dba5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersstransky
bugs1708416
milestone90.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 1708416 - Implement n-buffering for the Wayland software backend, r=stransky In order to fully comply with the spec and work on all compositors. This is somewhat inspired by `SurfacePoolCA`, moving buffers between buckets - in use or available. The idea is to make it easy to expand this further to share a common buffer pool between multiple surfaces. It works similar to EGL buffer handling and reuses the partial damage logic in WR by implementing buffer age. Notable changes to the current implementation: - always draw directly into shm-buffer memory - no caching. - drawing is purely driven by the compositor / the VsyncSource. No frame callbacks or threads apart from buffer release callbacks. One of the goals here is to handle different compositor behaviour efficiently - if a compositor releases buffers early, we want to reuse a single buffer whenever possible. If a compositor holds buffers longer, we want to minimize the area we need to redraw to older buffers. This is archived by always using the last released buffer. The expected usual buffer age is `1` in the fast release case and `2`-`3` in the holding case. This changes buffer handling quite significantly, so propper testing is due. However, as the overall architecture is somewhat simpler than before, it may improve stability in the long term. Note: instead of replacing, this now duplicates parts of the existing backend. This is in order to keep the existing one stable and gruadually phasing it out together with the Basic compositor. Differential Revision: https://phabricator.services.mozilla.com/D114349
gfx/webrender_bindings/RenderCompositorSWGL.cpp
gfx/webrender_bindings/RenderCompositorSWGL.h
widget/gtk/GtkCompositorWidget.cpp
widget/gtk/GtkCompositorWidget.h
widget/gtk/WindowSurface.h
widget/gtk/WindowSurfaceProvider.cpp
widget/gtk/WindowSurfaceProvider.h
widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp
widget/gtk/WindowSurfaceWaylandMultiBuffer.h
widget/gtk/moz.build
widget/gtk/nsWindow.cpp
--- a/gfx/webrender_bindings/RenderCompositorSWGL.cpp
+++ b/gfx/webrender_bindings/RenderCompositorSWGL.cpp
@@ -4,16 +4,21 @@
  * 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 "RenderCompositorSWGL.h"
 
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/widget/CompositorWidget.h"
 
+#ifdef MOZ_WAYLAND
+#  include "mozilla/gfx/gfxVars.h"
+#  include "mozilla/widget/GtkCompositorWidget.h"
+#endif
+
 namespace mozilla {
 using namespace gfx;
 
 namespace wr {
 
 /* static */
 UniquePtr<RenderCompositor> RenderCompositorSWGL::Create(
     const RefPtr<widget::CompositorWidget>& aWidget, nsACString& aError) {
@@ -47,16 +52,21 @@ bool RenderCompositorSWGL::MakeCurrent()
 }
 
 bool RenderCompositorSWGL::BeginFrame() {
   // Set up a temporary region representing the entire window surface in case a
   // dirty region is not supplied.
   ClearMappedBuffer();
   mDirtyRegion = LayoutDeviceIntRect(LayoutDeviceIntPoint(), GetBufferSize());
   wr_swgl_make_current(mContext);
+
+#ifdef MOZ_WAYLAND
+  mWidget->AsGTK()->PrepareBufferForFrame();
+#endif
+
   return true;
 }
 
 bool RenderCompositorSWGL::AllocateMappedBuffer(
     const wr::DeviceIntRect* aOpaqueRects, size_t aNumOpaqueRects) {
   // Request a new draw target to use from the widget...
   MOZ_ASSERT(!mDT);
   layers::BufferMode bufferMode = layers::BufferMode::BUFFERED;
@@ -244,16 +254,36 @@ bool RenderCompositorSWGL::RequestFullRe
 #ifdef MOZ_WIDGET_ANDROID
   // XXX Add partial present support.
   return true;
 #else
   return false;
 #endif
 }
 
+uint32_t RenderCompositorSWGL::GetMaxPartialPresentRects() {
+  return gfx::gfxVars::WebRenderMaxPartialPresentRects();
+}
+
+bool RenderCompositorSWGL::ShouldDrawPreviousPartialPresentRegions() {
+#ifdef MOZ_WAYLAND
+  return mWidget->AsGTK()->ShouldDrawPreviousPartialPresentRegions();
+#else
+  return false;
+#endif
+}
+
+size_t RenderCompositorSWGL::GetBufferAge() const {
+#ifdef MOZ_WAYLAND
+  return mWidget->AsGTK()->GetBufferAge();
+#else
+  return 0;
+#endif
+}
+
 void RenderCompositorSWGL::Pause() {}
 
 bool RenderCompositorSWGL::Resume() { return true; }
 
 LayoutDeviceIntSize RenderCompositorSWGL::GetBufferSize() {
   return mWidget->GetClientSize();
 }
 
--- a/gfx/webrender_bindings/RenderCompositorSWGL.h
+++ b/gfx/webrender_bindings/RenderCompositorSWGL.h
@@ -33,16 +33,19 @@ class RenderCompositorSWGL : public Rend
 
   void StartCompositing(const wr::DeviceIntRect* aDirtyRects,
                         size_t aNumDirtyRects,
                         const wr::DeviceIntRect* aOpaqueRects,
                         size_t aNumOpaqueRects) override;
 
   bool UsePartialPresent() override { return true; }
   bool RequestFullRender() override;
+  uint32_t GetMaxPartialPresentRects() override;
+  bool ShouldDrawPreviousPartialPresentRegions() override;
+  size_t GetBufferAge() const override;
 
   void Pause() override;
   bool Resume() override;
 
   layers::WebRenderBackend BackendType() const override {
     return layers::WebRenderBackend::SOFTWARE;
   }
   layers::WebRenderCompositor CompositorType() const override {
--- a/widget/gtk/GtkCompositorWidget.cpp
+++ b/widget/gtk/GtkCompositorWidget.cpp
@@ -1,58 +1,79 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "GtkCompositorWidget.h"
 
 #include "mozilla/layers/CompositorThread.h"
+#include "mozilla/Preferences.h"
 #include "mozilla/widget/InProcessCompositorWidget.h"
 #include "mozilla/widget/PlatformWidgetTypes.h"
 #include "nsWindow.h"
-#include "mozilla/X11Util.h"
+
+#ifdef MOZ_X11
+#  include "mozilla/X11Util.h"
+#endif
 
 #ifdef MOZ_WAYLAND
 #  include "mozilla/layers/NativeLayerWayland.h"
 #endif
 
 namespace mozilla {
 namespace widget {
 
 GtkCompositorWidget::GtkCompositorWidget(
     const GtkCompositorWidgetInitData& aInitData,
     const layers::CompositorOptions& aOptions, nsWindow* aWindow)
     : CompositorWidget(aOptions),
       mWidget(aWindow),
       mClientSize("GtkCompositorWidget::mClientSize") {
 #if defined(MOZ_WAYLAND)
-  if (!aInitData.IsX11Display()) {
+  if (GdkIsWaylandDisplay() && (GetCompositorOptions().UseSoftwareWebRender() ||
+                                !GetCompositorOptions().UseWebRender())) {
     if (!aWindow) {
       NS_WARNING("GtkCompositorWidget: We're missing nsWindow!");
     }
-    mProvider.Initialize(aWindow);
-    mNativeLayerRoot = nullptr;
+
+    // By default use multi-buffered backend for SW-WR and single-buffered
+    // for Basic. Assume that SW-WR and Basic never get mixed within the
+    // same process.
+    static bool useMultiBufferBackend =
+        [](const layers::CompositorOptions& aOptions) {
+          if (Preferences::HasUserValue(
+                  "widget.wayland.software-backend-multi-buffer")) {
+            return Preferences::GetBool(
+                "widget.wayland.software-backend-multi-buffer");
+          }
+          return aOptions.UseSoftwareWebRender();
+        }(GetCompositorOptions());
+
+    mProvider.Initialize(aWindow, useMultiBufferBackend);
   }
 #endif
 #if defined(MOZ_X11)
-  if (aInitData.IsX11Display()) {
+  if (GdkIsX11Display()) {
     mXWindow = (Window)aInitData.XWindow();
 
     // Grab the window's visual and depth
     XWindowAttributes windowAttrs;
     if (!XGetWindowAttributes(DefaultXDisplay(), mXWindow, &windowAttrs)) {
       NS_WARNING("GtkCompositorWidget(): XGetWindowAttributes() failed!");
     }
 
     Visual* visual = windowAttrs.visual;
     mDepth = windowAttrs.depth;
 
     // Initialize the window surface provider
-    mProvider.Initialize(mXWindow, visual, mDepth, aInitData.Shaped());
+    if (GetCompositorOptions().UseSoftwareWebRender() ||
+        !GetCompositorOptions().UseWebRender()) {
+      mProvider.Initialize(mXWindow, visual, mDepth, aInitData.Shaped());
+    }
   }
 #endif
   auto size = mClientSize.Lock();
   *size = aInitData.InitialClientSize();
 }
 
 GtkCompositorWidget::~GtkCompositorWidget() { mProvider.CleanupResources(); }
 
@@ -68,16 +89,28 @@ GtkCompositorWidget::StartRemoteDrawingI
   return mProvider.StartRemoteDrawingInRegion(aInvalidRegion, aBufferMode);
 }
 
 void GtkCompositorWidget::EndRemoteDrawingInRegion(
     gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
   mProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
 }
 
+void GtkCompositorWidget::PrepareBufferForFrame() {
+  return mProvider.PrepareBufferForFrame();
+}
+
+size_t GtkCompositorWidget::GetBufferAge() const {
+  return mProvider.GetBufferAge();
+}
+
+bool GtkCompositorWidget::ShouldDrawPreviousPartialPresentRegions() {
+  return mProvider.ShouldDrawPreviousPartialPresentRegions();
+}
+
 nsIWidget* GtkCompositorWidget::RealWidget() { return mWidget; }
 
 void GtkCompositorWidget::NotifyClientSizeChanged(
     const LayoutDeviceIntSize& aClientSize) {
   auto size = mClientSize.Lock();
   *size = aClientSize;
 }
 
--- a/widget/gtk/GtkCompositorWidget.h
+++ b/widget/gtk/GtkCompositorWidget.h
@@ -50,16 +50,19 @@ class GtkCompositorWidget : public Compo
   void EndRemoteDrawing() override;
 
   already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
       const LayoutDeviceIntRegion& aInvalidRegion,
       layers::BufferMode* aBufferMode) override;
   void EndRemoteDrawingInRegion(
       gfx::DrawTarget* aDrawTarget,
       const LayoutDeviceIntRegion& aInvalidRegion) override;
+  void PrepareBufferForFrame();
+  size_t GetBufferAge() const;
+  bool ShouldDrawPreviousPartialPresentRegions();
   uintptr_t GetWidgetKey() override;
 
   LayoutDeviceIntSize GetClientSize() override;
 
   nsIWidget* RealWidget() override;
   GtkCompositorWidget* AsGTK() override { return this; }
   CompositorWidgetDelegate* AsDelegate() override { return this; }
 
--- a/widget/gtk/WindowSurface.h
+++ b/widget/gtk/WindowSurface.h
@@ -26,16 +26,20 @@ class WindowSurface {
 
   // 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;
 
   // Whether the window surface represents a fallback method.
   virtual bool IsFallback() const { return false; }
 
+  virtual void PrepareBufferForFrame(){};
+  virtual size_t GetBufferAge() const { return 0; };
+  virtual bool ShouldDrawPreviousPartialPresentRegions() { return false; };
+
  protected:
   virtual ~WindowSurface() = default;
 };
 
 }  // namespace widget
 }  // namespace mozilla
 
 #endif  // _MOZILLA_WIDGET_WINDOW_SURFACE_H
--- a/widget/gtk/WindowSurfaceProvider.cpp
+++ b/widget/gtk/WindowSurfaceProvider.cpp
@@ -8,16 +8,17 @@
 
 #include "gfxPlatformGtk.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "nsWindow.h"
 
 #ifdef MOZ_WAYLAND
 #  include "WindowSurfaceWayland.h"
+#  include "WindowSurfaceWaylandMultiBuffer.h"
 #endif
 #ifdef MOZ_X11
 #  include "mozilla/X11Util.h"
 #  include "WindowSurfaceX11Image.h"
 #  include "WindowSurfaceX11SHM.h"
 #  include "WindowSurfaceXRender.h"
 #endif
 
@@ -25,47 +26,62 @@ namespace mozilla {
 namespace widget {
 
 using namespace mozilla::layers;
 
 WindowSurfaceProvider::WindowSurfaceProvider()
     : mWindowSurface(nullptr)
 #ifdef MOZ_WAYLAND
       ,
-      mWidget(nullptr)
+      mWidget(nullptr),
+      mUseMultiBuffer(false)
 #endif
 #ifdef MOZ_X11
       ,
       mIsShaped(false),
       mXDepth(0),
       mXWindow(0),
       mXVisual(nullptr)
 #endif
 {
 }
 
 #ifdef MOZ_WAYLAND
-void WindowSurfaceProvider::Initialize(nsWindow* aWidget) { mWidget = aWidget; }
+void WindowSurfaceProvider::Initialize(nsWindow* aWidget,
+                                       bool aUseMultiBuffer) {
+  mWidget = aWidget;
+  mUseMultiBuffer = aUseMultiBuffer;
+}
 #endif
 #ifdef MOZ_X11
 void WindowSurfaceProvider::Initialize(Window aWindow, Visual* aVisual,
                                        int aDepth, bool aIsShaped) {
   mXWindow = aWindow;
   mXVisual = aVisual;
   mXDepth = aDepth;
   mIsShaped = aIsShaped;
 }
 #endif
 
 void WindowSurfaceProvider::CleanupResources() { mWindowSurface = nullptr; }
 
 RefPtr<WindowSurface> WindowSurfaceProvider::CreateWindowSurface() {
 #ifdef MOZ_WAYLAND
   if (GdkIsWaylandDisplay()) {
-    LOG(("Drawing to nsWindow %p will use wl_surface\n", mWidget));
+    if (mUseMultiBuffer) {
+      LOG(
+          ("Drawing to nsWindow %p will use wl_surface. Using multi-buffered "
+           "backend.\n",
+           mWidget));
+      return MakeRefPtr<WindowSurfaceWaylandMB>(mWidget);
+    }
+    LOG(
+        ("Drawing to nsWindow %p will use wl_surface. Using single-buffered "
+         "backend.\n",
+         mWidget));
     return MakeRefPtr<WindowSurfaceWayland>(mWidget);
   }
 #endif
 #ifdef MOZ_X11
   if (GdkIsX11Display()) {
     // Blit to the window with the following priority:
     // 1. XRender (iff XRender is enabled && we are in-process)
     // 2. MIT-SHM
@@ -119,10 +135,26 @@ WindowSurfaceProvider::StartRemoteDrawin
   return dt.forget();
 }
 
 void WindowSurfaceProvider::EndRemoteDrawingInRegion(
     gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
   if (mWindowSurface) mWindowSurface->Commit(aInvalidRegion);
 }
 
+bool WindowSurfaceProvider::ShouldDrawPreviousPartialPresentRegions() {
+  return mWindowSurface
+             ? mWindowSurface->ShouldDrawPreviousPartialPresentRegions()
+             : false;
+}
+
+size_t WindowSurfaceProvider::GetBufferAge() const {
+  return mWindowSurface ? mWindowSurface->GetBufferAge() : 0;
+}
+
+void WindowSurfaceProvider::PrepareBufferForFrame() {
+  if (mWindowSurface) {
+    mWindowSurface->PrepareBufferForFrame();
+  }
+}
+
 }  // namespace widget
 }  // namespace mozilla
--- a/widget/gtk/WindowSurfaceProvider.h
+++ b/widget/gtk/WindowSurfaceProvider.h
@@ -36,17 +36,17 @@ class WindowSurfaceProvider final {
 
   /**
    * Initializes the WindowSurfaceProvider by giving it the window
    * handle and display to attach to. WindowSurfaceProvider doesn't
    * own the Display, Window, etc, and they must continue to exist
    * while WindowSurfaceProvider is used.
    */
 #ifdef MOZ_WAYLAND
-  void Initialize(nsWindow* aWidget);
+  void Initialize(nsWindow* aWidget, bool aUseMultiBuffer);
 #endif
 #ifdef MOZ_X11
   void Initialize(Window aWindow, Visual* aVisual, int aDepth, bool aIsShaped);
 #endif
 
   /**
    * Releases any surfaces created by this provider.
    * This is used by GtkCompositorWidget to get rid
@@ -54,23 +54,27 @@ class WindowSurfaceProvider final {
    */
   void CleanupResources();
 
   already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
       const LayoutDeviceIntRegion& aInvalidRegion,
       layers::BufferMode* aBufferMode);
   void EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget,
                                 const LayoutDeviceIntRegion& aInvalidRegion);
+  bool ShouldDrawPreviousPartialPresentRegions();
+  size_t GetBufferAge() const;
+  void PrepareBufferForFrame();
 
  private:
   RefPtr<WindowSurface> CreateWindowSurface();
 
   RefPtr<WindowSurface> mWindowSurface;
 #ifdef MOZ_WAYLAND
   nsWindow* mWidget;
+  bool mUseMultiBuffer;
 #endif
 #ifdef MOZ_X11
   bool mIsShaped;
   int mXDepth;
   Window mXWindow;
   Visual* mXVisual;
 #endif
 };
new file mode 100644
--- /dev/null
+++ b/widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp
@@ -0,0 +1,340 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 "WindowSurfaceWaylandMultiBuffer.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "MozContainer.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/WidgetUtils.h"
+
+#undef LOG
+#ifdef MOZ_LOGGING
+#  include "mozilla/Logging.h"
+#  include "Units.h"
+extern mozilla::LazyLogModule gWidgetWaylandLog;
+#  define LOGWAYLAND(args) \
+    MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, args)
+#else
+#  define LOGWAYLAND(args)
+#endif /* MOZ_LOGGING */
+
+namespace mozilla::widget {
+
+/*
+  Wayland multi-thread rendering scheme
+
+  Every rendering thread (main thread, compositor thread) contains its own
+  nsWaylandDisplay object connected to Wayland compositor (Mutter, Weston, etc.)
+
+  WindowSurfaceWayland implements WindowSurface class and draws nsWindow by
+  WindowSurface interface (Lock, Commit) to screen through nsWaylandDisplay.
+
+  ----------------------
+  | Wayland compositor |
+  ----------------------
+             ^
+             |
+  ----------------------
+  |  nsWaylandDisplay  |
+  ----------------------
+        ^          ^
+        |          |
+        |          |
+        |       ---------------------------------        ------------------
+        |       | WindowSurfaceWayland          |<------>| nsWindow       |
+        |       |                               |        ------------------
+        |       |  -----------------------      |
+        |       |  | WaylandShmBuffer    |      |
+        |       |  |                     |      |
+        |       |  | ------------------- |      |
+        |       |  | |  WaylandShmPool | |      |
+        |       |  | ------------------- |      |
+        |       |  -----------------------      |
+        |       |                               |
+        |       |  -----------------------      |
+        |       |  | WaylandShmBuffer    |      |
+        |       |  |                     |      |
+        |       |  | ------------------- |      |
+        |       |  | |  WaylandShmPool | |      |
+        |       |  | ------------------- |      |
+        |       |  -----------------------      |
+        |       ---------------------------------
+        |
+        |
+  ---------------------------------        ------------------
+  | WindowSurfaceWayland          |<------>| nsWindow       |
+  |                               |        ------------------
+  |  -----------------------      |
+  |  | WaylandShmBuffer    |      |
+  |  |                     |      |
+  |  | ------------------- |      |
+  |  | |  WaylandShmPool | |      |
+  |  | ------------------- |      |
+  |  -----------------------      |
+  |                               |
+  |  -----------------------      |
+  |  | WaylandShmBuffer    |      |
+  |  |                     |      |
+  |  | ------------------- |      |
+  |  | |  WaylandShmPool | |      |
+  |  | ------------------- |      |
+  |  -----------------------      |
+  ---------------------------------
+
+
+nsWaylandDisplay
+
+Is our connection to Wayland display server,
+holds our display connection (wl_display) and event queue (wl_event_queue).
+
+nsWaylandDisplay is created for every thread which sends data to Wayland
+compositor. Wayland events for main thread is served by default Gtk+ loop,
+for other threads (compositor) we must create wl_event_queue and run event loop.
+
+
+WindowSurfaceWayland
+
+Is a Wayland implementation of WindowSurface class for WindowSurfaceProvider,
+we implement Lock() and Commit() interfaces from WindowSurface
+for actual drawing.
+
+One WindowSurfaceWayland draws one nsWindow so those are tied 1:1.
+At Wayland level it holds one wl_surface object.
+
+To perform visualiation of nsWindow, WindowSurfaceWayland contains one
+wl_surface and two wl_buffer objects (owned by WaylandShmBuffer)
+as we use double buffering. When nsWindow drawing is finished to wl_buffer,
+the wl_buffer is attached to wl_surface and it's sent to Wayland compositor.
+
+When there's no wl_buffer available for drawing (all wl_buffers are locked in
+compositor for instance) we store the drawing to WindowImageSurface object
+and draw later when wl_buffer becomes available or discard the
+WindowImageSurface cache when whole screen is invalidated.
+
+WaylandShmBuffer
+
+Is a class which provides a wl_buffer for drawing.
+Wl_buffer is a main Wayland object with actual graphics data.
+Wl_buffer basically represent one complete window screen.
+When double buffering is involved every window (GdkWindow for instance)
+utilises two wl_buffers which are cycled. One is filed with data by application
+and one is rendered by compositor.
+
+WaylandShmBuffer is implemented by shared memory (shm).
+It owns wl_buffer object, owns WaylandShmPool
+(which provides the shared memory) and ties them together.
+
+WaylandShmPool
+
+WaylandShmPool acts as a manager of shared memory for WaylandShmBuffer.
+Allocates it, holds reference to it and releases it.
+
+We allocate shared memory (shm) by mmap(..., MAP_SHARED,...) as an interface
+between us and wayland compositor. We draw our graphics data to the shm and
+handle to wayland compositor by WaylandShmBuffer/WindowSurfaceWayland
+(wl_buffer/wl_surface).
+*/
+
+#define BACK_BUFFER_NUM 3
+
+WindowSurfaceWaylandMB::WindowSurfaceWaylandMB(nsWindow* aWindow)
+    : mSurfaceLock("WindowSurfaceWayland lock"),
+      mWindow(aWindow),
+      mWaylandDisplay(WaylandDisplayGet()),
+      mWaylandBuffer(nullptr) {}
+
+RefPtr<WaylandShmBuffer> WindowSurfaceWaylandMB::ObtainBufferFromPool() {
+  if (!mAvailableBuffers.IsEmpty()) {
+    RefPtr<WaylandShmBuffer> buffer = mAvailableBuffers.PopLastElement();
+    mInUseBuffers.AppendElement(buffer);
+    return buffer;
+  }
+
+  RefPtr<WaylandShmBuffer> buffer =
+      WaylandShmBuffer::Create(GetWaylandDisplay(), mMozContainerSize);
+  mInUseBuffers.AppendElement(buffer);
+
+  buffer->SetBufferReleaseFunc(
+      &WindowSurfaceWaylandMB::BufferReleaseCallbackHandler);
+  buffer->SetBufferReleaseData(this);
+
+  return buffer;
+}
+
+void WindowSurfaceWaylandMB::ReturnBufferToPool(
+    const RefPtr<WaylandShmBuffer>& aBuffer) {
+  for (const RefPtr<WaylandShmBuffer>& buffer : mInUseBuffers) {
+    if (buffer == aBuffer) {
+      if (buffer->IsMatchingSize(mMozContainerSize)) {
+        mAvailableBuffers.AppendElement(buffer);
+      }
+      mInUseBuffers.RemoveElement(buffer);
+      return;
+    }
+  }
+  MOZ_RELEASE_ASSERT(false, "Returned buffer not in use");
+}
+
+void WindowSurfaceWaylandMB::EnforcePoolSizeLimitLocked() {
+  // Enforce the pool size limit, removing least-recently-used entries as
+  // necessary.
+  while (mAvailableBuffers.Length() > BACK_BUFFER_NUM) {
+    mAvailableBuffers.RemoveElementAt(0);
+  }
+
+  NS_WARNING_ASSERTION(mInUseBuffers.Length() < 10, "We are leaking buffers");
+}
+
+void WindowSurfaceWaylandMB::PrepareBufferForFrame() {
+  MutexAutoLock lock(mSurfaceLock);
+  PrepareBufferForFrameLocked();
+}
+
+void WindowSurfaceWaylandMB::PrepareBufferForFrameLocked() {
+  if (mWindow->WindowType() == eWindowType_invisible) {
+    return;
+  }
+
+  LayoutDeviceIntSize newMozContainerSize = mWindow->GetMozContainerSize();
+  if (mMozContainerSize != newMozContainerSize) {
+    mMozContainerSize = newMozContainerSize;
+    mAvailableBuffers.Clear();
+  }
+
+  LOGWAYLAND(
+      ("WindowSurfaceWaylandMB::PrepareBufferForFrameLocked [%p] MozContainer "
+       "size [%d x %d]\n",
+       (void*)this, mMozContainerSize.width, mMozContainerSize.height));
+
+  MOZ_ASSERT(!mWaylandBuffer);
+  mWaylandBuffer = ObtainBufferFromPool();
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceWaylandMB::Lock(
+    const LayoutDeviceIntRegion& aRegion) {
+  MutexAutoLock lock(mSurfaceLock);
+
+  // The Basic layers compositor does not call PrepareBufferForFrame()
+  if (!mWaylandBuffer) {
+    PrepareBufferForFrameLocked();
+    if (!mWaylandBuffer) {
+      return nullptr;
+    }
+  }
+
+#ifdef MOZ_LOGGING
+  gfx::IntRect lockRect = aRegion.GetBounds().ToUnknownRect();
+  LOGWAYLAND(
+      ("WindowSurfaceWaylandMB::Lock [%p] [%d,%d] -> [%d x %d] rects %d "
+       "MozContainer size [%d x %d]\n",
+       (void*)this, lockRect.x, lockRect.y, lockRect.width, lockRect.height,
+       aRegion.GetNumRects(), mMozContainerSize.width,
+       mMozContainerSize.height));
+#endif
+
+  RefPtr<gfx::DrawTarget> dt = mWaylandBuffer->Lock();
+  return dt.forget();
+}
+
+void WindowSurfaceWaylandMB::Commit(
+    const LayoutDeviceIntRegion& aInvalidRegion) {
+  MutexAutoLock lock(mSurfaceLock);
+
+#ifdef MOZ_LOGGING
+  gfx::IntRect invalidRect = aInvalidRegion.GetBounds().ToUnknownRect();
+  LOGWAYLAND(
+      ("WindowSurfaceWaylandMB::Commit [%p] damage rect [%d, %d] -> [%d x %d] "
+       "MozContainer [%d x %d]\n",
+       (void*)this, invalidRect.x, invalidRect.y, invalidRect.width,
+       invalidRect.height, mMozContainerSize.width, mMozContainerSize.height));
+#endif
+
+  if (!mWaylandBuffer) {
+    LOGWAYLAND(
+        ("WindowSurfaceWaylandMB::Commit [%p] frame skipped: no buffer\n",
+         (void*)this));
+    IncrementBufferAge();
+    return;
+  }
+
+  MozContainer* container = mWindow->GetMozContainer();
+  wl_surface* waylandSurface = moz_container_wayland_surface_lock(container);
+  if (!waylandSurface) {
+    LOGWAYLAND(
+        ("WindowSurfaceWaylandMB::Commit [%p] frame skipped: can't lock "
+         "wl_surface\n",
+         (void*)this));
+    ReturnBufferToPool(mWaylandBuffer);
+    mWaylandBuffer = nullptr;
+    IncrementBufferAge();
+    return;
+  }
+
+  for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+    mozilla::LayoutDeviceIntRect r = iter.Get();
+    wl_surface_damage_buffer(waylandSurface, r.x, r.y, r.width, r.height);
+  }
+
+  mWaylandBuffer->AttachAndCommit(waylandSurface);
+
+  moz_container_wayland_surface_unlock(container, &waylandSurface);
+
+  mWaylandBuffer->ResetBufferAge();
+  mWaylandBuffer = nullptr;
+
+  EnforcePoolSizeLimitLocked();
+
+  IncrementBufferAge();
+
+  if (wl_display_flush(GetWaylandDisplay()->GetDisplay()) == -1) {
+    LOGWAYLAND(
+        ("WindowSurfaceWaylandMB::Commit [%p] flush failed\n", (void*)this));
+  }
+}
+
+void WindowSurfaceWaylandMB::IncrementBufferAge() {
+  for (const RefPtr<WaylandShmBuffer>& buffer : mInUseBuffers) {
+    buffer->IncrementBufferAge();
+  }
+  for (const RefPtr<WaylandShmBuffer>& buffer : mAvailableBuffers) {
+    buffer->IncrementBufferAge();
+  }
+}
+
+size_t WindowSurfaceWaylandMB::GetBufferAge() const {
+  LOGWAYLAND(("WindowSurfaceWaylandMB::GetBufferAge [%p] buffer %p age %lu\n",
+              (void*)this, mWaylandBuffer ? mWaylandBuffer.get() : nullptr,
+              mWaylandBuffer ? mWaylandBuffer->GetBufferAge() : 0));
+
+  return mWaylandBuffer ? mWaylandBuffer->GetBufferAge() : 0;
+}
+
+void WindowSurfaceWaylandMB::BufferReleaseCallbackHandler(wl_buffer* aBuffer) {
+  MutexAutoLock lock(mSurfaceLock);
+
+  for (const RefPtr<WaylandShmBuffer>& buffer : mInUseBuffers) {
+    if (buffer->GetWlBuffer() == aBuffer) {
+      ReturnBufferToPool(buffer);
+      break;
+    }
+  }
+}
+
+void WindowSurfaceWaylandMB::BufferReleaseCallbackHandler(void* aData,
+                                                          wl_buffer* aBuffer) {
+  auto* surface = reinterpret_cast<WindowSurfaceWaylandMB*>(aData);
+  surface->BufferReleaseCallbackHandler(aBuffer);
+}
+
+}  // namespace mozilla::widget
new file mode 100644
--- /dev/null
+++ b/widget/gtk/WindowSurfaceWaylandMultiBuffer.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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_WAYLAND_MULTI_BUFFER_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_MULTI_BUFFER_H
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/Mutex.h"
+#include "nsTArray.h"
+#include "nsWaylandDisplay.h"
+#include "nsWindow.h"
+#include "WaylandShmBuffer.h"
+#include "WindowSurface.h"
+
+namespace mozilla::widget {
+
+// WindowSurfaceWaylandMB is an abstraction for wl_surface
+// and related management
+class WindowSurfaceWaylandMB : public WindowSurface {
+ public:
+  explicit WindowSurfaceWaylandMB(nsWindow* aWindow);
+  ~WindowSurfaceWaylandMB() = default;
+
+  // Lock() / Commit() are called by gecko when Firefox
+  // wants to display something. Lock() returns a DrawTarget
+  // where gecko paints. When gecko is done it calls Commit()
+  // and we try to send the DrawTarget (backed by wl_buffer)
+  // to wayland compositor.
+  //
+  // If we fail (wayland compositor is busy,
+  // wl_surface is not created yet) we queue the painting
+  // and we send it to wayland compositor in FrameCallbackHandler()/
+  // FlushPendingCommits().
+  already_AddRefed<gfx::DrawTarget> Lock(
+      const LayoutDeviceIntRegion& aRegion) override;
+  void Commit(const LayoutDeviceIntRegion& aInvalidRegion) final;
+  RefPtr<nsWaylandDisplay> GetWaylandDisplay() { return mWaylandDisplay; };
+
+  void PrepareBufferForFrame() override;
+  size_t GetBufferAge() const override;
+  bool ShouldDrawPreviousPartialPresentRegions() override { return true; };
+
+  static void BufferReleaseCallbackHandler(void* aData, wl_buffer* aBuffer);
+
+ private:
+  RefPtr<WaylandShmBuffer> ObtainBufferFromPool();
+  void ReturnBufferToPool(const RefPtr<WaylandShmBuffer>& aBuffer);
+  void EnforcePoolSizeLimitLocked();
+  void PrepareBufferForFrameLocked();
+  void IncrementBufferAge();
+  void BufferReleaseCallbackHandler(wl_buffer* aBuffer);
+
+  mozilla::Mutex mSurfaceLock;
+
+  nsWindow* mWindow;
+  RefPtr<nsWaylandDisplay> mWaylandDisplay;
+  RefPtr<WaylandShmBuffer> mWaylandBuffer;
+  nsTArray<RefPtr<WaylandShmBuffer>> mInUseBuffers;
+  nsTArray<RefPtr<WaylandShmBuffer>> mAvailableBuffers;
+  LayoutDeviceIntSize mMozContainerSize;
+};
+
+}  // namespace mozilla::widget
+
+#endif  // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_MULTI_BUFFER_H
--- a/widget/gtk/moz.build
+++ b/widget/gtk/moz.build
@@ -84,16 +84,17 @@ if CONFIG["MOZ_WAYLAND"]:
     UNIFIED_SOURCES += [
         "DMABufLibWrapper.cpp",
         "DMABufSurface.cpp",
         "MozContainerWayland.cpp",
         "nsClipboardWayland.cpp",
         "nsWaylandDisplay.cpp",
         "WaylandShmBuffer.cpp",
         "WindowSurfaceWayland.cpp",
+        "WindowSurfaceWaylandMultiBuffer.cpp",
     ]
     EXPORTS.mozilla.widget += [
         "DMABufLibWrapper.h",
         "DMABufSurface.h",
         "MozContainerWayland.h",
         "nsWaylandDisplay.h",
         "WaylandShmBuffer.h",
     ]
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -5275,22 +5275,21 @@ nsresult nsWindow::Create(nsIWidget* aPa
       SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED);
     }
     // Dummy call to a function in mozgtk to prevent the linker from removing
     // the dependency with --as-needed.
     if (GdkIsX11Display()) {
       XShmQueryExtension(DefaultXDisplay());
     }
   }
-#  ifdef MOZ_WAYLAND
-  else if (GdkIsWaylandDisplay()) {
-    mSurfaceProvider.Initialize(this);
+#endif
+#ifdef MOZ_WAYLAND
+  if (GdkIsWaylandDisplay()) {
     WaylandStartVsync();
   }
-#  endif
 #endif
 
   // Set default application name when it's empty.
   if (mGtkWindowAppName.IsEmpty()) {
     mGtkWindowAppName = gAppData->name;
   }
   RefreshWindowClass();