Bug 1422966 - Implement SurfaceWayland for Wayland, r=jhorak
authorMartin Stransky <stransky@redhat.com>
Mon, 04 Dec 2017 22:29:08 +0100
changeset 396548 68803e96b4bebbcbdfede91ebc208cd75dfb81a7
parent 396547 4a82861a13995730173b1436aac8a48ee0f0c22b
child 396549 2ad057a99aaebbe26e311f74a9de24b45f241b78
push id57034
push userstransky@redhat.com
push dateFri, 15 Dec 2017 15:12:47 +0000
treeherderautoland@68803e96b4be [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjhorak
bugs1422966
milestone59.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 1422966 - Implement SurfaceWayland for Wayland, r=jhorak WindowSurfaceWayland is Wayland implementation of WindowSurface class. One WindowSurfaceWayland object manages drawing of one nsWindow so those are tied 1:1. It implements base Lock() and Commit() interfaces from WindowSurface. At Wayland side it represents one wl_surface object. To perform visualiation of nsWindow, WindowSurfaceWayland contains one wl_surface and two wl_buffer (by WindowBackBuffer) objects (as we use double buffering). When nsWindow drawing is finished to wl_buffer, the wl_buffer is attached to wl_surface and it's sent to Wayland compositor. MozReview-Commit-ID: 9NoamtF87e6
widget/gtk/WindowSurfaceWayland.cpp
widget/gtk/WindowSurfaceWayland.h
--- a/widget/gtk/WindowSurfaceWayland.cpp
+++ b/widget/gtk/WindowSurfaceWayland.cpp
@@ -512,17 +512,17 @@ WindowBackBuffer::Detach()
 {
   mAttached = false;
 }
 
 bool
 WindowBackBuffer::SetImageDataFromBackBuffer(
   class WindowBackBuffer* aSourceBuffer)
 {
-  if (!MatchSize(aSourceBuffer)) {
+  if (!IsMatchingSize(aSourceBuffer)) {
     Resize(aSourceBuffer->mWidth, aSourceBuffer->mHeight);
   }
 
   mShmPool.SetImageDataFromPool(aSourceBuffer->mShmPool,
     aSourceBuffer->mWidth * aSourceBuffer->mHeight * BUFFER_BPP);
   return true;
 }
 
@@ -533,10 +533,205 @@ WindowBackBuffer::Lock(const LayoutDevic
   gfx::IntSize lockSize(bounds.XMost(), bounds.YMost());
 
   return gfxPlatform::CreateDrawTargetForData(static_cast<unsigned char*>(mShmPool.GetImageData()),
                                               lockSize,
                                               BUFFER_BPP * mWidth,
                                               mWaylandDisplay->GetSurfaceFormat());
 }
 
+static void
+frame_callback_handler(void *data, struct wl_callback *callback, uint32_t time)
+{
+  auto surface = reinterpret_cast<WindowSurfaceWayland*>(data);
+  surface->FrameCallbackHandler();
+}
+
+static const struct wl_callback_listener frame_listener = {
+  frame_callback_handler
+};
+
+WindowSurfaceWayland::WindowSurfaceWayland(nsWindow *aWindow)
+  : mWindow(aWindow)
+  , mWaylandDisplay(WaylandDisplayGet(aWidget->GetWaylandDisplay()))
+  , mFrontBuffer(nullptr)
+  , mBackBuffer(nullptr)
+  , mFrameCallback(nullptr)
+  , mFrameCallbackSurface(nullptr)
+  , mDelayedCommit(false)
+  , mFullScreenDamage(false)
+  , mIsMainThread(NS_IsMainThread())
+{
+}
+
+WindowSurfaceWayland::~WindowSurfaceWayland()
+{
+  delete mFrontBuffer;
+  delete mBackBuffer;
+
+  if (mFrameCallback) {
+    wl_callback_destroy(mFrameCallback);
+  }
+
+  if (!mIsMainThread) {
+    // We can be destroyed from main thread even though we was created/used
+    // in compositor thread. We have to unref/delete WaylandDisplay in compositor
+    // thread then.
+    MessageLoop::current()->PostTask(
+      NewRunnableFunction(&WaylandDisplayRelease, mWaylandDisplay->GetDisplay()));
+  } else {
+    WaylandDisplayRelease(mWaylandDisplay->GetDisplay());
+  }
+}
+
+WindowBackBuffer*
+WindowSurfaceWayland::GetBufferToDraw(int aWidth, int aHeight)
+{
+  if (!mFrontBuffer) {
+    mFrontBuffer = new WindowBackBuffer(mWaylandDisplay, aWidth, aHeight);
+    mBackBuffer = new WindowBackBuffer(mWaylandDisplay, aWidth, aHeight);
+    return mFrontBuffer;
+  }
+
+  if (!mFrontBuffer->IsAttached()) {
+    if (!mFrontBuffer->IsMatchingSize(aWidth, aHeight)) {
+      mFrontBuffer->Resize(aWidth, aHeight);
+    }
+    return mFrontBuffer;
+  }
+
+  // Front buffer is used by compositor, draw to back buffer
+  if (mBackBuffer->IsAttached()) {
+    NS_WARNING("No drawing buffer available");
+    return nullptr;
+  }
+
+  MOZ_ASSERT(!mDelayedCommit,
+             "Uncommitted buffer switch, screen artifacts ahead.");
+
+  WindowBackBuffer *tmp = mFrontBuffer;
+  mFrontBuffer = mBackBuffer;
+  mBackBuffer = tmp;
+
+  if (mBackBuffer->IsMatchingSize(aWidth, aHeight)) {
+    // Former front buffer has the same size as a requested one.
+    // Gecko may expect a content already drawn on screen so copy
+    // existing data to the new buffer.
+    mFrontBuffer->Sync(mBackBuffer);
+    // When buffer switches we need to damage whole screen
+    // (https://bugzilla.redhat.com/show_bug.cgi?id=1418260)
+    mFullScreenDamage = true;
+  } else {
+    // Former buffer has different size from the new request. Only resize
+    // the new buffer and leave gecko to render new whole content.
+    mFrontBuffer->Resize(aWidth, aHeight);
+  }
+
+  return mFrontBuffer;
+}
+
+already_AddRefed<gfx::DrawTarget>
+WindowSurfaceWayland::Lock(const LayoutDeviceIntRegion& aRegion)
+{
+  MOZ_ASSERT(mIsMainThread == NS_IsMainThread());
+
+  // We allocate back buffer to widget size but return only
+  // portion requested by aRegion.
+  LayoutDeviceIntRect rect = mWindow->GetBounds();
+  WindowBackBuffer* buffer = GetBufferToDraw(rect.width,
+                                             rect.height);
+  if (!buffer) {
+    NS_WARNING("No drawing buffer available");
+    return nullptr;
+  }
+
+  return buffer->Lock(aRegion);
+}
+
+void
+WindowSurfaceWayland::Commit(const LayoutDeviceIntRegion& aInvalidRegion)
+{
+  MOZ_ASSERT(mIsMainThread == NS_IsMainThread());
+
+  wl_surface* waylandSurface = mWindow->GetWaylandSurface();
+  if (!waylandSurface) {
+    // Target window is already destroyed - don't bother to render there.
+    return;
+  }
+  wl_proxy_set_queue((struct wl_proxy *)waylandSurface,
+                     mWaylandDisplay->GetEventQueue());
+
+  if (mFullScreenDamage) {
+    LayoutDeviceIntRect rect = mWindow->GetBounds();
+    wl_surface_damage(waylandSurface, 0, 0, rect.width, rect.height);
+    mFullScreenDamage = false;
+  } else {
+    for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+      const mozilla::LayoutDeviceIntRect &r = iter.Get();
+      wl_surface_damage(waylandSurface, r.x, r.y, r.width, r.height);
+    }
+  }
+
+  // Frame callback is always connected to actual wl_surface. When the surface
+  // is unmapped/deleted the frame callback is never called. Unfortunatelly
+  // we don't know if the frame callback is not going to be called.
+  // But our mozcontainer code deletes wl_surface when the GdkWindow is hidden
+  // creates a new one when is visible.
+  if (mFrameCallback && mFrameCallbackSurface == waylandSurface) {
+    // Do nothing here - we have a valid wl_surface and the buffer will be
+    // commited to compositor in next frame callback event.
+    mDelayedCommit = true;
+    return;
+  } else  {
+    if (mFrameCallback) {
+      // Delete frame callback connected to obsoleted wl_surface.
+      wl_callback_destroy(mFrameCallback);
+    }
+
+    mFrameCallback = wl_surface_frame(waylandSurface);
+    wl_callback_add_listener(mFrameCallback, &frame_listener, this);
+    mFrameCallbackSurface = waylandSurface;
+
+    // Let the wayland know of the current scaling factor for the hdpi
+    // displays
+    wl_surface_set_buffer_scale(waylandSurface, mWindow->GdkScaleFactor());
+
+    // There's no pending frame callback so we can draw immediately
+    // and create frame callback for possible subsequent drawing.
+    mFrontBuffer->Attach(waylandSurface);
+    mDelayedCommit = false;
+  }
+}
+
+void
+WindowSurfaceWayland::FrameCallbackHandler()
+{
+  MOZ_ASSERT(mIsMainThread == NS_IsMainThread());
+
+  if (mFrameCallback) {
+    wl_callback_destroy(mFrameCallback);
+    mFrameCallback = nullptr;
+    mFrameCallbackSurface = nullptr;
+  }
+
+  if (mDelayedCommit) {
+    wl_surface* waylandSurface = mWindow->GetWaylandSurface();
+    if (!waylandSurface) {
+      // Target window is already destroyed - don't bother to render there.
+      NS_WARNING("No drawing buffer available");
+      return;
+    }
+    wl_proxy_set_queue((struct wl_proxy *)waylandSurface,
+                       mWaylandDisplay->GetEventQueue());
+
+    // Send pending surface to compositor and register frame callback
+    // for possible subsequent drawing.
+    mFrameCallback = wl_surface_frame(waylandSurface);
+    wl_callback_add_listener(mFrameCallback, &frame_listener, this);
+    mFrameCallbackSurface = waylandSurface;
+
+    mFrontBuffer->Attach(waylandSurface);
+    mDelayedCommit = false;
+  }
+}
+
 }  // namespace widget
 }  // namespace mozilla
