Bug 1467128 - [Wayland] Get VSync from Gtk/Wayland, r=lsalzman
authorMartin Stransky <stransky@redhat.com>
Fri, 29 Jun 2018 13:15:41 +0200
changeset 813059 d1c14db600431c187fbade3dcf218778fc309710
parent 812953 ae034e2f6e1a8b853ef73aa740b3e7a06f45a145
child 813060 8c7b5e66c06cb8e5196909d1c421628b0ee3fc26
push id114752
push userbmo:dave.hunt@gmail.com
push dateMon, 02 Jul 2018 10:28:01 +0000
reviewerslsalzman
bugs1467128
milestone63.0a1
Bug 1467128 - [Wayland] Get VSync from Gtk/Wayland, r=lsalzman VSync on Wayland is a bit tricky as we can get only "last VSync" event signal with CLOCK_MONOTONIC timestamp or none (if application is hidden/minimized). That means we should draw a next frame at "last Vsync + frame delay" time and also approximate next VSync event when we don't get any. MozReview-Commit-ID: FI3Z4nkmDNK
gfx/thebes/gfxPlatformGtk.cpp
gfx/thebes/gfxPlatformGtk.h
widget/gtk/WindowSurfaceWayland.cpp
--- a/gfx/thebes/gfxPlatformGtk.cpp
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -46,16 +46,20 @@
 #include "GLContextGLX.h"
 #include "GLXLibrary.h"
 
 /* Undefine the Status from Xlib since it will conflict with system headers on OSX */
 #if defined(__APPLE__) && defined(Status)
 #undef Status
 #endif
 
+#ifdef MOZ_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif
+
 #endif /* MOZ_X11 */
 
 #include <fontconfig/fontconfig.h>
 
 #include "nsMathUtils.h"
 
 #define GDK_PIXMAP_SIZE_MAX 32767
 
@@ -89,16 +93,22 @@ gfxPlatformGtk::gfxPlatformGtk()
 #ifdef MOZ_X11
     if (gfxPlatform::IsHeadless() && GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
       mCompositorDisplay = XOpenDisplay(nullptr);
       MOZ_ASSERT(mCompositorDisplay, "Failed to create compositor display!");
     } else {
       mCompositorDisplay = nullptr;
     }
 #endif // MOZ_X11
+#ifdef MOZ_WAYLAND
+    // Wayland compositors use g_get_monotonic_time() to get timestamps.
+    mWaylandLastVsyncTimestamp = (g_get_monotonic_time() / 1000);
+    // Set default display fps to 60
+    mWaylandFrameDelay = 1000/60;
+#endif
 }
 
 gfxPlatformGtk::~gfxPlatformGtk()
 {
 #ifdef MOZ_X11
     if (mCompositorDisplay) {
       XCloseDisplay(mCompositorDisplay);
     }
@@ -505,26 +515,26 @@ gfxPlatformGtk::CheckVariationFontSuppor
   // until at least 2.7.1.
   FT_Int major, minor, patch;
   FT_Library_Version(GetFTLibrary(), &major, &minor, &patch);
   return major * 1000000 + minor * 1000 + patch >= 2007001;
 }
 
 #ifdef MOZ_X11
 
