Bug 1699985 - Implement basic native Wayland compositor backend, r=stransky,mstange,aosmond
authorRobert Mader <robert.mader@posteo.de>
Tue, 11 May 2021 13:38:59 +0000
changeset 579379 ac3dcf91080f8e37d4dc62bb4781e03b1c3c548f
parent 579378 9b1f2cdcd1f4103314f2bdbc04da4ef784e25a57
child 579380 83b1cfc403eec5739d9bd7c0eaa7027869df0a59
push id142958
push userrobert.mader@posteo.de
push dateTue, 11 May 2021 13:41:25 +0000
treeherderautoland@ac3dcf91080f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersstransky, mstange, aosmond
bugs1699985, 1699754
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 1699985 - Implement basic native Wayland compositor backend, r=stransky,mstange,aosmond This implements a mostly working native backend for Wayland. It can be enabled via `gfx.webrender.compositor.force-enabled`. The focus here was to get a basic structure in place while mini- mising changes in shared code. Known issues and limitations: - No readback - this will likely require an internal compositor again, as Wayland doesn't allow easily allow readback of the composited image, at least not without asking for permission. Alternatively, a new Wayland extension could be written for it. - Frame-call related issues when using a compositor that optimizes them (e.g. Gnome-Shell). This will be fixed in a follow-up, in the mean time disabling `widget.wayland.opaque-region.enabled` and `widget.wayland.vsync.enabled` works around the issues. - Only works on Weston or very recent versions of Gnome-Shell, see bug 1699754 Differential Revision: https://phabricator.services.mozilla.com/D111662
gfx/layers/LayersTypes.h
gfx/layers/NativeLayer.h
gfx/layers/NativeLayerWayland.cpp
gfx/layers/NativeLayerWayland.h
gfx/layers/SurfacePool.h
gfx/layers/SurfacePoolWayland.cpp
gfx/layers/SurfacePoolWayland.h
gfx/layers/moz.build
gfx/thebes/gfxPlatformGtk.cpp
gfx/thebes/gfxPlatformGtk.h
gfx/webrender_bindings/RenderCompositor.cpp
gfx/webrender_bindings/RenderCompositorNative.cpp
gfx/webrender_bindings/RenderThread.cpp
gfx/webrender_bindings/moz.build
widget/gtk/GfxInfo.cpp
widget/gtk/GtkCompositorWidget.cpp
widget/gtk/GtkCompositorWidget.h
widget/gtk/MozContainerWayland.cpp
widget/gtk/MozContainerWayland.h
--- a/gfx/layers/LayersTypes.h
+++ b/gfx/layers/LayersTypes.h
@@ -184,16 +184,17 @@ enum class WebRenderBackend : int8_t { H
 
 enum class WebRenderCompositor : int8_t {
   DRAW = 0,
   DIRECT_COMPOSITION,
   CORE_ANIMATION,
   SOFTWARE,
   D3D11,
   OPENGL,
+  WAYLAND,
   LAST
 };
 
 const char* GetLayersBackendName(LayersBackend aBackend);
 
 enum class TextureType : int8_t {
   Unknown = 0,
   D3D11,
--- a/gfx/layers/NativeLayer.h
+++ b/gfx/layers/NativeLayer.h
@@ -25,16 +25,17 @@ class GLContext;
 namespace wr {
 class RenderTextureHost;
 }
 
 namespace layers {
 
 class NativeLayer;
 class NativeLayerCA;
+class NativeLayerWayland;
 class NativeLayerRootSnapshotter;
 class SurfacePoolHandle;
 
 // NativeLayerRoot and NativeLayer allow building up a flat layer "tree" of
 // sibling layers. These layers provide a cross-platform abstraction for the
 // platform's native layers, such as CoreAnimation layers on macOS.
 // Every layer has a rectangle that describes its position and size in the
 // window. The native layer root is usually be created by the window, and then
@@ -47,16 +48,18 @@ class NativeLayerRoot {
       const gfx::IntSize& aSize, bool aIsOpaque,
       SurfacePoolHandle* aSurfacePoolHandle) = 0;
   virtual already_AddRefed<NativeLayer> CreateLayerForExternalTexture(
       bool aIsOpaque) = 0;
 
   virtual void AppendLayer(NativeLayer* aLayer) = 0;
   virtual void RemoveLayer(NativeLayer* aLayer) = 0;
   virtual void SetLayers(const nsTArray<RefPtr<NativeLayer>>& aLayers) = 0;
+  virtual void PauseCompositor(){};
+  virtual bool ResumeCompositor() { return true; };
 
   // Publish the layer changes to the screen. Returns whether the commit was
   // successful.
   virtual bool CommitToScreen() = 0;
 
   // Returns a new NativeLayerRootSnapshotter that can be used to read back the
   // visual output of this NativeLayerRoot. The snapshotter needs to be
   // destroyed on the same thread that CreateSnapshotter() was called on. Only
@@ -112,16 +115,17 @@ class NativeLayerRootSnapshotter : publi
 // change. And secondly, manipulating a small layer rather than a large layer
 // will reduce the window manager's work for that frame because it'll only copy
 // the pixels of the small layer to the screen.
 class NativeLayer {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NativeLayer)
 
   virtual NativeLayerCA* AsNativeLayerCA() { return nullptr; }
+  virtual NativeLayerWayland* AsNativeLayerWayland() { return nullptr; }
 
   // The size and opaqueness of a layer are supplied during layer creation and
   // never change.
   virtual gfx::IntSize GetSize() = 0;
   virtual bool IsOpaque() = 0;
 
   // The location of the layer, in integer device pixels.
   // This is applied to the layer, before the transform is applied.
new file mode 100644
--- /dev/null
+++ b/gfx/layers/NativeLayerWayland.cpp
@@ -0,0 +1,514 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/layers/NativeLayerWayland.h"
+
+#include <utility>
+#include <algorithm>
+
+#include "gfxUtils.h"
+#include "GLContextEGL.h"
+#include "GLContextProvider.h"
+#include "mozilla/layers/SurfacePoolWayland.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/webrender/RenderThread.h"
+
+namespace mozilla::layers {
+
+using gfx::IntPoint;
+using gfx::IntRect;
+using gfx::IntRegion;
+using gfx::IntSize;
+using gfx::Matrix4x4;
+using gfx::Point;
+using gfx::Rect;
+using gfx::Size;
+using gl::GLContextEGL;
+
+/* static */
+already_AddRefed<NativeLayerRootWayland>
+NativeLayerRootWayland::CreateForMozContainer(MozContainer* aContainer) {
+  RefPtr<NativeLayerRootWayland> layerRoot =
+      new NativeLayerRootWayland(aContainer);
+  return layerRoot.forget();
+}
+
+NativeLayerRootWayland::NativeLayerRootWayland(MozContainer* aContainer)
+    : mMutex("NativeLayerRootWayland"), mContainer(aContainer) {}
+
+void NativeLayerRootWayland::EnsureSurfaceInitialized() {
+  MutexAutoLock lock(mMutex);
+
+  if (mInitialized) {
+    return;
+  }
+
+  mEGLWindow = moz_container_wayland_get_egl_window(mContainer, 1);
+  if (!mEGLWindow) {
+    return;
+  }
+
+  moz_container_wayland_egl_window_set_size(mContainer, 1, 1);
+  wp_viewport* viewporter = moz_container_wayland_get_viewport(mContainer);
+  wp_viewport_set_source(viewporter, wl_fixed_from_int(0), wl_fixed_from_int(0),
+                         wl_fixed_from_int(1), wl_fixed_from_int(1));
+  wp_viewport_set_destination(viewporter, 1, 1);
+
+  // TODO: use shm-buffer instead of GL
+  GLContextEGL* gl = GLContextEGL::Cast(wr::RenderThread::Get()->SingletonGL());
+  auto egl = gl->mEgl;
+
+  mEGLSurface = egl->fCreateWindowSurface(gl->mConfig, mEGLWindow, nullptr);
+  MOZ_ASSERT(mEGLSurface != EGL_NO_SURFACE);
+
+  gl->SetEGLSurfaceOverride(mEGLSurface);
+  gl->MakeCurrent();
+
+  gl->fClearColor(0.f, 0.f, 0.f, 0.f);
+  gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
+
+  egl->fSwapInterval(0);
+  egl->fSwapBuffers(mEGLSurface);
+
+  gl->SetEGLSurfaceOverride(nullptr);
+  gl->MakeCurrent();
+
+  mInitialized = true;
+}
+
+already_AddRefed<NativeLayer> NativeLayerRootWayland::CreateLayer(
+    const IntSize& aSize, bool aIsOpaque,
+    SurfacePoolHandle* aSurfacePoolHandle) {
+  RefPtr<NativeLayer> layer = new NativeLayerWayland(
+      aSize, aIsOpaque, aSurfacePoolHandle->AsSurfacePoolHandleWayland());
+  return layer.forget();
+}
+
+already_AddRefed<NativeLayer>
+NativeLayerRootWayland::CreateLayerForExternalTexture(bool aIsOpaque) {
+  RefPtr<NativeLayer> layer = new NativeLayerWayland(aIsOpaque);
+  return layer.forget();
+}
+
+void NativeLayerRootWayland::AppendLayer(NativeLayer* aLayer) {
+  MOZ_ASSERT(false);
+  MutexAutoLock lock(mMutex);
+
+  RefPtr<NativeLayerWayland> layerWayland = aLayer->AsNativeLayerWayland();
+  MOZ_RELEASE_ASSERT(layerWayland);
+
+  mSublayers.AppendElement(layerWayland);
+  layerWayland->SetBackingScale(mBackingScale);
+}
+
+void NativeLayerRootWayland::RemoveLayer(NativeLayer* aLayer) {
+  MOZ_ASSERT(false);
+  MutexAutoLock lock(mMutex);
+
+  RefPtr<NativeLayerWayland> layerWayland = aLayer->AsNativeLayerWayland();
+  MOZ_RELEASE_ASSERT(layerWayland);
+
+  mSublayers.RemoveElement(layerWayland);
+}
+
+void NativeLayerRootWayland::EnsureShowLayer(
+    const RefPtr<NativeLayerWayland>& aLayer) {
+  if (aLayer->mIsShown) {
+    return;
+  }
+
+  RefPtr<NativeSurfaceWayland> nativeSurface = aLayer->mNativeSurface;
+  if (!nativeSurface->mWlSubsurface) {
+    wl_surface* wlSurface = moz_container_wayland_surface_lock(mContainer);
+    wl_subcompositor* subcompositor =
+        widget::WaylandDisplayGet()->GetSubcompositor();
+
+    nativeSurface->mWlSubsurface = wl_subcompositor_get_subsurface(
+        subcompositor, nativeSurface->mWlSurface, wlSurface);
+
+    moz_container_wayland_surface_unlock(mContainer, &wlSurface);
+  }
+
+  aLayer->mIsShown = true;
+}
+
+void NativeLayerRootWayland::EnsureHideLayer(
+    const RefPtr<NativeLayerWayland>& aLayer) {
+  if (!aLayer->mIsShown) {
+    return;
+  }
+
+  RefPtr<NativeSurfaceWayland> nativeSurface = aLayer->mNativeSurface;
+
+  wl_subsurface_set_position(nativeSurface->mWlSubsurface, 20, 20);
+  wp_viewport_set_source(nativeSurface->mViewport, wl_fixed_from_int(0),
+                         wl_fixed_from_int(0), wl_fixed_from_int(1),
+                         wl_fixed_from_int(1));
+  wp_viewport_set_destination(nativeSurface->mViewport, 1, 1);
+  wl_surface_commit(nativeSurface->mWlSurface);
+
+  wl_surface* wlSurface = moz_container_wayland_surface_lock(mContainer);
+  wl_subsurface_place_below(nativeSurface->mWlSubsurface, wlSurface);
+  moz_container_wayland_surface_unlock(mContainer, &wlSurface);
+
+  aLayer->mIsShown = false;
+}
+
+void NativeLayerRootWayland::UnmapLayer(
+    const RefPtr<NativeLayerWayland>& aLayer) {
+  RefPtr<NativeSurfaceWayland> nativeSurface = aLayer->mNativeSurface;
+  g_clear_pointer(&nativeSurface->mWlSubsurface, wl_subsurface_destroy);
+  aLayer->mIsShown = false;
+}
+
+void NativeLayerRootWayland::SetLayers(
+    const nsTArray<RefPtr<NativeLayer>>& aLayers) {
+  MutexAutoLock lock(mMutex);
+
+  // Ideally, we'd just be able to do mSublayers = std::move(aLayers).
+  // However, aLayers has a different type: it carries NativeLayer objects,
+  // whereas mSublayers carries NativeLayerWayland objects, so we have to
+  // downcast all the elements first. There's one other reason to look at all
+  // the elements in aLayers first: We need to make sure any new layers know
+  // about our current backing scale.
+
+  nsTArray<RefPtr<NativeLayerWayland>> newSublayers(aLayers.Length());
+  for (const RefPtr<NativeLayer>& layer : aLayers) {
+    RefPtr<NativeLayerWayland> layerWayland = layer->AsNativeLayerWayland();
+    MOZ_RELEASE_ASSERT(layerWayland);
+    layerWayland->SetBackingScale(mBackingScale);
+    newSublayers.AppendElement(std::move(layerWayland));
+  }
+
+  if (newSublayers != mSublayers) {
+    for (const RefPtr<NativeLayerWayland>& layer : mSublayers) {
+      if (!newSublayers.Contains(layer)) {
+        EnsureHideLayer(layer);
+      }
+    }
+    mSublayers = std::move(newSublayers);
+  }
+
+  wl_surface* previousSurface = nullptr;
+  for (const RefPtr<NativeLayerWayland>& layer : mSublayers) {
+    RefPtr<NativeSurfaceWayland> nativeSurface = layer->mNativeSurface;
+
+    Rect surfaceRectClipped =
+        Rect(0, 0, (float)layer->mSize.width, (float)layer->mSize.height);
+    surfaceRectClipped = surfaceRectClipped.Intersect(Rect(layer->mValidRect));
+
+    Point relPosition = layer->mTransform.TransformPoint(Point(0, 0));
+    Point absPosition = Point((float)layer->mPosition.x + relPosition.x,
+                              (float)layer->mPosition.y + relPosition.y);
+
+    Point scaledSize =
+        layer->mTransform.TransformPoint(
+            Point((float)layer->mSize.width, (float)layer->mSize.height)) -
+        relPosition;
+    float scaleX = scaledSize.x / (float)layer->mSize.width;
+    float scaleY = scaledSize.y / (float)layer->mSize.height;
+
+    surfaceRectClipped.x += absPosition.x;
+    surfaceRectClipped.y += absPosition.y;
+    surfaceRectClipped.width *= scaleX;
+    surfaceRectClipped.height *= scaleY;
+
+    if (layer->mClipRect) {
+      surfaceRectClipped =
+          surfaceRectClipped.Intersect(Rect(layer->mClipRect.value()));
+    }
+
+    if (roundf(surfaceRectClipped.width) > 0 &&
+        roundf(surfaceRectClipped.height) > 0) {
+      EnsureShowLayer(layer);
+    } else {
+      EnsureHideLayer(layer);
+      continue;
+    }
+
+    double scale = moz_container_wayland_get_scale(mContainer);
+    wl_subsurface_set_position(nativeSurface->mWlSubsurface,
+                               floor(surfaceRectClipped.x / scale),
+                               floor(surfaceRectClipped.y / scale));
+    wp_viewport_set_destination(nativeSurface->mViewport,
+                                ceil(surfaceRectClipped.width / scale),
+                                ceil(surfaceRectClipped.height / scale));
+
+    Rect bufferClip = Rect(surfaceRectClipped.x - absPosition.x,
+                           surfaceRectClipped.y - absPosition.y,
+                           surfaceRectClipped.width, surfaceRectClipped.height);
+
+    bufferClip.x /= scaleX;
+    bufferClip.y /= scaleY;
+    bufferClip.width /= scaleX;
+    bufferClip.height /= scaleY;
+
+    // WR uses top-left coordinates but our buffers are in bottom-left ones
+    if (layer->SurfaceIsFlipped()) {
+      wl_surface_set_buffer_transform(nativeSurface->mWlSurface,
+                                      WL_OUTPUT_TRANSFORM_NORMAL);
+    } else {
+      wl_surface_set_buffer_transform(nativeSurface->mWlSurface,
+                                      WL_OUTPUT_TRANSFORM_FLIPPED_180);
+    }
+
+    wp_viewport_set_source(nativeSurface->mViewport,
+                           wl_fixed_from_double(bufferClip.x),
+                           wl_fixed_from_double(bufferClip.y),
+                           wl_fixed_from_double(bufferClip.width),
+                           wl_fixed_from_double(bufferClip.height));
+
+    wl_compositor* compositor = widget::WaylandDisplayGet()->GetCompositor();
+    struct wl_region* region = wl_compositor_create_region(compositor);
+    if (layer->mIsOpaque &&
+        StaticPrefs::widget_wayland_opaque_region_enabled_AtStartup()) {
+      wl_region_add(region, 0, 0, INT32_MAX, INT32_MAX);
+    }
+    wl_surface_set_opaque_region(layer->mNativeSurface->mWlSurface, region);
+    wl_region_destroy(region);
+
+    if (previousSurface) {
+      wl_subsurface_place_above(nativeSurface->mWlSubsurface, previousSurface);
+      previousSurface = nativeSurface->mWlSurface;
+    } else {
+      wl_surface* wlSurface = moz_container_wayland_surface_lock(mContainer);
+      wl_subsurface_place_above(nativeSurface->mWlSubsurface, wlSurface);
+      moz_container_wayland_surface_unlock(mContainer, &wlSurface);
+      previousSurface = nativeSurface->mWlSurface;
+    }
+  }
+}
+
+void NativeLayerRootWayland::SetBackingScale(float aBackingScale) {
+  MutexAutoLock lock(mMutex);
+
+  mBackingScale = aBackingScale;
+  for (const RefPtr<NativeLayerWayland>& layer : mSublayers) {
+    layer->SetBackingScale(aBackingScale);
+  }
+}
+
+float NativeLayerRootWayland::BackingScale() {
+  MutexAutoLock lock(mMutex);
+  return mBackingScale;
+}
+
+bool NativeLayerRootWayland::CommitToScreen() {
+  {
+    MutexAutoLock lock(mMutex);
+
+    wl_surface* wlSurface = moz_container_wayland_surface_lock(mContainer);
+    for (RefPtr<NativeLayerWayland>& layer : mSublayers) {
+      RefPtr<NativeSurfaceWayland> nativeSurface = layer->mNativeSurface;
+      if (!layer->mDirtyRegion.IsEmpty()) {
+        GLContextEGL* gl = GLContextEGL::Cast(layer->mSurfacePoolHandle->gl());
+        auto egl = gl->mEgl;
+
+        gl->SetEGLSurfaceOverride(nativeSurface->GetEGLSurface());
+        gl->MakeCurrent();
+
+        egl->fSwapInterval(0);
+        egl->fSwapBuffers(nativeSurface->GetEGLSurface());
+
+        gl->SetEGLSurfaceOverride(nullptr);
+        gl->MakeCurrent();
+
+        layer->mDirtyRegion.SetEmpty();
+      } else {
+        wl_surface_commit(nativeSurface->mWlSurface);
+      }
+    }
+
+    if (mInitialized) {
+      wl_surface_commit(wlSurface);
+    }
+    moz_container_wayland_surface_unlock(mContainer, &wlSurface);
+  }
+
+  EnsureSurfaceInitialized();
+  return true;
+}
+
+void NativeLayerRootWayland::PauseCompositor() {
+  MutexAutoLock lock(mMutex);
+
+  for (RefPtr<NativeLayerWayland>& layer : mSublayers) {
+    UnmapLayer(layer);
+  }
+
+  GLContextEGL* gl = GLContextEGL::Cast(wr::RenderThread::Get()->SingletonGL());
+  auto egl = gl->mEgl;
+  egl->fDestroySurface(mEGLSurface);
+  mEGLSurface = nullptr;
+  mEGLWindow = nullptr;
+  mInitialized = false;
+}
+
+bool NativeLayerRootWayland::ResumeCompositor() { return true; }
+
+UniquePtr<NativeLayerRootSnapshotter>
+NativeLayerRootWayland::CreateSnapshotter() {
+  MutexAutoLock lock(mMutex);
+  return nullptr;
+}
+
+NativeLayerWayland::NativeLayerWayland(
+    const IntSize& aSize, bool aIsOpaque,
+    SurfacePoolHandleWayland* aSurfacePoolHandle)
+    : mMutex("NativeLayerWayland"),
+      mSurfacePoolHandle(aSurfacePoolHandle),
+      mSize(aSize),
+      mIsOpaque(aIsOpaque),
+      mNativeSurface(nullptr) {
+  MOZ_RELEASE_ASSERT(mSurfacePoolHandle,
+                     "Need a non-null surface pool handle.");
+}
+
+NativeLayerWayland::NativeLayerWayland(bool aIsOpaque)
+    : mMutex("NativeLayerWayland"),
+      mSurfacePoolHandle(nullptr),
+      mIsOpaque(aIsOpaque) {
+  MOZ_ASSERT(false);  // external image
+}
+
+NativeLayerWayland::~NativeLayerWayland() {
+  if (mNativeSurface) {
+    mSurfacePoolHandle->ReturnSurfaceToPool(mNativeSurface);
+  }
+}
+
+void NativeLayerWayland::AttachExternalImage(
+    wr::RenderTextureHost* aExternalImage) {
+  MOZ_ASSERT(false);
+}
+
+void NativeLayerWayland::SetSurfaceIsFlipped(bool aIsFlipped) {
+  MutexAutoLock lock(mMutex);
+
+  if (aIsFlipped != mSurfaceIsFlipped) {
+    mSurfaceIsFlipped = aIsFlipped;
+  }
+}
+
+bool NativeLayerWayland::SurfaceIsFlipped() {
+  MutexAutoLock lock(mMutex);
+
+  return mSurfaceIsFlipped;
+}
+
+IntSize NativeLayerWayland::GetSize() {
+  MutexAutoLock lock(mMutex);
+  return mSize;
+}
+
+void NativeLayerWayland::SetPosition(const IntPoint& aPosition) {
+  MutexAutoLock lock(mMutex);
+
+  if (aPosition != mPosition) {
+    mPosition = aPosition;
+  }
+}
+
+IntPoint NativeLayerWayland::GetPosition() {
+  MutexAutoLock lock(mMutex);
+  return mPosition;
+}
+
+void NativeLayerWayland::SetTransform(const Matrix4x4& aTransform) {
+  MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(aTransform.IsRectilinear());
+
+  if (aTransform != mTransform) {
+    mTransform = aTransform;
+  }
+}
+
+void NativeLayerWayland::SetSamplingFilter(
+    gfx::SamplingFilter aSamplingFilter) {
+  MutexAutoLock lock(mMutex);
+
+  if (aSamplingFilter != mSamplingFilter) {
+    mSamplingFilter = aSamplingFilter;
+  }
+}
+
+Matrix4x4 NativeLayerWayland::GetTransform() {
+  MutexAutoLock lock(mMutex);
+  return mTransform;
+}
+
+IntRect NativeLayerWayland::GetRect() {
+  MutexAutoLock lock(mMutex);
+  return IntRect(mPosition, mSize);
+}
+
+void NativeLayerWayland::SetBackingScale(float aBackingScale) {
+  MutexAutoLock lock(mMutex);
+
+  if (aBackingScale != mBackingScale) {
+    mBackingScale = aBackingScale;
+  }
+}
+
+bool NativeLayerWayland::IsOpaque() {
+  MutexAutoLock lock(mMutex);
+  return mIsOpaque;
+}
+
+void NativeLayerWayland::SetClipRect(const Maybe<gfx::IntRect>& aClipRect) {
+  MutexAutoLock lock(mMutex);
+
+  if (aClipRect != mClipRect) {
+    mClipRect = aClipRect;
+  }
+}
+
+Maybe<gfx::IntRect> NativeLayerWayland::ClipRect() {
+  MutexAutoLock lock(mMutex);
+  return mClipRect;
+}
+
+gfx::IntRect NativeLayerWayland::CurrentSurfaceDisplayRect() {
+  MutexAutoLock lock(mMutex);
+  return mDisplayRect;
+}
+
+RefPtr<gfx::DrawTarget> NativeLayerWayland::NextSurfaceAsDrawTarget(
+    const IntRect& aDisplayRect, const IntRegion& aUpdateRegion,
+    gfx::BackendType aBackendType) {
+  MOZ_ASSERT(false);
+  return nullptr;
+}
+
+Maybe<GLuint> NativeLayerWayland::NextSurfaceAsFramebuffer(
+    const IntRect& aDisplayRect, const IntRegion& aUpdateRegion,
+    bool aNeedsDepth) {
+  MutexAutoLock lock(mMutex);
+
+  mValidRect = IntRect(aDisplayRect);
+  mDirtyRegion = IntRegion(aUpdateRegion);
+
+  MOZ_ASSERT(!mSize.IsEmpty());
+  if (!mNativeSurface) {
+    mNativeSurface = mSurfacePoolHandle->ObtainSurfaceFromPool(mSize);
+  }
+  GLContextEGL* gl = GLContextEGL::Cast(mSurfacePoolHandle->gl());
+
+  gl->SetEGLSurfaceOverride(mNativeSurface->GetEGLSurface());
+  gl->MakeCurrent();
+
+  return Some(0);
+}
+
+void NativeLayerWayland::NotifySurfaceReady() {
+  MutexAutoLock lock(mMutex);
+
+  GLContextEGL* gl = GLContextEGL::Cast(mSurfacePoolHandle->gl());
+  gl->SetEGLSurfaceOverride(nullptr);
+  gl->MakeCurrent();
+}
+
+void NativeLayerWayland::DiscardBackbuffers() {}
+
+}  // namespace mozilla::layers
new file mode 100644
--- /dev/null
+++ b/gfx/layers/NativeLayerWayland.h
@@ -0,0 +1,137 @@
+/* -*- 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/. */
+
+#ifndef mozilla_layers_NativeLayerWayland_h
+#define mozilla_layers_NativeLayerWayland_h
+
+#include <deque>
+#include <unordered_map>
+
+#include "mozilla/Mutex.h"
+
+#include "mozilla/layers/NativeLayer.h"
+#include "mozilla/layers/SurfacePoolWayland.h"
+#include "mozilla/widget/MozContainerWayland.h"
+#include "nsRegion.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+
+namespace gl {
+class GLContextEGL;
+}  // namespace gl
+
+namespace layers {
+
+class NativeLayerRootWayland : public NativeLayerRoot {
+ public:
+  static already_AddRefed<NativeLayerRootWayland> CreateForMozContainer(
+      MozContainer* aContainer);
+
+  // Overridden methods
+  already_AddRefed<NativeLayer> CreateLayer(
+      const gfx::IntSize& aSize, bool aIsOpaque,
+      SurfacePoolHandle* aSurfacePoolHandle) override;
+  void AppendLayer(NativeLayer* aLayer) override;
+  void RemoveLayer(NativeLayer* aLayer) override;
+  void SetLayers(const nsTArray<RefPtr<NativeLayer>>& aLayers) override;
+  UniquePtr<NativeLayerRootSnapshotter> CreateSnapshotter() override;
+  bool CommitToScreen() override;
+
+  // When the compositor is paused the wl_surface of the MozContainer will
+  // get destroyed. We thus have to recreate subsurface relationships for
+  // all tiles after resume. This is a implementation specific quirk of
+  // our GTK-Wayland backend.
+  void PauseCompositor() override;
+  bool ResumeCompositor() override;
+
+  void SetBackingScale(float aBackingScale);
+  float BackingScale();
+
+  already_AddRefed<NativeLayer> CreateLayerForExternalTexture(
+      bool aIsOpaque) override;
+
+ protected:
+  explicit NativeLayerRootWayland(MozContainer* aContainer);
+  ~NativeLayerRootWayland() = default;
+
+  void EnsureSurfaceInitialized();
+  void EnsureShowLayer(const RefPtr<NativeLayerWayland>& aLayer);
+  void EnsureHideLayer(const RefPtr<NativeLayerWayland>& aLayer);
+  void UnmapLayer(const RefPtr<NativeLayerWayland>& aLayer);
+
+  Mutex mMutex;
+
+  nsTArray<RefPtr<NativeLayerWayland>> mSublayers;
+  float mBackingScale = 1.0f;
+  MozContainer* mContainer = nullptr;
+  bool mInitialized = false;
+  wl_egl_window* mEGLWindow = nullptr;
+  EGLSurface mEGLSurface = nullptr;
+  struct wp_viewport* mViewport = nullptr;
+};
+
+class NativeLayerWayland : public NativeLayer {
+ public:
+  virtual NativeLayerWayland* AsNativeLayerWayland() override { return this; }
+
+  // Overridden methods
+  gfx::IntSize GetSize() override;
+  void SetPosition(const gfx::IntPoint& aPosition) override;
+  gfx::IntPoint GetPosition() override;
+  void SetTransform(const gfx::Matrix4x4& aTransform) override;
+  gfx::Matrix4x4 GetTransform() override;
+  gfx::IntRect GetRect() override;
+  void SetSamplingFilter(gfx::SamplingFilter aSamplingFilter) override;
+  RefPtr<gfx::DrawTarget> NextSurfaceAsDrawTarget(
+      const gfx::IntRect& aDisplayRect, const gfx::IntRegion& aUpdateRegion,
+      gfx::BackendType aBackendType) override;
+  Maybe<GLuint> NextSurfaceAsFramebuffer(const gfx::IntRect& aDisplayRect,
+                                         const gfx::IntRegion& aUpdateRegion,
+                                         bool aNeedsDepth) override;
+  void NotifySurfaceReady() override;
+  void DiscardBackbuffers() override;
+  bool IsOpaque() override;
+  void SetClipRect(const Maybe<gfx::IntRect>& aClipRect) override;
+  Maybe<gfx::IntRect> ClipRect() override;
+  gfx::IntRect CurrentSurfaceDisplayRect() override;
+  void SetSurfaceIsFlipped(bool aIsFlipped) override;
+  bool SurfaceIsFlipped() override;
+
+  void AttachExternalImage(wr::RenderTextureHost* aExternalImage) override;
+
+  void SetBackingScale(float aBackingScale);
+
+ protected:
+  friend class NativeLayerRootWayland;
+
+  NativeLayerWayland(const gfx::IntSize& aSize, bool aIsOpaque,
+                     SurfacePoolHandleWayland* aSurfacePoolHandle);
+  explicit NativeLayerWayland(bool aIsOpaque);
+  ~NativeLayerWayland() override;
+
+  Mutex mMutex;
+
+  RefPtr<SurfacePoolHandleWayland> mSurfacePoolHandle;
+  gfx::IntPoint mPosition;
+  gfx::Matrix4x4 mTransform;
+  gfx::IntRect mDisplayRect;
+  gfx::IntRect mValidRect;
+  gfx::IntRegion mDirtyRegion;
+  gfx::IntSize mSize;
+  Maybe<gfx::IntRect> mClipRect;
+  gfx::SamplingFilter mSamplingFilter = gfx::SamplingFilter::POINT;
+  float mBackingScale = 1.0f;
+  bool mSurfaceIsFlipped = false;
+  const bool mIsOpaque = false;
+  bool mIsShown = false;
+
+  RefPtr<NativeSurfaceWayland> mNativeSurface;
+};
+
+}  // namespace layers
+}  // namespace mozilla
+
+#endif  // mozilla_layers_NativeLayerWayland_h
--- a/gfx/layers/SurfacePool.h
+++ b/gfx/layers/SurfacePool.h
@@ -26,40 +26,44 @@ class SurfacePoolHandle;
 // A pool of surfaces for NativeLayers. Manages GL resources. Since GLContexts
 // are bound to their creator thread, a pool should not be shared across
 // threads. Call Create() to create an instance. Call GetHandleForGL() to obtain
 // a handle that can be passed to NativeLayerRoot::CreateLayer.
 class SurfacePool {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SurfacePool);
 
-#ifdef XP_MACOSX
+#if defined(XP_MACOSX) || defined(MOZ_WAYLAND)
   static RefPtr<SurfacePool> Create(size_t aPoolSizeLimit);
 #endif
 
   // aGL can be nullptr.
   virtual RefPtr<SurfacePoolHandle> GetHandleForGL(gl::GLContext* aGL) = 0;
   virtual void DestroyGLResourcesForContext(gl::GLContext* aGL) = 0;
 
  protected:
   virtual ~SurfacePool() = default;
 };
 
 class SurfacePoolHandleCA;
+class SurfacePoolHandleWayland;
 
 // A handle to the process-wide surface pool. Users should create one handle per
 // OS window, and call OnBeginFrame() and OnEndFrame() on the handle at
 // appropriate times. OnBeginFrame() and OnEndFrame() should be called on the
 // thread that the surface pool was created on.
 // These handles are stored on NativeLayers that are created with them and keep
 // the SurfacePool alive.
 class SurfacePoolHandle {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SurfacePoolHandle);
   virtual SurfacePoolHandleCA* AsSurfacePoolHandleCA() { return nullptr; }
+  virtual SurfacePoolHandleWayland* AsSurfacePoolHandleWayland() {
+    return nullptr;
+  }
 
   virtual RefPtr<SurfacePool> Pool() = 0;
 
   // Should be called every frame, in order to do rate-limited cleanup tasks.
   virtual void OnBeginFrame() = 0;
   virtual void OnEndFrame() = 0;
 
  protected:
new file mode 100644
--- /dev/null
+++ b/gfx/layers/SurfacePoolWayland.cpp
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nullptr; 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 "mozilla/layers/SurfacePoolWayland.h"
+
+#include "GLContextEGL.h"
+
+namespace mozilla::layers {
+
+using gl::GLContext;
+using gl::GLContextEGL;
+
+NativeSurfaceWayland::NativeSurfaceWayland(const gfx::IntSize& aSize,
+                                           GLContext* aGL)
+    : mGL(aGL) {
+  wl_compositor* compositor = widget::WaylandDisplayGet()->GetCompositor();
+  mWlSurface = wl_compositor_create_surface(compositor);
+
+  wl_region* region = wl_compositor_create_region(compositor);
+  wl_surface_set_input_region(mWlSurface, region);
+  wl_region_destroy(region);
+
+  wp_viewporter* viewporter = widget::WaylandDisplayGet()->GetViewporter();
+  MOZ_ASSERT(viewporter);
+  mViewport = wp_viewporter_get_viewport(viewporter, mWlSurface);
+
+  mEGLWindow = wl_egl_window_create(mWlSurface, aSize.width, aSize.height);
+
+  GLContextEGL* egl = GLContextEGL::Cast(mGL);
+  mEGLSurface =
+      egl->mEgl->fCreateWindowSurface(egl->mConfig, mEGLWindow, nullptr);
+  MOZ_ASSERT(mEGLSurface != EGL_NO_SURFACE);
+}
+
+NativeSurfaceWayland::~NativeSurfaceWayland() {
+  GLContextEGL* egl = GLContextEGL::Cast(mGL);
+  egl->mEgl->fDestroySurface(mEGLSurface);
+  g_clear_pointer(&mEGLWindow, wl_egl_window_destroy);
+  g_clear_pointer(&mViewport, wp_viewport_destroy);
+  g_clear_pointer(&mWlSurface, wl_surface_destroy);
+}
+
+/* static */ RefPtr<SurfacePool> SurfacePool::Create(size_t aPoolSizeLimit) {
+  return new SurfacePoolWayland(aPoolSizeLimit);
+}
+
+SurfacePoolWayland::SurfacePoolWayland(size_t aPoolSizeLimit)
+    : mPoolSizeLimit(aPoolSizeLimit) {}
+
+RefPtr<SurfacePoolHandle> SurfacePoolWayland::GetHandleForGL(GLContext* aGL) {
+  return new SurfacePoolHandleWayland(this, aGL);
+}
+
+void SurfacePoolWayland::DestroyGLResourcesForContext(GLContext* aGL) {
+  // Assume a single shared GL context for now
+  MOZ_ASSERT(mInUseEntries.empty());
+  mAvailableEntries.Clear();
+}
+
+bool SurfacePoolWayland::CanRecycleSurfaceForRequest(
+    const SurfacePoolEntry& aEntry, const gfx::IntSize& aSize, GLContext* aGL) {
+  return aEntry.mSize == aSize;
+}
+
+RefPtr<NativeSurfaceWayland> SurfacePoolWayland::ObtainSurfaceFromPool(
+    const gfx::IntSize& aSize, GLContext* aGL) {
+  auto iterToRecycle =
+      std::find_if(mAvailableEntries.begin(), mAvailableEntries.end(),
+                   [&](const SurfacePoolEntry& aEntry) {
+                     return CanRecycleSurfaceForRequest(aEntry, aSize, aGL);
+                   });
+  if (iterToRecycle != mAvailableEntries.end()) {
+    RefPtr<NativeSurfaceWayland> surface = iterToRecycle->mNativeSurface;
+    mInUseEntries.insert({surface.get(), std::move(*iterToRecycle)});
+    mAvailableEntries.RemoveElementAt(iterToRecycle);
+    return surface;
+  }
+
+  RefPtr<NativeSurfaceWayland> surface = new NativeSurfaceWayland(aSize, aGL);
+  mInUseEntries.insert({surface.get(), SurfacePoolEntry{aSize, surface}});
+
+  return surface;
+}
+
+void SurfacePoolWayland::ReturnSurfaceToPool(
+    const RefPtr<NativeSurfaceWayland>& aSurface) {
+  auto inUseEntryIter = mInUseEntries.find(aSurface);
+  MOZ_RELEASE_ASSERT(inUseEntryIter != mInUseEntries.end());
+
+  mAvailableEntries.AppendElement(std::move(inUseEntryIter->second));
+  mInUseEntries.erase(inUseEntryIter);
+
+  g_clear_pointer(&aSurface->mWlSubsurface, wl_subsurface_destroy);
+}
+
+void SurfacePoolWayland::EnforcePoolSizeLimit() {
+  // Enforce the pool size limit, removing least-recently-used entries as
+  // necessary.
+  while (mAvailableEntries.Length() > mPoolSizeLimit) {
+    mAvailableEntries.RemoveElementAt(0);
+  }
+}
+
+SurfacePoolHandleWayland::SurfacePoolHandleWayland(
+    RefPtr<SurfacePoolWayland> aPool, GLContext* aGL)
+    : mPool(std::move(aPool)), mGL(aGL) {}
+
+void SurfacePoolHandleWayland::OnBeginFrame() {}
+
+void SurfacePoolHandleWayland::OnEndFrame() { mPool->EnforcePoolSizeLimit(); }
+
+RefPtr<NativeSurfaceWayland> SurfacePoolHandleWayland::ObtainSurfaceFromPool(
+    const gfx::IntSize& aSize) {
+  return mPool->ObtainSurfaceFromPool(aSize, mGL);
+}
+
+void SurfacePoolHandleWayland::ReturnSurfaceToPool(
+    const RefPtr<NativeSurfaceWayland>& aSurface) {
+  mPool->ReturnSurfaceToPool(aSurface);
+}
+
+}  // namespace mozilla::layers
new file mode 100644
--- /dev/null
+++ b/gfx/layers/SurfacePoolWayland.h
@@ -0,0 +1,114 @@
+/* -*- 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/. */
+
+#ifndef mozilla_layers_SurfacePoolWayland_h
+#define mozilla_layers_SurfacePoolWayland_h
+
+#include <wayland-egl.h>
+
+#include "mozilla/layers/SurfacePool.h"
+#include "mozilla/widget/nsWaylandDisplay.h"
+
+namespace mozilla {
+
+namespace layers {
+
+class NativeSurfaceWayland {
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NativeSurfaceWayland);
+
+  EGLSurface GetEGLSurface() { return mEGLSurface; }
+
+  struct wl_surface* mWlSurface = nullptr;
+  struct wl_subsurface* mWlSubsurface = nullptr;
+  struct wp_viewport* mViewport = nullptr;
+
+ private:
+  friend class SurfacePoolWayland;
+  NativeSurfaceWayland(const gfx::IntSize& aSize, gl::GLContext* aGL);
+  ~NativeSurfaceWayland();
+
+  gl::GLContext* mGL = nullptr;
+  struct wl_egl_window* mEGLWindow = nullptr;
+  EGLSurface mEGLSurface = nullptr;
+};
+
+class SurfacePoolWayland final : public SurfacePool {
+ public:
+  // Get a handle for a new window. aGL can be nullptr.
+  RefPtr<SurfacePoolHandle> GetHandleForGL(gl::GLContext* aGL) override;
+
+  // Destroy all GL resources associated with aGL managed by this pool.
+  void DestroyGLResourcesForContext(gl::GLContext* aGL) override;
+
+ private:
+  friend class SurfacePoolHandleWayland;
+  friend RefPtr<SurfacePool> SurfacePool::Create(size_t aPoolSizeLimit);
+
+  explicit SurfacePoolWayland(size_t aPoolSizeLimit);
+
+  RefPtr<NativeSurfaceWayland> ObtainSurfaceFromPool(const gfx::IntSize& aSize,
+                                                     gl::GLContext* aGL);
+  void ReturnSurfaceToPool(const RefPtr<NativeSurfaceWayland>& aSurface);
+  void EnforcePoolSizeLimit();
+
+  struct SurfacePoolEntry {
+    gfx::IntSize mSize;
+    RefPtr<NativeSurfaceWayland> mNativeSurface;  // non-null
+  };
+
+  bool CanRecycleSurfaceForRequest(const SurfacePoolEntry& aEntry,
+                                   const gfx::IntSize& aSize,
+                                   gl::GLContext* aGL);
+
+  // Stores the entries for surfaces that are in use by NativeLayerWayland, i.e.
+  // an entry is inside mInUseEntries between calls to ObtainSurfaceFromPool()
+  // and ReturnSurfaceToPool().
+  std::unordered_map<NativeSurfaceWayland*, SurfacePoolEntry> mInUseEntries;
+
+  // Stores entries which are no longer in use by NativeLayerWayland but are
+  // still in use by the window server, i.e. for which
+  // WaylandSurfaceIsInUse(pendingSurfaceEntry.mEntry.mNativeSurface.get()) TODO
+  // still returns true. These entries are checked once per frame inside
+  // CollectPendingSurfaces(), and returned to mAvailableEntries once the
+  // window server is done.
+  // nsTArray<SurfacePoolEntry> mPendingEntries;
+
+  // Stores entries which are available for recycling. These entries are not
+  // in use by a NativeLayerWayland or by the window server.
+  nsTArray<SurfacePoolEntry> mAvailableEntries;
+
+  size_t mPoolSizeLimit;
+};
+
+// A surface pool handle that is stored on NativeLayerWayland and keeps the
+// SurfacePool alive.
+class SurfacePoolHandleWayland final : public SurfacePoolHandle {
+ public:
+  SurfacePoolHandleWayland* AsSurfacePoolHandleWayland() override {
+    return this;
+  }
+
+  RefPtr<NativeSurfaceWayland> ObtainSurfaceFromPool(const gfx::IntSize& aSize);
+  void ReturnSurfaceToPool(const RefPtr<NativeSurfaceWayland>& aSurface);
+  const auto& gl() { return mGL; }
+
+  RefPtr<SurfacePool> Pool() override { return mPool; }
+  void OnBeginFrame() override;
+  void OnEndFrame() override;
+
+ private:
+  friend class SurfacePoolWayland;
+  SurfacePoolHandleWayland(RefPtr<SurfacePoolWayland> aPool,
+                           gl::GLContext* aGL);
+
+  const RefPtr<SurfacePoolWayland> mPool;
+  const RefPtr<gl::GLContext> mGL;
+};
+
+}  // namespace layers
+}  // namespace mozilla
+
+#endif
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -293,18 +293,24 @@ if CONFIG["MOZ_X11"]:
         "composite/X11TextureHost.cpp",
         "ipc/ShadowLayerUtilsX11.cpp",
         "opengl/X11TextureSourceOGL.cpp",
     ]
 
 if CONFIG["MOZ_WAYLAND"]:
     EXPORTS.mozilla.layers += [
         "DMABUFSurfaceImage.h",
+        "NativeLayerWayland.h",
         "opengl/DMABUFTextureClientOGL.h",
         "opengl/DMABUFTextureHostOGL.h",
+        "SurfacePoolWayland.h",
+    ]
+    UNIFIED_SOURCES += [
+        "NativeLayerWayland.cpp",
+        "SurfacePoolWayland.cpp",
     ]
     SOURCES += [
         "DMABUFSurfaceImage.cpp",
         "opengl/DMABUFTextureClientOGL.cpp",
         "opengl/DMABUFTextureHostOGL.cpp",
     ]
 
 if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
