Bug 1580152 - [Wayland] Fix rendering glitches on wayland , r=jhorak a=RyanVM
authorMartin Stransky <stransky@redhat.com>
Tue, 17 Sep 2019 12:34:28 +0000 (2019-09-17)
changeset 551935 3281a617f22b09aabf10f293ae12d8f9483f5bf0
parent 551934 ea623101ca501adf6142e32957d96b3bcfbd9e3f
child 551936 45c0e8a9df93f545bfacf07e5a78bd69559c6adf
push id12000
push userarchaeopteryx@coole-files.de
push dateThu, 19 Sep 2019 16:46:41 +0000 (2019-09-19)
treeherdermozilla-beta@45c0e8a9df93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjhorak, RyanVM
bugs1580152
milestone70.0
Bug 1580152 - [Wayland] Fix rendering glitches on wayland , r=jhorak a=RyanVM - Recently we're missing some drawings as we disabled flushing cached images in frame callback. Let's enable it again and make sure we don't flush the drawings between Lock()/Commit() compositor calls which is controlled by mBufferCommitAllowed. - When we draw directly to wl_buffer, flush all cached drawings we have or clear them if there's fullscreen update. It prevents potential rendering of cached images over unrelevant buffer content. - Flush cached images when wl_buffer is detached by wayland compositor. It allows to paint delayed drawings and ensures they won't stay in the queue infinitely. - Use mBufferPendingCommit to indicate that the WaylandBuffer contains updates from gecko which has not been submitted to wayland compositor yet. Allows delated commit handlers (frame callback, delayed commit and when wl_buffer is detached) to send WaylandBuffer content to wayland compositor. - Record time of last finished commit to mLastCommitTime and throws warning when wayland compositor does not release wl_buffer in 200ms. - Use wl_display_sync() to synchronize wl_display events. Wait for events from wl_display until all pending events are processed before we start drawing at WindowSurfaceWayland::Lock(). There may wl_buffer release event waiting which releases wl_buffer for rendering. - Don't use XMost()/YMost() to get drawing area size. - Remove mDisplayThreadMessageLoop as it's no longer used. Differential Revision: https://phabricator.services.mozilla.com/D45661
widget/gtk/WindowSurfaceWayland.cpp
widget/gtk/WindowSurfaceWayland.h
widget/gtk/mozwayland/mozwayland.h
widget/gtk/nsWaylandDisplay.cpp
widget/gtk/nsWaylandDisplay.h
--- a/widget/gtk/WindowSurfaceWayland.cpp
+++ b/widget/gtk/WindowSurfaceWayland.cpp
@@ -27,16 +27,19 @@
 #  include "Units.h"
 extern mozilla::LazyLogModule gWidgetWaylandLog;
 #  define LOGWAYLAND(args) \
     MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, args)
 #else
 #  define LOGWAYLAND(args)
 #endif /* MOZ_LOGGING */
 