-class GLXVsyncSource final : public VsyncSource
+class GtkVsyncSource final : public VsyncSource
 {
 public:
-  GLXVsyncSource()
+  GtkVsyncSource()
   {
     MOZ_ASSERT(NS_IsMainThread());
     mGlobalDisplay = new GLXDisplay();
   }
 
-  virtual ~GLXVsyncSource()
+  virtual ~GtkVsyncSource()
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
   virtual Display& GetGlobalDisplay() override
   {
     return *mGlobalDisplay;
   }
@@ -536,39 +546,52 @@ public:
   public:
     GLXDisplay() : mGLContext(nullptr)
                  , mXDisplay(nullptr)
                  , mSetupLock("GLXVsyncSetupLock")
                  , mVsyncThread("GLXVsyncThread")
                  , mVsyncTask(nullptr)
                  , mVsyncEnabledLock("GLXVsyncEnabledLock")
                  , mVsyncEnabled(false)
+#ifdef MOZ_WAYLAND
+                 , mIsWaylandDisplay(false)
+#endif
     {
     }
 
     // Sets up the display's GL context on a worker thread.
     // Required as GLContexts may only be used by the creating thread.
     // Returns true if setup was a success.
     bool Setup()
     {
       MonitorAutoLock lock(mSetupLock);
       MOZ_ASSERT(NS_IsMainThread());
       if (!mVsyncThread.Start())
         return false;
 
       RefPtr<Runnable> vsyncSetup =
-        NewRunnableMethod("GLXVsyncSource::GLXDisplay::SetupGLContext",
+        NewRunnableMethod("GtkVsyncSource::GLXDisplay::SetupGLContext",
                           this,
                           &GLXDisplay::SetupGLContext);
       mVsyncThread.message_loop()->PostTask(vsyncSetup.forget());
       // Wait until the setup has completed.
       lock.Wait();
       return mGLContext != nullptr;
     }
 
+#ifdef MOZ_WAYLAND
+    bool SetupWayland()
+    {
+      MonitorAutoLock lock(mSetupLock);
+      MOZ_ASSERT(NS_IsMainThread());
+      mIsWaylandDisplay = true;
+      return mVsyncThread.Start();
+    }
+#endif
+
     // Called on the Vsync thread to setup the GL context.
     void SetupGLContext()
     {
         MonitorAutoLock lock(mSetupLock);
         MOZ_ASSERT(!NS_IsMainThread());
         MOZ_ASSERT(!mGLContext, "GLContext already setup!");
 
         // Create video sync timer on a separate Display to prevent locking the
@@ -613,29 +636,35 @@ public:
         }
 
         lock.NotifyAll();
     }
 
     virtual void EnableVsync() override
     {
       MOZ_ASSERT(NS_IsMainThread());
+#if !defined(MOZ_WAYLAND)
       MOZ_ASSERT(mGLContext, "GLContext not setup!");
+#endif
 
       MonitorAutoLock lock(mVsyncEnabledLock);
       if (mVsyncEnabled) {
         return;
       }
       mVsyncEnabled = true;
 
       // If the task has not nulled itself out, it hasn't yet realized
       // that vsync was disabled earlier, so continue its execution.
       if (!mVsyncTask) {
         mVsyncTask = NewRunnableMethod(
-          "GLXVsyncSource::GLXDisplay::RunVsync", this, &GLXDisplay::RunVsync);
+          "GtkVsyncSource::GLXDisplay::RunVsync", this,
+#if defined(MOZ_WAYLAND)
+          mIsWaylandDisplay ? &GLXDisplay::RunVsyncWayland :
+#endif
+          &GLXDisplay::RunVsync);
         RefPtr<Runnable> addrefedTask = mVsyncTask;
         mVsyncThread.message_loop()->PostTask(addrefedTask.forget());
       }
     }
 
     virtual void DisableVsync() override
     {
       MonitorAutoLock lock(mVsyncEnabledLock);
@@ -650,17 +679,17 @@ public:
 
     virtual void Shutdown() override
     {
       MOZ_ASSERT(NS_IsMainThread());
       DisableVsync();
 
       // Cleanup thread-specific resources before shutting down.
       RefPtr<Runnable> shutdownTask = NewRunnableMethod(
-        "GLXVsyncSource::GLXDisplay::Cleanup", this, &GLXDisplay::Cleanup);
+        "GtkVsyncSource::GLXDisplay::Cleanup", this, &GLXDisplay::Cleanup);
       mVsyncThread.message_loop()->PostTask(shutdownTask.forget());
 
       // Stop, waiting for the cleanup task to finish execution.
       mVsyncThread.Stop();
     }
 
   private:
     virtual ~GLXDisplay()
@@ -709,50 +738,96 @@ public:
           }
         }
 
         lastVsync = TimeStamp::Now();
         NotifyVsync(lastVsync);
       }
     }
 
+#ifdef MOZ_WAYLAND
+    /* VSync on Wayland is tricky as we can get only "last VSync" event signal.
+     * That means we should draw next frame at "last Vsync + frame delay" time.
+     */
+    void RunVsyncWayland()
+    {
+      MOZ_ASSERT(!NS_IsMainThread());
+
+      for (;;) {
+        {
+          MonitorAutoLock lock(mVsyncEnabledLock);
+          if (!mVsyncEnabled) {
+            mVsyncTask = nullptr;
+            return;
+          }
+        }
+
+        gint64 lastVsync = gfxPlatformGtk::GetPlatform()->GetWaylandLastVsync();
+        gint64 currTime = (g_get_monotonic_time() / 1000);
+
+        gint64 remaining = gfxPlatformGtk::GetPlatform()->GetWaylandFrameDelay() -
+          (currTime - lastVsync);
+        if (remaining > 0) {
+          PlatformThread::Sleep(remaining);
+        } else {
+          // Time from last HW Vsync is longer than our frame delay,
+          // use our approximation then.
+          gfxPlatformGtk::GetPlatform()->SetWaylandLastVsync(currTime);
+        }
+
+        NotifyVsync(TimeStamp::Now());
+      }
+    }
+#endif
+
     void Cleanup() {
       MOZ_ASSERT(!NS_IsMainThread());
 
       mGLContext = nullptr;
-      XCloseDisplay(mXDisplay);
+      if (mXDisplay)
+        XCloseDisplay(mXDisplay);
     }
 
     // Owned by the vsync thread.
     RefPtr<gl::GLContextGLX> mGLContext;
     _XDisplay* mXDisplay;
     Monitor mSetupLock;
     base::Thread mVsyncThread;
     RefPtr<Runnable> mVsyncTask;
     Monitor mVsyncEnabledLock;
     bool mVsyncEnabled;