--- a/widget/gtk/WindowSurfaceWayland.h
+++ b/widget/gtk/WindowSurfaceWayland.h
@@ -69,21 +69,21 @@ public:
 
   void Attach(wl_surface* aSurface);
   void Detach();
   bool IsAttached() { return mAttached; }
 
   bool Resize(int aWidth, int aHeight);
   bool SetImageDataFromBackBuffer(class WindowBackBuffer* aSourceBuffer);
 
-  bool MatchSize(int aWidth, int aHeight)
+  bool IsMatchingSize(int aWidth, int aHeight)
   {
     return aWidth == mWidth && aHeight == mHeight;
   }
-  bool MatchSize(class WindowBackBuffer *aBuffer)
+  bool IsMatchingSize(class WindowBackBuffer *aBuffer)
   {
     return aBuffer->mWidth == mWidth && aBuffer->mHeight == mHeight;
   }
 
 private:
   void Create(int aWidth, int aHeight);
   void Release();
 
@@ -94,12 +94,38 @@ private:
   // and passes it to wayland compositor by wl_surface object.
   wl_buffer*          mWaylandBuffer;
   int                 mWidth;
   int                 mHeight;
   bool                mAttached;
   nsWaylandDisplay*   mWaylandDisplay;
 };
 
+// WindowSurfaceWayland is an abstraction for wl_surface
+// and related management
+class WindowSurfaceWayland : public WindowSurface {
+public:
+  WindowSurfaceWayland(nsWindow *aWindow);
+  ~WindowSurfaceWayland();
+
+  already_AddRefed<gfx::DrawTarget> Lock(const LayoutDeviceIntRegion& aRegion) override;
+  void                      Commit(const LayoutDeviceIntRegion& aInvalidRegion) final;
+  void                      FrameCallbackHandler();
+
+private:
+  WindowBackBuffer*         GetBufferToDraw(int aWidth, int aHeight);
+
+  // TODO: Do we need to hold a reference to nsWindow object?
+  nsWindow*                 mWindow;
+  nsWaylandDisplay*         mWaylandDisplay;
+  WindowBackBuffer*         mFrontBuffer;
+  WindowBackBuffer*         mBackBuffer;
+  wl_callback*              mFrameCallback;
+  wl_surface*               mFrameCallbackSurface;
+  bool                      mDelayedCommit;
+  bool                      mFullScreenDamage;
+  bool                      mIsMainThread;
+};
+
 }  // namespace widget
 }  // namespace mozilla
 
 #endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_H