Bug 1422966 - Implement SurfaceWayland for Wayland, r?jhorak draft
authorMartin Stransky <stransky@redhat.com>
Mon, 04 Dec 2017 22:29:08 +0100
changeset 712044 51c264306d7547143f0e905e62a4d98bc7c58c2a
parent 711990 7d281617386ca8b6042763ade14cd0adb66a790a
child 743957 427ab962ada04bd236d55380844fda67b177adfb
push id93232
push userstransky@redhat.com
push dateFri, 15 Dec 2017 15:10:08 +0000
reviewersjhorak
bugs1422966
milestone59.0a1
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