--- a/gfx/thebes/gfxPlatformGtk.cpp
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -216,16 +216,44 @@ void gfxPlatformGtk::InitDmabufConfig() 
   }
 #else
   feature.DisableByDefault(FeatureStatus::Unavailable,
                            "Wayland support missing",
                            "FEATURE_FAILURE_NO_WAYLAND"_ns);
 #endif
 }
 
+void gfxPlatformGtk::InitWebRenderConfig() {
+  gfxPlatform::InitWebRenderConfig();
+
+  if (!XRE_IsParentProcess()) {
+    return;
+  }
+
+  FeatureState& feature = gfxConfig::GetFeature(Feature::WEBRENDER_COMPOSITOR);
+  if (feature.IsEnabled()) {
+    if (!gfxConfig::IsEnabled(Feature::WEBRENDER)) {
+      feature.ForceDisable(FeatureStatus::Unavailable, "WebRender disabled",
+                           "FEATURE_FAILURE_WR_DISABLED"_ns);
+    } else if (!IsWaylandDisplay()) {
+      feature.ForceDisable(FeatureStatus::Unavailable,
+                           "Wayland support missing",
+                           "FEATURE_FAILURE_NO_WAYLAND"_ns);
+    }
+#ifdef MOZ_WAYLAND
+    else if (!widget::WaylandDisplayGet()->GetViewporter()) {
+      feature.ForceDisable(FeatureStatus::Unavailable,
+                           "Requires wp_viewporter protocol support",
+                           "FEATURE_FAILURE_REQUIRES_WPVIEWPORTER"_ns);
+    }
+#endif
+  }
+  gfxVars::SetUseWebRenderCompositor(feature.IsEnabled());
+}
+
 void gfxPlatformGtk::FlushContentDrawing() {
   if (gfxVars::UseXRender()) {
     XFlush(DefaultXDisplay());
   }
 }
 
 void gfxPlatformGtk::InitPlatformGPUProcessPrefs() {
 #ifdef MOZ_WAYLAND
--- a/gfx/thebes/gfxPlatformGtk.h
+++ b/gfx/thebes/gfxPlatformGtk.h
@@ -83,16 +83,17 @@ class gfxPlatformGtk final : public gfxP
   bool IsWaylandDisplay() override {
     return !mIsX11Display && !gfxPlatform::IsHeadless();
   }
 
  protected:
   void InitX11EGLConfig();
   void InitDmabufConfig();
   void InitPlatformGPUProcessPrefs() override;
+  void InitWebRenderConfig() override;
   bool CheckVariationFontSupport() override;
 
   int8_t mMaxGenericSubstitutions;
 
  private:
   nsTArray<uint8_t> GetPlatformCMSOutputProfileData() override;
 
   bool mIsX11Display;
--- a/gfx/webrender_bindings/RenderCompositor.cpp
+++ b/gfx/webrender_bindings/RenderCompositor.cpp
@@ -17,20 +17,25 @@
 #include "mozilla/webrender/RenderCompositorOGL.h"
 #include "mozilla/webrender/RenderCompositorSWGL.h"
 #include "mozilla/widget/CompositorWidget.h"
 
 #ifdef XP_WIN
 #  include "mozilla/webrender/RenderCompositorANGLE.h"
 #endif
 
-#if defined(MOZ_WAYLAND) || defined(MOZ_WIDGET_ANDROID)
+#ifdef MOZ_WIDGET_ANDROID
 #  include "mozilla/webrender/RenderCompositorEGL.h"
 #endif
 
+#ifdef MOZ_WAYLAND
+#  include "mozilla/webrender/RenderCompositorEGL.h"
+#  include "mozilla/webrender/RenderCompositorNative.h"
+#endif
+
 #ifdef XP_MACOSX
 #  include "mozilla/webrender/RenderCompositorNative.h"
 #endif
 
 namespace mozilla::wr {
 
 void wr_compositor_add_surface(void* aCompositor, wr::NativeSurfaceId aId,
                                const wr::CompositorSurfaceTransform* aTransform,
@@ -172,16 +177,22 @@ UniquePtr<RenderCompositor> RenderCompos
   }
 
 #ifdef XP_WIN
   if (gfx::gfxVars::UseWebRenderANGLE()) {
     return RenderCompositorANGLE::Create(aWidget, aError);
   }
 #endif
 
+#if defined(MOZ_WAYLAND)
+  if (gfx::gfxVars::UseWebRenderCompositor()) {
+    return RenderCompositorNativeOGL::Create(aWidget, aError);
+  }
+#endif
+
 #if defined(MOZ_WAYLAND) || defined(MOZ_WIDGET_ANDROID)
   UniquePtr<RenderCompositor> eglCompositor =
       RenderCompositorEGL::Create(aWidget, aError);
   if (eglCompositor) {
     return eglCompositor;
   }
 #endif
 
--- a/gfx/webrender_bindings/RenderCompositorNative.cpp
+++ b/gfx/webrender_bindings/RenderCompositorNative.cpp
@@ -15,24 +15,23 @@
 #include "mozilla/layers/CompositionRecorder.h"
 #include "mozilla/layers/NativeLayer.h"
 #include "mozilla/layers/SurfacePool.h"
 #include "mozilla/StaticPrefs_gfx.h"
 #include "mozilla/webrender/RenderThread.h"
 #include "mozilla/widget/CompositorWidget.h"
 #include "RenderCompositorRecordedFrame.h"
 
-namespace mozilla {
-namespace wr {
+namespace mozilla::wr {
 
 RenderCompositorNative::RenderCompositorNative(
     const RefPtr<widget::CompositorWidget>& aWidget, gl::GLContext* aGL)
     : RenderCompositor(aWidget),
       mNativeLayerRoot(GetWidget()->GetNativeLayerRoot()) {
-#ifdef XP_MACOSX
+#if defined(XP_MACOSX) || defined(MOZ_WAYLAND)
   auto pool = RenderThread::Get()->SharedSurfacePool();
   if (pool) {
     mSurfacePoolHandle = pool->GetHandleForGL(aGL);
   }
 #endif
   MOZ_RELEASE_ASSERT(mSurfacePoolHandle);
 }
 
@@ -81,24 +80,30 @@ RenderedFrameId RenderCompositorNative::
   if (mNativeLayerForEntireWindow) {
     mNativeLayerForEntireWindow->NotifySurfaceReady();
     mNativeLayerRoot->CommitToScreen();
   }
 
   return frameId;
 }
 
-void RenderCompositorNative::Pause() {}
+void RenderCompositorNative::Pause() { mNativeLayerRoot->PauseCompositor(); }
 
-bool RenderCompositorNative::Resume() { return true; }
+bool RenderCompositorNative::Resume() {
+  return mNativeLayerRoot->ResumeCompositor();
+}
 
 inline layers::WebRenderCompositor RenderCompositorNative::CompositorType()
     const {
   if (gfx::gfxVars::UseWebRenderCompositor()) {
+#if defined(XP_MACOSX)
     return layers::WebRenderCompositor::CORE_ANIMATION;
+#elif defined(MOZ_WAYLAND)
+    return layers::WebRenderCompositor::WAYLAND;
+#endif
   }
   return layers::WebRenderCompositor::DRAW;
 }
 
 LayoutDeviceIntSize RenderCompositorNative::GetBufferSize() {
   return mWidget->GetClientSize();
 }
 
@@ -634,10 +639,9 @@ bool RenderCompositorNativeSWGL::MapTile
 
 void RenderCompositorNativeSWGL::UnmapTile() {
   if (!mNativeLayerForEntireWindow && mCurrentlyBoundNativeLayer) {
     UnmapNativeLayer();
     UnbindNativeLayer();
   }
 }
 
-}  // namespace wr
-}  // namespace mozilla
+}  // namespace mozilla::wr
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -960,17 +960,17 @@ RenderThread::GetProgramsForCompositorOG
   if (!mProgramsForCompositorOGL) {
     mProgramsForCompositorOGL =
         MakeAndAddRef<layers::ShaderProgramOGLsHolder>(mSingletonGL);
   }
   return mProgramsForCompositorOGL;
 }
 
 RefPtr<layers::SurfacePool> RenderThread::SharedSurfacePool() {
-#ifdef XP_MACOSX
+#if defined(XP_MACOSX) || defined(MOZ_WAYLAND)
   if (!mSurfacePool) {
     size_t poolSizeLimit =
         StaticPrefs::gfx_webrender_compositor_surface_pool_size_AtStartup();
     mSurfacePool = layers::SurfacePool::Create(poolSizeLimit);
   }
 #endif
   return mSurfacePool;
 }