+#ifdef MOZ_WAYLAND
+    bool mIsWaylandDisplay;
+#endif
   };
 private:
   // We need a refcounted VsyncSource::Display to use chromium IPC runnables.
   RefPtr<GLXDisplay> mGlobalDisplay;
 };
 
 already_AddRefed<gfx::VsyncSource>
 gfxPlatformGtk::CreateHardwareVsyncSource()
 {
+#ifdef MOZ_WAYLAND
+  if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) {
+    RefPtr<VsyncSource> vsyncSource = new GtkVsyncSource();
+    VsyncSource::Display& display = vsyncSource->GetGlobalDisplay();
+    static_cast<GtkVsyncSource::GLXDisplay&>(display).SetupWayland();
+    return vsyncSource.forget();
+  }
+#endif
+
   // Only use GLX vsync when the OpenGL compositor is being used.
   // The extra cost of initializing a GLX context while blocking the main
   // thread is not worth it when using basic composition.
-  // Also don't use it on non-X11 displays.
   if (gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
-    if (GDK_IS_X11_DISPLAY(gdk_display_get_default()) &&
-        gl::sGLXLibrary.SupportsVideoSync()) {
-      RefPtr<VsyncSource> vsyncSource = new GLXVsyncSource();
+    if (gl::sGLXLibrary.SupportsVideoSync()) {
+      RefPtr<VsyncSource> vsyncSource = new GtkVsyncSource();
       VsyncSource::Display& display = vsyncSource->GetGlobalDisplay();
-      if (!static_cast<GLXVsyncSource::GLXDisplay&>(display).Setup()) {
+      if (!static_cast<GtkVsyncSource::GLXDisplay&>(display).Setup()) {
         NS_WARNING("Failed to setup GLContext, falling back to software vsync.");
         return gfxPlatform::CreateHardwareVsyncSource();
       }
       return vsyncSource.forget();
     }
     NS_WARNING("SGI_video_sync unsupported. Falling back to software vsync.");
   }
   return gfxPlatform::CreateHardwareVsyncSource();
--- a/gfx/thebes/gfxPlatformGtk.h
+++ b/gfx/thebes/gfxPlatformGtk.h
@@ -102,23 +102,42 @@ public:
 #endif
 
 #ifdef MOZ_X11
     Display* GetCompositorDisplay() {
       return mCompositorDisplay;
     }
 #endif // MOZ_X11
 
+#ifdef MOZ_WAYLAND
+    void SetWaylandLastVsync(uint32_t aVsyncTimestamp) {
+      mWaylandLastVsyncTimestamp = aVsyncTimestamp;
+    }
+    int64_t GetWaylandLastVsync() {
+      return mWaylandLastVsyncTimestamp;
+    }
+    void SetWaylandFrameDelay(int64_t aFrameDelay) {
+      mWaylandFrameDelay = aFrameDelay;
+    }
+    int64_t GetWaylandFrameDelay() {
+      return mWaylandFrameDelay;
+    }
+#endif
+
 protected:
     bool CheckVariationFontSupport() override;
 
     int8_t mMaxGenericSubstitutions;
 
 private:
     virtual void GetPlatformCMSOutputProfile(void *&mem,
                                              size_t &size) override;
 
 #ifdef MOZ_X11
     Display* mCompositorDisplay;
 #endif
+#ifdef MOZ_WAYLAND
+    int64_t  mWaylandLastVsyncTimestamp;
+    int64_t  mWaylandFrameDelay;
+#endif
 };
 
 #endif /* GFX_PLATFORM_GTK_H */
--- a/widget/gtk/WindowSurfaceWayland.cpp
+++ b/widget/gtk/WindowSurfaceWayland.cpp
@@ -546,16 +546,18 @@ WindowBackBuffer::Lock()
                                               mWaylandDisplay->GetSurfaceFormat());
 }
 
 static void
 frame_callback_handler(void *data, struct wl_callback *callback, uint32_t time)
 {
   auto surface = reinterpret_cast<WindowSurfaceWayland*>(data);
   surface->FrameCallbackHandler();
+
+  gfxPlatformGtk::GetPlatform()->SetWaylandLastVsync(time);
 }
 
 static const struct wl_callback_listener frame_listener = {
   frame_callback_handler
 };
 
 WindowSurfaceWayland::WindowSurfaceWayland(nsWindow *aWindow)
   : mWindow(aWindow)