+// Maximal compositing timeout it miliseconds
+#define COMPOSITING_TIMEOUT 200
+
 namespace mozilla {
 namespace widget {
 
 bool WindowSurfaceWayland::mUseDMABuf = false;
 bool WindowSurfaceWayland::mUseDMABufInitialized = false;
 
 /*
   Wayland multi-thread rendering scheme
@@ -193,16 +196,20 @@ available and widget.wayland_dmabuf_back
 
 */
 
 #define EVENT_LOOP_DELAY (1000 / 240)
 
 #define BUFFER_BPP 4
 gfx::SurfaceFormat WindowBackBuffer::mFormat = gfx::SurfaceFormat::B8G8R8A8;
 
+nsWaylandDisplay* WindowBackBuffer::GetWaylandDisplay() {
+  return mWindowSurfaceWayland->GetWaylandDisplay();
+}
+
 int WaylandShmPool::CreateTemporaryFile(int aSize) {
   const char* tmppath = getenv("XDG_RUNTIME_DIR");
   MOZ_RELEASE_ASSERT(tmppath, "Missing XDG_RUNTIME_DIR env variable.");
 
   nsPrintfCString tmpname("%s/mozilla-shared-XXXXXX", tmppath);
 
   char* filename;
   int fd = -1;
@@ -337,20 +344,21 @@ void WindowBackBufferShm::Release() {
   wl_buffer_destroy(mWaylandBuffer);
   mWidth = mHeight = 0;
 }
 
 void WindowBackBufferShm::Clear() {
   memset(mShmPool.GetImageData(), 0, mHeight * mWidth * BUFFER_BPP);
 }
 
-WindowBackBufferShm::WindowBackBufferShm(nsWaylandDisplay* aWaylandDisplay,
-                                         int aWidth, int aHeight)
-    : WindowBackBuffer(aWaylandDisplay),
-      mShmPool(aWaylandDisplay, aWidth * aHeight * BUFFER_BPP),
+WindowBackBufferShm::WindowBackBufferShm(
+    WindowSurfaceWayland* aWindowSurfaceWayland, int aWidth, int aHeight)
+    : WindowBackBuffer(aWindowSurfaceWayland),
+      mShmPool(aWindowSurfaceWayland->GetWaylandDisplay(),
+               aWidth * aHeight * BUFFER_BPP),
       mWaylandBuffer(nullptr),
       mWidth(aWidth),
       mHeight(aHeight),
       mAttached(false) {
   Create(aWidth, aHeight);
 }
 
 WindowBackBufferShm::~WindowBackBufferShm() { Release(); }
@@ -382,16 +390,19 @@ void WindowBackBuffer::Attach(wl_surface
 }
 
 void WindowBackBufferShm::Detach(wl_buffer* aBuffer) {
   LOGWAYLAND(("WindowBackBufferShm::Detach [%p] wl_buffer %p ID %d\n",
               (void*)this, (void*)aBuffer,
               aBuffer ? wl_proxy_get_id((struct wl_proxy*)aBuffer) : -1));
 
   mAttached = false;
+
+  // Commit any potential cached drawings from latest Lock()/Commit() cycle.
+  mWindowSurfaceWayland->CommitWaylandBuffer();
 }
 
 bool WindowBackBufferShm::SetImageDataFromBuffer(
     class WindowBackBuffer* aSourceBuffer) {
   auto sourceBuffer = static_cast<class WindowBackBufferShm*>(aSourceBuffer);
   if (!IsMatchingSize(sourceBuffer)) {
     Resize(sourceBuffer->mWidth, sourceBuffer->mHeight);
   }
@@ -411,18 +422,18 @@ already_AddRefed<gfx::DrawTarget> Window
   gfx::IntSize lockSize(mWidth, mHeight);
   mIsLocked = true;
   return gfxPlatform::CreateDrawTargetForData(
       static_cast<unsigned char*>(mShmPool.GetImageData()), lockSize,
       BUFFER_BPP * mWidth, GetSurfaceFormat());
 }
 
 WindowBackBufferDMABuf::WindowBackBufferDMABuf(
-    nsWaylandDisplay* aWaylandDisplay, int aWidth, int aHeight)
-    : WindowBackBuffer(aWaylandDisplay) {
+    WindowSurfaceWayland* aWindowSurfaceWayland, int aWidth, int aHeight)
+    : WindowBackBuffer(aWindowSurfaceWayland) {
   mDMAbufSurface.Create(aWidth, aHeight);
 
   LOGWAYLAND(
       ("WindowBackBufferDMABuf::WindowBackBufferDMABuf [%p] Created DMABuf "
        "buffer [%d x %d]\n",
        (void*)this, aWidth, aHeight));
 }
 
@@ -471,16 +482,19 @@ bool WindowBackBufferDMABuf::SetImageDat
   WindowBackBufferDMABuf* source =
       static_cast<WindowBackBufferDMABuf*>(aSourceBuffer);
   mDMAbufSurface.CopyFrom(&source->mDMAbufSurface);
   return true;
 }
 
 void WindowBackBufferDMABuf::Detach(wl_buffer* aBuffer) {
   mDMAbufSurface.WLBufferDetach();
+
+  // Commit any potential cached drawings from latest Lock()/Commit() cycle.
+  mWindowSurfaceWayland->CommitWaylandBuffer();
 }
 
 void WindowBackBufferDMABuf::Clear() { mDMAbufSurface.Clear(); }
 
 static void frame_callback_handler(void* data, struct wl_callback* callback,
                                    uint32_t time) {
   auto surface = reinterpret_cast<WindowSurfaceWayland*>(data);
   surface->FrameCallbackHandler();
@@ -492,31 +506,32 @@ static const struct wl_callback_listener
     frame_callback_handler};
 
 WindowSurfaceWayland::WindowSurfaceWayland(nsWindow* aWindow)
     : mWindow(aWindow),
       mWaylandDisplay(WaylandDisplayGet()),
       mWaylandBuffer(nullptr),
       mFrameCallback(nullptr),
       mLastCommittedSurface(nullptr),
-      mDisplayThreadMessageLoop(MessageLoop::current()),
       mDelayedCommitHandle(nullptr),
+      mLastCommitTime(0),
       mDrawToWaylandBufferDirectly(true),
-      mPendingCommit(false),
+      mBufferPendingCommit(false),
+      mBufferCommitAllowed(false),
       mWholeWindowBufferDamage(false),
       mBufferNeedsClear(false),
       mIsMainThread(NS_IsMainThread()),
       mNeedScaleFactorUpdate(true) {
   for (int i = 0; i < BACK_BUFFER_NUM; i++) mBackupBuffer[i] = nullptr;
   mRenderingCacheMode = static_cast<RenderingCacheMode>(
       mWaylandDisplay->GetRenderingCacheModePref());
 }
 
 WindowSurfaceWayland::~WindowSurfaceWayland() {
-  if (mPendingCommit) {
+  if (mBufferPendingCommit) {
     NS_WARNING("Deleted WindowSurfaceWayland with a pending commit!");
   }
 
   if (mDelayedCommitHandle) {
     // Delete reference to this to prevent WaylandBufferDelayCommitHandler()
     // operate on released this. mDelayedCommitHandle itself will
     // be released at WaylandBufferDelayCommitHandler().
     *mDelayedCommitHandle = nullptr;
@@ -545,34 +560,34 @@ bool WindowSurfaceWayland::UseDMABufBack
   return mUseDMABuf;
 }
 
 WindowBackBuffer* WindowSurfaceWayland::CreateWaylandBuffer(int aWidth,
                                                             int aHeight) {
   if (UseDMABufBackend()) {
     static bool sDMABufBufferCreated = false;
     WindowBackBuffer* buffer =
-        new WindowBackBufferDMABuf(mWaylandDisplay, aWidth, aHeight);
+        new WindowBackBufferDMABuf(this, aWidth, aHeight);
     if (buffer) {
       sDMABufBufferCreated = true;
       return buffer;
     }
     // If this is the first failure and there's no dmabuf already active
     // we can safely fallback to Shm. Otherwise we can't mix DMAbuf and
     // SHM buffers so just fails now.
     if (sDMABufBufferCreated) {
       NS_WARNING("Failed to allocate DMABuf buffer!");
       return nullptr;
     } else {
       NS_WARNING("Wayland DMABuf failed, switched back to Shm backend!");
       mUseDMABuf = false;
     }
   }
 
-  return new WindowBackBufferShm(mWaylandDisplay, aWidth, aHeight);
+  return new WindowBackBufferShm(this, aWidth, aHeight);
 }
 
 WindowBackBuffer* WindowSurfaceWayland::GetWaylandBufferToDraw(
     bool aCanSwitchBuffer) {
   LOGWAYLAND(
       ("WindowSurfaceWayland::GetWaylandBufferToDraw [%p] Requested buffer [%d "
        "x %d]\n",
        (void*)this, mBufferScreenRect.width, mBufferScreenRect.height));
@@ -673,16 +688,21 @@ WindowBackBuffer* WindowSurfaceWayland::
 already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::LockWaylandBuffer(
     bool aCanSwitchBuffer) {
   WindowBackBuffer* buffer = GetWaylandBufferToDraw(aCanSwitchBuffer);
 
   LOGWAYLAND(("WindowSurfaceWayland::LockWaylandBuffer [%p] Got buffer %p\n",
               (void*)this, (void*)buffer));
 
   if (!buffer) {
+    if (mLastCommitTime && (g_get_monotonic_time() / 1000) - mLastCommitTime >
+                               COMPOSITING_TIMEOUT) {
+      NS_WARNING(
+          "Slow response from Wayland compositor, visual glitches ahead.");
+    }
     return nullptr;
   }
 
   if (mBufferNeedsClear && mWholeWindowBufferDamage) {
     buffer->Clear();
     mBufferNeedsClear = false;
   }
 
@@ -724,19 +744,17 @@ static bool IsWindowFullScreenUpdate(
 static bool IsPopupFullScreenUpdate(
     LayoutDeviceIntRect& aScreenRect,
     const LayoutDeviceIntRegion& aUpdatedRegion) {
   // We know that popups can be drawn from two parts; a panel and an arrow.
   // Assume we redraw whole popups when we have two rects and bounding
   // box is equal to window borders.
   if (aUpdatedRegion.GetNumRects() > 2) return false;
 
-  gfx::IntRect bounds = aUpdatedRegion.GetBounds().ToUnknownRect();
-  gfx::IntSize lockSize(bounds.XMost(), bounds.YMost());
-
+  gfx::IntRect lockSize = aUpdatedRegion.GetBounds().ToUnknownRect();
   return (aScreenRect.width == lockSize.width &&
           aScreenRect.height == lockSize.height);
 }
 
 bool WindowSurfaceWayland::CanDrawToWaylandBufferDirectly(
     const LayoutDeviceIntRect& aScreenRect,
     const LayoutDeviceIntRegion& aUpdatedRegion) {
   // whole buffer damage or no cache - we can go direct rendering safely.
@@ -751,56 +769,44 @@ bool WindowSurfaceWayland::CanDrawToWayl
       return false;
     }
 
     // More than one regions can overlap and produce flickering/artifacts.
     if (aUpdatedRegion.GetNumRects() > 1) {
       return false;
     }
 
-    gfx::IntRect bounds = aUpdatedRegion.GetBounds().ToUnknownRect();
-    gfx::IntSize lockSize(bounds.XMost(), bounds.YMost());
+    gfx::IntRect lockSize = aUpdatedRegion.GetBounds().ToUnknownRect();
 
     // There's some heuristics here. Let's enable direct rendering for large
     // screen updates like video playback or page scrolling which is bigger
     // than 1/3 of screen.
     if (lockSize.width * 3 > aScreenRect.width &&
         lockSize.height * 3 > aScreenRect.height) {
       return true;
     }
   }
   return false;
 }
 
-/*
-  There are some situations which can happen here:
-
-  A) Lock() is called to whole surface. In that case we don't need
-     to clip/buffer the drawing and we can return wl_buffer directly
-     for drawing.
-       - mWaylandBuffer is available - that's an ideal situation.
-       - mWaylandBuffer is locked by compositor - flip buffers and draw.
-          - if we can't flip buffers - go B)
-
-  B) Lock() is requested for part(s) of screen. We need to provide temporary
-     surface to draw into and copy result (clipped) to target wl_surface.
- */
 already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::Lock(
     const LayoutDeviceIntRegion& aRegion) {
   MOZ_ASSERT(mIsMainThread == NS_IsMainThread());
 
-  // Disable all commits from frame callback handler and delayed comit handler
-  // as we're updated by gecko compositor.
-  mPendingCommit = false;
+  // Wait until all pending events are processed. There may be queued
+  // wl_buffer release event which releases our wl_buffer for further rendering.
+  mWaylandDisplay->WaitForSyncEnd();
+
+  // Disable all commits (from potential frame callback/delayed handlers)
+  // until next WindowSurfaceWayland::Commit() call.
+  mBufferCommitAllowed = false;
 
   LayoutDeviceIntRect lockedScreenRect = mWindow->GetBounds();
-  gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
-  gfx::IntSize lockSize(bounds.XMost(), bounds.YMost());
+  gfx::IntRect lockSize = aRegion.GetBounds().ToUnknownRect();
 
-  // Are we asked for entire nsWindow to draw?
   bool isTransparentPopup =
       mWindow->IsWaylandPopup() &&
       (eTransparencyTransparent == mWindow->GetTransparencyMode());
 
   // We have request to lock whole buffer/window.
   mWholeWindowBufferDamage =
       isTransparentPopup ? IsPopupFullScreenUpdate(lockedScreenRect, aRegion)
                          : IsWindowFullScreenUpdate(lockedScreenRect, aRegion);
@@ -809,31 +815,31 @@ already_AddRefed<gfx::DrawTarget> Window
   // otherwise leave it as-is, mBufferNeedsClear can be set from previous
   // (already pending) commits which are cached now.
   if (mWholeWindowBufferDamage) {
     mBufferNeedsClear =
         mWindow->WaylandSurfaceNeedsClear() || isTransparentPopup;
   }
 
   LOGWAYLAND(
-      ("WindowSurfaceWayland::Lock [%p] lockSize [%d x %d] windowSize [%d x "
-       "%d]\n",
-       (void*)this, lockSize.width, lockSize.height, lockedScreenRect.width,
-       lockedScreenRect.height));
+      ("WindowSurfaceWayland::Lock [%p] [%d,%d] -> [%d x %d] rects %d "
+       "windowSize [%d x %d]\n",
+       (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
+       aRegion.GetNumRects(), lockedScreenRect.width, lockedScreenRect.height));
   LOGWAYLAND(("   nsWindow = %p\n", mWindow));
   LOGWAYLAND(("   isPopup = %d\n", mWindow->IsWaylandPopup()));
   LOGWAYLAND(("   isTransparentPopup = %d\n", isTransparentPopup));
   LOGWAYLAND(("   IsPopupFullScreenUpdate = %d\n",
               IsPopupFullScreenUpdate(lockedScreenRect, aRegion)));
   LOGWAYLAND(("   IsWindowFullScreenUpdate = %d\n",
               IsWindowFullScreenUpdate(lockedScreenRect, aRegion)));
   LOGWAYLAND(("   mBufferNeedsClear = %d\n", mBufferNeedsClear));
   LOGWAYLAND(("   mWholeWindowBufferDamage = %d\n", mWholeWindowBufferDamage));
 
-#if DEBUG
+#if MOZ_LOGGING
   if (!(mBufferScreenRect == lockedScreenRect)) {
     LOGWAYLAND(("   screen size changed\n"));
   }
 #endif
 
   if (!(mBufferScreenRect == lockedScreenRect)) {
     // Screen (window) size changed and we still have some painting pending
     // for the last window size. That can happen when window is resized.
@@ -849,23 +855,30 @@ already_AddRefed<gfx::DrawTarget> Window
       return nullptr;
     }
     mBufferScreenRect = lockedScreenRect;
   }
 
   mDrawToWaylandBufferDirectly =
       CanDrawToWaylandBufferDirectly(mBufferScreenRect, aRegion);
   if (mDrawToWaylandBufferDirectly) {
+    LOGWAYLAND(("   Direct drawing\n"));
     // If there's any pending image commit scratch them as we're going
     // to redraw the whole sceen anyway.
-    mDelayedImageCommits.Clear();
+    if (mWholeWindowBufferDamage) {
+      LOGWAYLAND(("   Whole buffer update, clear cache.\n"));
+      mDelayedImageCommits.Clear();
+    }
 
     RefPtr<gfx::DrawTarget> dt = LockWaylandBuffer(
         /* aCanSwitchBuffer */ mWholeWindowBufferDamage);
     if (dt) {
+      if (!mWholeWindowBufferDamage) {
+        DrawDelayedImageCommits(dt, mWaylandBufferDamage);
+      }
       return dt.forget();
     }
   }
 
   // Any caching is disabled and we don't have any back buffer available.
   if (mRenderingCacheMode == CACHE_NONE) {
     return nullptr;
   }
@@ -874,25 +887,35 @@ already_AddRefed<gfx::DrawTarget> Window
   //
   // 1) We don't have any front buffer available. Try indirect drawing
   //    to mImageSurface which is mirrored to front buffer at commit.
   // 2) Only part of the screen is locked. We can't lock entire screen for
   //    such drawing as it produces visible artifacts.
   mDrawToWaylandBufferDirectly = false;
 
   LOGWAYLAND(("   Indirect drawing.\n"));
-  return LockImageSurface(lockSize);
+  return LockImageSurface(gfx::IntSize(lockSize.XMost(), lockSize.YMost()));
 }
 
 void WindowImageSurface::Draw(gfx::SourceSurface* aSurface,
                               gfx::DrawTarget* aDest,
                               const LayoutDeviceIntRegion& aRegion) {
+#ifdef MOZ_LOGGING
+  gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+  LOGWAYLAND(("WindowImageSurface::Draw\n"));
+  LOGWAYLAND(("    rects num %d\n", aRegion.GetNumRects()));
+  LOGWAYLAND(("    bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
+              bounds.width, bounds.height));
+#endif
+
   for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
     mozilla::LayoutDeviceIntRect r = iter.Get();
     gfx::Rect rect(r.ToUnknownRect());
+    LOGWAYLAND(("    draw rect [%f,%f] -> [%f x %f]\n", rect.x, rect.y,
+                rect.width, rect.height));
     aDest->DrawSurface(aSurface, rect, rect);
   }
 }
 
 void WindowImageSurface::Draw(gfx::DrawTarget* aDest,
                               LayoutDeviceIntRegion& aWaylandBufferDamage) {
   Draw(mSurface.get(), aDest, mUpdateRegion);
   aWaylandBufferDamage.OrWith(mUpdateRegion);
@@ -903,75 +926,103 @@ WindowImageSurface::WindowImageSurface(
     : mImageSurface(aImageSurface), mUpdateRegion(aUpdateRegion) {
   mSurface = gfx::Factory::CreateSourceSurfaceForCairoSurface(
       mImageSurface->CairoSurface(), mImageSurface->GetSize(),
       mImageSurface->Format());
 }
 
 void WindowSurfaceWayland::DrawDelayedImageCommits(
     gfx::DrawTarget* aDrawTarget, LayoutDeviceIntRegion& aWaylandBufferDamage) {
+  LOGWAYLAND(
+      ("WindowSurfaceWayland::DrawDelayedImageCommits [%p]\n", (void*)this));
+
   for (unsigned int i = 0; i < mDelayedImageCommits.Length(); i++) {
     mDelayedImageCommits[i].Draw(aDrawTarget, aWaylandBufferDamage);
   }
   mDelayedImageCommits.Clear();
 }
 
 void WindowSurfaceWayland::CacheImageSurface(
     const LayoutDeviceIntRegion& aRegion) {
-  LOGWAYLAND(("WindowSurfaceWayland::CacheImageSurface [%p]", (void*)this));
+#ifdef MOZ_LOGGING
+  gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+  LOGWAYLAND(("WindowSurfaceWayland::CacheImageSurface [%p]\n", (void*)this));
+  LOGWAYLAND(("    rects num %d\n", aRegion.GetNumRects()));
+  LOGWAYLAND(("    bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
+              bounds.width, bounds.height));
+#endif
 
   mDelayedImageCommits.AppendElement(
       WindowImageSurface(mImageSurface, aRegion));
   // mImageSurface is owned by mDelayedImageCommits
   mImageSurface = nullptr;
+
+  LOGWAYLAND(
+      ("    There's %d cached images\n", int(mDelayedImageCommits.Length())));
 }
 
 bool WindowSurfaceWayland::CommitImageCacheToWaylandBuffer() {
+  if (!mDelayedImageCommits.Length()) {
+    return false;
+  }
+
   MOZ_ASSERT(!mDrawToWaylandBufferDirectly);
 
-  if (mDelayedImageCommits.Length()) {
-    RefPtr<gfx::DrawTarget> dt = LockWaylandBuffer(
-        /* aCanSwitchBuffer */ mWholeWindowBufferDamage);
-    if (!dt) {
-      return false;
-    }
+  RefPtr<gfx::DrawTarget> dt = LockWaylandBuffer(
+      /* aCanSwitchBuffer */ mWholeWindowBufferDamage);
+  if (!dt) {
+    return false;
+  }
 
-    LOGWAYLAND(
-        ("   Flushing %ld cached WindowImageSurfaces to Wayland buffer\n",
-         long(mDelayedImageCommits.Length() + 1)));
+  LOGWAYLAND(("   Flushing %ld cached WindowImageSurfaces to Wayland buffer\n",
+              long(mDelayedImageCommits.Length())));
 
-    // Draw any delayed image commits first
-    DrawDelayedImageCommits(dt, mWaylandBufferDamage);
-    UnlockWaylandBuffer();
-  }
+  DrawDelayedImageCommits(dt, mWaylandBufferDamage);
+  UnlockWaylandBuffer();
 
   return true;
 }
 
 static void WaylandBufferDelayCommitHandler(WindowSurfaceWayland** aSurface) {
   if (*aSurface) {
     (*aSurface)->DelayedCommitHandler();
   } else {
     // Referenced WindowSurfaceWayland is already deleted.
     // Do nothing but just release the mDelayedCommitHandle allocated at
     // WindowSurfaceWayland::CommitWaylandBuffer().
     free(aSurface);
   }
 }
 
 void WindowSurfaceWayland::CommitWaylandBuffer() {
-  MOZ_ASSERT(mPendingCommit, "Committing empty surface!");
+  MOZ_ASSERT(!mWaylandBuffer->IsAttached(),
+             "We can't draw to attached wayland buffer!");
 
   LOGWAYLAND(("WindowSurfaceWayland::CommitWaylandBuffer [%p]\n", (void*)this));
   LOGWAYLAND(
       ("   mDrawToWaylandBufferDirectly = %d\n", mDrawToWaylandBufferDirectly));
   LOGWAYLAND(("   mWholeWindowBufferDamage = %d\n", mWholeWindowBufferDamage));
   LOGWAYLAND(("   mDelayedCommitHandle = %p\n", mDelayedCommitHandle));
   LOGWAYLAND(("   mFrameCallback = %p\n", mFrameCallback));
   LOGWAYLAND(("   mLastCommittedSurface = %p\n", mLastCommittedSurface));
+  LOGWAYLAND(("   mBufferPendingCommit = %d\n", mBufferPendingCommit));
+  LOGWAYLAND(("   mBufferCommitAllowed = %d\n", mBufferCommitAllowed));
+
+  if (!mBufferCommitAllowed) {
+    return;
+  }
+
+  if (CommitImageCacheToWaylandBuffer()) {
+    mBufferPendingCommit = true;
+  }
+
+  // There's nothing to do here
+  if (!mBufferPendingCommit) {
+    return;
+  }
 
   wl_surface* waylandSurface = mWindow->GetWaylandSurface();
   if (!waylandSurface) {
     LOGWAYLAND(("    [%p] mWindow->GetWaylandSurface() failed, delay commit.\n",
                 (void*)this));
 
     // Target window is not created yet - delay the commit. This can happen only
     // when the window is newly created and there's no active
@@ -1007,24 +1058,27 @@ void WindowSurfaceWayland::CommitWayland
     // If our stored wl_surface does not match the actual one it means the frame
     // callback is no longer active and we should release it.
     wl_callback_destroy(mFrameCallback);
     mFrameCallback = nullptr;
     mLastCommittedSurface = nullptr;
   }
 
   if (mWholeWindowBufferDamage) {
+    LOGWAYLAND(("   send whole screen damage\n"));
     wl_surface_damage(waylandSurface, 0, 0, mBufferScreenRect.width,
                       mBufferScreenRect.height);
     mWholeWindowBufferDamage = false;
     mNeedScaleFactorUpdate = true;
   } else {
     for (auto iter = mWaylandBufferDamage.RectIter(); !iter.Done();
          iter.Next()) {
       mozilla::LayoutDeviceIntRect r = iter.Get();
+      LOGWAYLAND(("   wl_surface_damage_buffer [%d, %d] -> [%d, %d]\n", r.x,
+                  r.y, r.width, r.height));
       wl_surface_damage_buffer(waylandSurface, r.x, r.y, r.width, r.height);
     }
   }
 
   // Clear all back buffer damage as we're committing
   // all requested regions.
   mWaylandBufferDamage.SetEmpty();
 
@@ -1033,90 +1087,86 @@ void WindowSurfaceWayland::CommitWayland
 
   if (mNeedScaleFactorUpdate || mLastCommittedSurface != waylandSurface) {
     wl_surface_set_buffer_scale(waylandSurface, mWindow->GdkScaleFactor());
     mNeedScaleFactorUpdate = false;
   }
 
   mWaylandBuffer->Attach(waylandSurface);
   mLastCommittedSurface = waylandSurface;
+  mLastCommitTime = g_get_monotonic_time() / 1000;
+
+  // Ask wl_display to start events synchronization. We're going to wait
+  // until all events are processed before next WindowSurfaceWayland::Lock()
+  // as we hope for free wl_buffer there.
+  mWaylandDisplay->SyncBegin();
 
   // There's no pending commit, all changes are sent to compositor.
-  mPendingCommit = false;
+  mBufferPendingCommit = false;
 }
 
 void WindowSurfaceWayland::Commit(const LayoutDeviceIntRegion& aInvalidRegion) {
   MOZ_ASSERT(mIsMainThread == NS_IsMainThread());
 
-#ifdef DEBUG
+#ifdef MOZ_LOGGING
   {
-    gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect();
-    gfx::IntSize lockSize(bounds.XMost(), bounds.YMost());
-
+    gfx::IntRect lockSize = aInvalidRegion.GetBounds().ToUnknownRect();
     LOGWAYLAND(
-        ("WindowSurfaceWayland::Commit [%p] lockSize [%d x %d] screenSize [%d "
-         "x %d]\n",
-         (void*)this, lockSize.width, lockSize.height, mBufferScreenRect.width,
-         mBufferScreenRect.height));
+        ("WindowSurfaceWayland::Commit [%p] damage size [%d, %d] -> [%d x %d]"
+         "screenSize [%d x %d]\n",
+         (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
+         mBufferScreenRect.width, mBufferScreenRect.height));
     LOGWAYLAND(("    mDrawToWaylandBufferDirectly = %d\n",
                 mDrawToWaylandBufferDirectly));
     LOGWAYLAND(
         ("    mWholeWindowBufferDamage = %d\n", mWholeWindowBufferDamage));
   }
 #endif
 
   if (mDrawToWaylandBufferDirectly) {
     MOZ_ASSERT(mWaylandBuffer->IsLocked());
     // If we're not at fullscreen damage add drawing area from aInvalidRegion
     if (!mWholeWindowBufferDamage) {
       mWaylandBufferDamage.OrWith(aInvalidRegion);
     }
     UnlockWaylandBuffer();
-    mPendingCommit = true;
+    mBufferPendingCommit = true;
   } else {
     MOZ_ASSERT(!mWaylandBuffer->IsLocked(),
                "Drawing to already locked buffer?");
     CacheImageSurface(aInvalidRegion);
-    if (CommitImageCacheToWaylandBuffer()) {
-      mPendingCommit = true;
-    }
   }
 
-  if (mPendingCommit) {
-    CommitWaylandBuffer();
-  }
+  mBufferCommitAllowed = true;
+  CommitWaylandBuffer();
 }
 
 void WindowSurfaceWayland::FrameCallbackHandler() {
   MOZ_ASSERT(mIsMainThread == NS_IsMainThread());
   MOZ_ASSERT(mFrameCallback != nullptr,
              "FrameCallbackHandler() called without valid frame callback!");
   MOZ_ASSERT(mLastCommittedSurface != nullptr,
              "FrameCallbackHandler() called without valid wl_surface!");
 
   LOGWAYLAND(
       ("WindowSurfaceWayland::FrameCallbackHandler [%p]\n", (void*)this));
 
   wl_callback_destroy(mFrameCallback);
   mFrameCallback = nullptr;
 
-  if (mPendingCommit) {
-    CommitWaylandBuffer();
-  }
+  CommitWaylandBuffer();
 }
 
 void WindowSurfaceWayland::DelayedCommitHandler() {
   MOZ_ASSERT(mDelayedCommitHandle != nullptr, "Missing mDelayedCommitHandle!");
 
   LOGWAYLAND(
       ("WindowSurfaceWayland::DelayedCommitHandler [%p]\n", (void*)this));
 
   *mDelayedCommitHandle = nullptr;
   free(mDelayedCommitHandle);
   mDelayedCommitHandle = nullptr;
 
-  if (mPendingCommit) {
-    CommitWaylandBuffer();
-  }
+  CommitWaylandBuffer();
 }
 
 }  // namespace widget
 }  // namespace mozilla
--- a/widget/gtk/WindowSurfaceWayland.h
+++ b/widget/gtk/WindowSurfaceWayland.h
@@ -12,16 +12,18 @@
 #include "nsWaylandDisplay.h"
 #include "WaylandDMABufSurface.h"
 
 #define BACK_BUFFER_NUM 2
 
 namespace mozilla {
 namespace widget {
 
+class WindowSurfaceWayland;
+
 // Allocates and owns shared memory for Wayland drawing surface
 class WaylandShmPool {
  public:
   WaylandShmPool(nsWaylandDisplay* aDisplay, int aSize);
   ~WaylandShmPool();
 
   bool Resize(int aSize);
   wl_shm_pool* GetShmPool() { return mShmPool; };
@@ -64,30 +66,32 @@ class WindowBackBuffer {
     return aWidth == GetWidth() && aHeight == GetHeight();
   }
   bool IsMatchingSize(class WindowBackBuffer* aBuffer) {
     return aBuffer->IsMatchingSize(GetWidth(), GetHeight());
   }
 
   static gfx::SurfaceFormat GetSurfaceFormat() { return mFormat; }
 
-  nsWaylandDisplay* GetWaylandDisplay() { return mWaylandDisplay; };
+  nsWaylandDisplay* GetWaylandDisplay();
 
-  WindowBackBuffer(nsWaylandDisplay* aWaylandDisplay)
-      : mWaylandDisplay(aWaylandDisplay){};
+  WindowBackBuffer(WindowSurfaceWayland* aWindowSurfaceWayland)
+      : mWindowSurfaceWayland(aWindowSurfaceWayland){};
   virtual ~WindowBackBuffer(){};
 
+ protected:
+  WindowSurfaceWayland* mWindowSurfaceWayland;
+
  private:
   static gfx::SurfaceFormat mFormat;
-  nsWaylandDisplay* mWaylandDisplay;
 };
 
 class WindowBackBufferShm : public WindowBackBuffer {
  public:
-  WindowBackBufferShm(nsWaylandDisplay* aWaylandDisplay, int aWidth,
+  WindowBackBufferShm(WindowSurfaceWayland* aWindowSurfaceWayland, int aWidth,
                       int aHeight);
   ~WindowBackBufferShm();
 
   already_AddRefed<gfx::DrawTarget> Lock();
   bool IsLocked() { return mIsLocked; };
   void Unlock() { mIsLocked = false; };
 
   void Detach(wl_buffer* aBuffer);
@@ -116,18 +120,18 @@ class WindowBackBufferShm : public Windo
   int mWidth;
   int mHeight;
   bool mAttached;
   bool mIsLocked;
 };
 
 class WindowBackBufferDMABuf : public WindowBackBuffer {
  public:
-  WindowBackBufferDMABuf(nsWaylandDisplay* aWaylandDisplay, int aWidth,
-                         int aHeight);
+  WindowBackBufferDMABuf(WindowSurfaceWayland* aWindowSurfaceWayland,
+                         int aWidth, int aHeight);
   ~WindowBackBufferDMABuf();
 
   bool IsAttached();
   void SetAttached();
 
   int GetWidth();
   int GetHeight();
   wl_buffer* GetWlBuffer();
@@ -165,22 +169,49 @@ class WindowImageSurface {
 
 // WindowSurfaceWayland is an abstraction for wl_surface
 // and related management
 class WindowSurfaceWayland : public WindowSurface {
  public:
   explicit WindowSurfaceWayland(nsWindow* aWindow);
   ~WindowSurfaceWayland();
 
+  // 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()/
+  // DelayedCommitHandler/CommitWaylandBuffer().
   already_AddRefed<gfx::DrawTarget> Lock(
       const LayoutDeviceIntRegion& aRegion) override;
   void Commit(const LayoutDeviceIntRegion& aInvalidRegion) final;
+
+  // It's called from wayland compositor when there's the right
+  // time to send wl_buffer to display. It's no-op if there's no
+  // queued commits.
   void FrameCallbackHandler();
+
+  // When a new window is created we may not have a valid wl_surface
+  // for drawing (Gtk haven't created it yet). All commits are queued
+  // and DelayedCommitHandler() is called by timer when wl_surface is ready
+  // for drawing.
   void DelayedCommitHandler();
 
+  // Try to commit all queued drawings to Wayland compositor. This is usually
+  // called from other routines but can be used to explicitly flush
+  // all drawings as we do when wl_buffer is released
+  // (see WindowBackBufferShm::Detach() for instance).
+  void CommitWaylandBuffer();
+
+  nsWaylandDisplay* GetWaylandDisplay() { return mWaylandDisplay; };
+
   // Image cache mode can be set by widget.wayland_cache_mode
   typedef enum {
     // Cache and clip all drawings, default. It's slowest
     // but also without any rendered artifacts.
     CACHE_ALL = 0,
     // Cache drawing only when back buffer is missing. May produce
     // some rendering artifacts and flickering when partial screen update
     // is rendered.
@@ -202,43 +233,90 @@ class WindowSurfaceWayland : public Wind
       const LayoutDeviceIntRect& aScreenRect,
       const LayoutDeviceIntRegion& aUpdatedRegion);
 
   already_AddRefed<gfx::DrawTarget> LockImageSurface(
       const gfx::IntSize& aLockSize);
 
   void CacheImageSurface(const LayoutDeviceIntRegion& aRegion);
   bool CommitImageCacheToWaylandBuffer();
-  void CommitWaylandBuffer();
 
   void DrawDelayedImageCommits(gfx::DrawTarget* aDrawTarget,
                                LayoutDeviceIntRegion& aWaylandBufferDamage);
 
   // TODO: Do we need to hold a reference to nsWindow object?
   nsWindow* mWindow;
   // Buffer screen rects helps us understand if we operate on
   // the same window size as we're called on WindowSurfaceWayland::Lock().
   // mBufferScreenRect is window size when our wayland buffer was allocated.
   LayoutDeviceIntRect mBufferScreenRect;
   nsWaylandDisplay* mWaylandDisplay;
+
+  // Actual buffer (backed by wl_buffer) where all drawings go into.
+  // Drawn areas are stored at mWaylandBufferDamage and if there's
+  // any uncommited drawings which needs to be send to wayland compositor
+  // the mBufferPendingCommit is set.
   WindowBackBuffer* mWaylandBuffer;
+  WindowBackBuffer* mBackupBuffer[BACK_BUFFER_NUM];
   LayoutDeviceIntRegion mWaylandBufferDamage;
-  WindowBackBuffer* mBackupBuffer[BACK_BUFFER_NUM];
+
+  // After every commit to wayland compositor a frame callback is requested.
+  // Any next commit to wayland compositor will happen when frame callback
+  // comes from wayland compositor back as it's the best time to do the commit.
   wl_callback* mFrameCallback;
   wl_surface* mLastCommittedSurface;
-  MessageLoop* mDisplayThreadMessageLoop;
+
+  // Registered reference to pending DelayedCommitHandler() call.
   WindowSurfaceWayland** mDelayedCommitHandle;
+
+  // Cached drawings. If we can't get WaylandBuffer (wl_buffer) at
+  // WindowSurfaceWayland::Lock() we direct gecko rendering to
+  // mImageSurface.
+  // If we can't get WaylandBuffer at WindowSurfaceWayland::Commit()
+  // time, mImageSurface is moved to mDelayedImageCommits which
+  // holds all cached drawings.
+  // mDelayedImageCommits can be drawn by FrameCallbackHandler(),
+  // DelayedCommitHandler() or when WaylandBuffer is detached.
   RefPtr<gfxImageSurface> mImageSurface;
   AutoTArray<WindowImageSurface, 30> mDelayedImageCommits;
+
+  int64_t mLastCommitTime;
+
+  // Indicates that we don't have any cached drawings at mDelayedImageCommits
+  // and WindowSurfaceWayland::Lock() returned WaylandBuffer to gecko
+  // to draw into.
   bool mDrawToWaylandBufferDirectly;
-  bool mPendingCommit;
+
+  // Set when actual WaylandBuffer contains drawings which are not send to
+  // wayland compositor yet.
+  bool mBufferPendingCommit;
+
+  // We can't send WaylandBuffer (wl_buffer) to compositor when gecko
+  // is rendering into it (i.e. between WindowSurfaceWayland::Lock() /
+  // WindowSurfaceWayland::Commit()).
+  // Thus we use mBufferCommitAllowed to disable commit by callbacks
+  // (FrameCallbackHandler(), DelayedCommitHandler())
+  bool mBufferCommitAllowed;
+
+  // Set when WindowSurfaceWayland::Lock() requested drawing area matches
+  // nsWindow size. When whole window is repainted we can switch WaylandBuffer
+  // and throw away any cached drawings from previous rendering.
   bool mWholeWindowBufferDamage;
+
+  // We need to clear WaylandBuffer when entire transparent window is repainted.
+  // This typically apply to popup windows.
   bool mBufferNeedsClear;
+
   bool mIsMainThread;
+
+  // When new WaylandBuffer (wl_buffer) is send to wayland compositor
+  // (buffer switch or resize) we also need to set its scale factor.
   bool mNeedScaleFactorUpdate;
+
+  // Image caching strategy, see RenderingCacheMode for details.
   RenderingCacheMode mRenderingCacheMode;
 
   static bool UseDMABufBackend();
   static bool mUseDMABufInitialized;
   static bool mUseDMABuf;
 };
 
 }  // namespace widget
--- a/widget/gtk/mozwayland/mozwayland.h
+++ b/widget/gtk/mozwayland/mozwayland.h
@@ -22,16 +22,19 @@ extern "C" {
 
 MOZ_EXPORT int wl_display_roundtrip_queue(struct wl_display* display,
                                           struct wl_event_queue* queue);
 MOZ_EXPORT uint32_t wl_proxy_get_version(struct wl_proxy* proxy);
 MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_constructor(
     struct wl_proxy* proxy, uint32_t opcode,
     const struct wl_interface* interface, ...);
 
+MOZ_EXPORT void* wl_proxy_create_wrapper(void* proxy);
+MOZ_EXPORT void wl_proxy_wrapper_destroy(void* proxy_wrapper);
+
 /* We need implement some missing functions from wayland-client-protocol.h
  */
 #ifndef WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM
 enum wl_data_device_manager_dnd_action {
   WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE = 0,
   WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY = 1,
   WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE = 2,
   WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK = 4
--- a/widget/gtk/nsWaylandDisplay.cpp
+++ b/widget/gtk/nsWaylandDisplay.cpp
@@ -253,16 +253,71 @@ static void global_registry_remover(void
 static const struct wl_registry_listener registry_listener = {
     global_registry_handler, global_registry_remover};
 
 bool nsWaylandDisplay::DispatchEventQueue() {
   wl_display_dispatch_queue_pending(mDisplay, mEventQueue);
   return true;
 }
 
+void nsWaylandDisplay::SyncEnd() {
+  wl_callback_destroy(mSyncCallback);
+  mSyncCallback = nullptr;
+}
+
+static void wayland_sync_callback(void* data, struct wl_callback* callback,
+                                  uint32_t time) {
+  auto display = static_cast<nsWaylandDisplay*>(data);
+  display->SyncEnd();
+}
+
+static const struct wl_callback_listener sync_callback_listener = {
+    .done = wayland_sync_callback};
+
+void nsWaylandDisplay::SyncBegin() {
+  WaitForSyncEnd();
+
+  // Use wl_display_sync() to synchronize wayland events.
+  // See dri2_wl_swap_buffers_with_damage() from MESA
+  // or wl_display_roundtrip_queue() from wayland-client.
+  struct wl_display* displayWrapper =
+      static_cast<wl_display*>(wl_proxy_create_wrapper((void*)mDisplay));
+  if (!displayWrapper) {
+    NS_WARNING("Failed to create wl_proxy wrapper!");
+    return;
+  }
+
+  wl_proxy_set_queue((struct wl_proxy*)displayWrapper, mEventQueue);
+  mSyncCallback = wl_display_sync(displayWrapper);
+  wl_proxy_wrapper_destroy((void*)displayWrapper);
+
+  if (!mSyncCallback) {
+    NS_WARNING("Failed to create wl_display_sync callback!");
+    return;
+  }
+
+  wl_callback_add_listener(mSyncCallback, &sync_callback_listener, this);
+  wl_display_flush(mDisplay);
+}
+
+void nsWaylandDisplay::WaitForSyncEnd() {
+  // We're done here
+  if (!mSyncCallback) {
+    return;
+  }
+
+  while (mSyncCallback != nullptr) {
+    if (wl_display_dispatch_queue(mDisplay, mEventQueue) == -1) {
+      NS_WARNING("wl_display_dispatch_queue failed!");
+      SyncEnd();
+      return;
+    }
+  }
+}
+
 bool nsWaylandDisplay::Matches(wl_display* aDisplay) {
   return mThreadId == PR_GetCurrentThread() && aDisplay == mDisplay;
 }
 
 bool nsWaylandDisplay::ConfigureGbm() {
   if (!nsGbmLib::IsAvailable()) {
     return false;
   }
@@ -315,16 +370,17 @@ nsWaylandDisplay::nsWaylandDisplay(wl_di
       mThreadId(PR_GetCurrentThread()),
       mDisplay(aDisplay),
       mEventQueue(nullptr),
       mDataDeviceManager(nullptr),
       mCompositor(nullptr),
       mSubcompositor(nullptr),
       mSeat(nullptr),
       mShm(nullptr),
+      mSyncCallback(nullptr),
       mPrimarySelectionDeviceManager(nullptr),
       mRegistry(nullptr),
       mDmabuf(nullptr),
       mGbmDevice(nullptr),
       mGbmFd(-1),
       mXRGBFormat({false, false, -1, nullptr, 0}),
       mARGBFormat({false, false, -1, nullptr, 0}),
       mGdmConfigured(false),
--- a/widget/gtk/nsWaylandDisplay.h
+++ b/widget/gtk/nsWaylandDisplay.h
@@ -35,16 +35,21 @@ struct GbmFormat {
 // We have a global nsWaylandDisplay object for each thread,
 // recently we have three for main, compositor and render one.
 class nsWaylandDisplay {
  public:
   explicit nsWaylandDisplay(wl_display* aDisplay);
   virtual ~nsWaylandDisplay();
 
   bool DispatchEventQueue();
+
+  void SyncBegin();
+  void SyncEnd();
+  void WaitForSyncEnd();
+
   bool Matches(wl_display* aDisplay);
 
   MessageLoop* GetDispatcherThreadLoop() { return mDispatcherThreadLoop; }
   wl_display* GetDisplay() { return mDisplay; };
   wl_event_queue* GetEventQueue() { return mEventQueue; };
   wl_compositor* GetCompositor(void) { return mCompositor; };
   wl_subcompositor* GetSubcompositor(void) { return mSubcompositor; };
   wl_data_device_manager* GetDataDeviceManager(void) {
@@ -89,16 +94,17 @@ class nsWaylandDisplay {
   PRThread* mThreadId;
   wl_display* mDisplay;
   wl_event_queue* mEventQueue;
   wl_data_device_manager* mDataDeviceManager;
   wl_compositor* mCompositor;
   wl_subcompositor* mSubcompositor;
   wl_seat* mSeat;
   wl_shm* mShm;
+  wl_callback* mSyncCallback;
   gtk_primary_selection_device_manager* mPrimarySelectionDeviceManager;
   wl_registry* mRegistry;
   zwp_linux_dmabuf_v1* mDmabuf;
   gbm_device* mGbmDevice;
   int mGbmFd;
   GbmFormat mXRGBFormat;
   GbmFormat mARGBFormat;
   bool mGdmConfigured;