--- a/gfx/webrender_bindings/moz.build
+++ b/gfx/webrender_bindings/moz.build
@@ -85,19 +85,21 @@ if CONFIG["MOZ_ENABLE_D3D10_LAYER"]:
     ]
     SOURCES += [
         "DCLayerTree.cpp",
         "RenderCompositorANGLE.cpp",
     ]
 
 if CONFIG["MOZ_WAYLAND"]:
     EXPORTS.mozilla.webrender += [
+        "RenderCompositorNative.h",
         "RenderDMABUFTextureHost.h",
     ]
     SOURCES += [
+        "RenderCompositorNative.cpp",
         "RenderDMABUFTextureHost.cpp",
     ]
 
 if CONFIG["MOZ_WIDGET_TOOLKIT"] in ("android", "gtk"):
     CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"]
     CXXFLAGS += CONFIG["CAIRO_FT_CFLAGS"]
 
 if CONFIG["COMPILE_ENVIRONMENT"]:
--- a/widget/gtk/GfxInfo.cpp
+++ b/widget/gtk/GfxInfo.cpp
@@ -765,16 +765,24 @@ const nsTArray<GfxDriverInfo>& GfxInfo::
         OperatingSystem::Linux, ScreenSizeStatus::SmallAndMedium,
         BatteryStatus::All, DesktopEnvironment::All, WindowProtocol::All,
         DriverVendor::All, DeviceFamily::All,
         nsIGfxInfo::FEATURE_WEBRENDER_SOFTWARE,
         nsIGfxInfo::FEATURE_ALLOW_ALWAYS, DRIVER_COMPARISON_IGNORED,
         V(0, 0, 0, 0), "FEATURE_ROLLOUT_S_M_SCRN_SOFTWARE_WR", "");
 
     ////////////////////////////////////
+    // FEATURE_WEBRENDER_COMPOSITOR
+    APPEND_TO_DRIVER_BLOCKLIST(
+        OperatingSystem::Linux, DeviceFamily::All,
+        nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR,
+        nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+        V(0, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_COMPOSITOR_DISABLED", "");
+
+    ////////////////////////////////////
     // FEATURE_X11_EGL
     APPEND_TO_DRIVER_BLOCKLIST(
         OperatingSystem::Linux, DeviceFamily::All, nsIGfxInfo::FEATURE_X11_EGL,
         nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
         V(0, 0, 0, 0), "FEATURE_FAILURE_X11_EGL_DISABLED", "");
 
     ////////////////////////////////////
 
--- a/widget/gtk/GtkCompositorWidget.cpp
+++ b/widget/gtk/GtkCompositorWidget.cpp
@@ -6,31 +6,36 @@
 #include "GtkCompositorWidget.h"
 
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/widget/InProcessCompositorWidget.h"
 #include "mozilla/widget/PlatformWidgetTypes.h"
 #include "nsWindow.h"
 #include "mozilla/X11Util.h"
 
+#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 (!aWindow) {
       NS_WARNING("GtkCompositorWidget: We're missing nsWindow!");
     }
     mProvider.Initialize(aWindow);
+    mNativeLayerRoot = nullptr;
   }
 #endif
 #if defined(MOZ_X11)
   if (aInitData.IsX11Display()) {
     mXWindow = (Window)aInitData.XWindow();
 
     // Grab the window's visual and depth
     XWindowAttributes windowAttrs;
@@ -119,10 +124,25 @@ LayoutDeviceIntRegion GtkCompositorWidge
     return LayoutDeviceIntRect(LayoutDeviceIntPoint(0, 0), GetClientSize());
   }
 
   // Clear background of titlebar area to render titlebar
   // transparent corners correctly.
   return mWidget->GetTitlebarRect();
 }
 
+#ifdef MOZ_WAYLAND
+RefPtr<mozilla::layers::NativeLayerRoot>
+GtkCompositorWidget::GetNativeLayerRoot() {
+  if (gfx::gfxVars::UseWebRenderCompositor()) {
+    if (!mNativeLayerRoot) {
+      MOZ_ASSERT(mWidget && mWidget->GetMozContainer());
+      mNativeLayerRoot = NativeLayerRootWayland::CreateForMozContainer(
+          mWidget->GetMozContainer());
+    }
+    return mNativeLayerRoot;
+  }
+  return nullptr;
+}
+#endif
+
 }  // namespace widget
 }  // namespace mozilla
--- a/widget/gtk/GtkCompositorWidget.h
+++ b/widget/gtk/GtkCompositorWidget.h
@@ -10,16 +10,21 @@
 #include "mozilla/DataMutex.h"
 #include "mozilla/widget/CompositorWidget.h"
 #include "WindowSurfaceProvider.h"
 
 class nsIWidget;
 class nsWindow;
 
 namespace mozilla {
+
+namespace layers {
+class NativeLayerRootWayland;
+}  // namespace layers
+
 namespace widget {
 
 class PlatformCompositorWidgetDelegate : public CompositorWidgetDelegate {
  public:
   virtual void NotifyClientSizeChanged(
       const LayoutDeviceIntSize& aClientSize) = 0;
 
   // CompositorWidgetDelegate Overrides
@@ -63,16 +68,17 @@ class GtkCompositorWidget : public Compo
 
   LayoutDeviceIntRegion GetTransparentRegion() override;
 
 #if defined(MOZ_X11)
   Window XWindow() const { return mXWindow; }
 #endif
 #if defined(MOZ_WAYLAND)
   void SetEGLNativeWindowSize(const LayoutDeviceIntSize& aEGLWindowSize);
+  RefPtr<mozilla::layers::NativeLayerRoot> GetNativeLayerRoot() override;
 #endif
 
   // PlatformCompositorWidgetDelegate Overrides
 
   void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override;
 
  protected:
   nsWindow* mWidget;
@@ -87,14 +93,18 @@ class GtkCompositorWidget : public Compo
   DataMutex<LayoutDeviceIntSize> mClientSize;
 
   WindowSurfaceProvider mProvider;
 
 #if defined(MOZ_X11)
   Window mXWindow = {};
 #endif
   int32_t mDepth = {};
+
+#ifdef MOZ_WAYLAND
+  RefPtr<mozilla::layers::NativeLayerRootWayland> mNativeLayerRoot;
+#endif
 };
 
 }  // namespace widget
 }  // namespace mozilla
 
 #endif  // widget_gtk_GtkCompositorWidget_h
--- a/widget/gtk/MozContainerWayland.cpp
+++ b/widget/gtk/MozContainerWayland.cpp
@@ -601,8 +601,28 @@ void moz_container_wayland_update_opaque
   if (moz_container_wayland_has_egl_window(container)) {
     moz_container_wayland_set_opaque_region(container);
   }
 }
 
 gboolean moz_container_wayland_can_draw(MozContainer* container) {
   return container ? container->wl_container.ready_to_draw : false;
 }
+
+double moz_container_wayland_get_scale(MozContainer* container) {
+  MozContainerWayland* wl_container = &container->wl_container;
+  MutexAutoLock lock(*wl_container->container_lock);
+
+  nsWindow* window = moz_container_get_nsWindow(container);
+  return window ? window->FractionalScaleFactor() : 1;
+}
+
+struct wp_viewport* moz_container_wayland_get_viewport(
+    MozContainer* container) {
+  MozContainerWayland* wl_container = &container->wl_container;
+  MutexAutoLock lock(*wl_container->container_lock);
+
+  if (!wl_container->viewport) {
+    wl_container->viewport = wp_viewporter_get_viewport(
+        WaylandDisplayGet()->GetViewporter(), wl_container->surface);
+  }
+  return wl_container->viewport;
+}
--- a/widget/gtk/MozContainerWayland.h
+++ b/widget/gtk/MozContainerWayland.h
@@ -73,10 +73,12 @@ void moz_container_wayland_egl_window_se
                                                int width, int height);
 void moz_container_wayland_set_scale_factor(MozContainer* container);
 void moz_container_wayland_add_initial_draw_callback(
     MozContainer* container, const std::function<void(void)>& initial_draw_cb);
 wl_surface* moz_gtk_widget_get_wl_surface(GtkWidget* aWidget);
 void moz_container_wayland_update_opaque_region(MozContainer* container,
                                                 bool aSubtractCorners);
 gboolean moz_container_wayland_can_draw(MozContainer* container);
+double moz_container_wayland_get_scale(MozContainer* container);
+struct wp_viewport* moz_container_wayland_get_viewport(MozContainer* container);
 
 #endif /* __MOZ_CONTAINER_WAYLAND_H__ */