[gtk+/wip/chergert/quartz-frame-clock: 2/2] quartz: add GdkDisplayLinkSource and sync to frame clock



commit f7c5b93062a81066721f478e6fb5a669df9d48ed
Author: Christian Hergert <christian hergert me>
Date:   Fri Sep 4 14:06:55 2015 -0700

    quartz: add GdkDisplayLinkSource and sync to frame clock
    
    This is a GSource that will fire when a new frame should be drawn.
    It receives callbacks from CVDisplayLink on the high-priority thread
    and then marshals that request to the GSource.

 gdk/quartz/Makefile.am            |    2 +
 gdk/quartz/gdkdisplay-quartz.c    |  117 +++++++++++++++++++
 gdk/quartz/gdkdisplaylinksource.c |  228 +++++++++++++++++++++++++++++++++++++
 gdk/quartz/gdkdisplaylinksource.h |   48 ++++++++
 gdk/quartz/gdkprivate-quartz.h    |   13 ++
 gdk/quartz/gdkwindow-quartz.c     |   49 ++++++++
 6 files changed, 457 insertions(+), 0 deletions(-)
---
diff --git a/gdk/quartz/Makefile.am b/gdk/quartz/Makefile.am
index 8954849..f7c30e9 100644
--- a/gdk/quartz/Makefile.am
+++ b/gdk/quartz/Makefile.am
@@ -28,6 +28,8 @@ libgdk_quartz_la_SOURCES =            \
        gdkdevicemanager-core-quartz.c  \
        gdkdevicemanager-core-quartz.h  \
        gdkdisplay-quartz.c     \
+       gdkdisplaylinksource.c  \
+       gdkdisplaylinksource.h  \
        gdkdisplaymanager-quartz.c      \
        gdkdnd-quartz.c         \
        gdkdnd-quartz.h         \
diff --git a/gdk/quartz/gdkdisplay-quartz.c b/gdk/quartz/gdkdisplay-quartz.c
index 6b4afc8..82a00cd 100644
--- a/gdk/quartz/gdkdisplay-quartz.c
+++ b/gdk/quartz/gdkdisplay-quartz.c
@@ -20,12 +20,14 @@
 
 #include <gdk/gdk.h>
 #include <gdk/gdkdisplayprivate.h>
+#include <gdk/gdkframeclockprivate.h>
 
 #include "gdkprivate-quartz.h"
 #include "gdkquartzscreen.h"
 #include "gdkquartzwindow.h"
 #include "gdkquartzdisplay.h"
 #include "gdkquartzdevicemanager-core.h"
+#include "gdkdisplaylinksource.h"
 
 
 struct _GdkQuartzDisplay
@@ -33,6 +35,9 @@ struct _GdkQuartzDisplay
   GdkDisplay display;
 
   GList *input_devices;
+
+  GSource *frame_source;
+  GArray  *frame_handlers;
 };
 
 struct _GdkQuartzDisplayClass
@@ -40,6 +45,12 @@ struct _GdkQuartzDisplayClass
   GdkDisplayClass display_class;
 };
 
+typedef struct
+{
+  GdkQuartzFrameCallback callback;
+  GdkWindow *window;
+} FrameHandler;
+
 static GdkWindow *
 gdk_quartz_display_get_default_group (GdkDisplay *display)
 {
@@ -108,6 +119,107 @@ gdk_quartz_display_init_input (GdkDisplay *display)
   g_list_free (list);
 }
 
+void
+_gdk_quartz_display_add_frame_callback (GdkDisplay             *display,
+                                        GdkQuartzFrameCallback  callback,
+                                        GdkWindow              *window)
+{
+  GdkQuartzDisplay *display_quartz;
+  FrameHandler handler;
+
+  display_quartz = GDK_QUARTZ_DISPLAY (display);
+
+  handler.callback = callback;
+  handler.window = window;
+
+  if (display_quartz->frame_handlers == NULL)
+    display_quartz->frame_handlers = g_array_new (FALSE, FALSE, sizeof (FrameHandler));
+
+  g_array_append_val (display_quartz->frame_handlers, handler);
+
+  if (display_quartz->frame_handlers->len == 1)
+    gdk_display_link_source_unpause ((GdkDisplayLinkSource *)display_quartz->frame_source);
+}
+
+void
+_gdk_quartz_display_remove_frame_callback (GdkDisplay             *display,
+                                           GdkQuartzFrameCallback  callback,
+                                           GdkWindow              *window)
+{
+  GdkQuartzDisplay *display_quartz;
+  gint i;
+
+  display_quartz = GDK_QUARTZ_DISPLAY (display);
+
+  for (i = 0; i < display_quartz->frame_handlers->len; i++)
+    {
+      FrameHandler *handler = &g_array_index (display_quartz->frame_handlers, FrameHandler, i);
+
+      if ((handler->window == window) && (handler->callback == callback))
+        {
+          g_array_remove_index_fast (display_quartz->frame_handlers, i);
+          break;
+        }
+    }
+
+  if (display_quartz->frame_handlers->len == 0)
+    gdk_display_link_source_pause ((GdkDisplayLinkSource *)display_quartz->frame_source);
+}
+
+static gboolean
+gdk_quartz_display_frame_cb (gpointer data)
+{
+  GdkDisplayLinkSource *source;
+  GdkQuartzDisplay *display_quartz = data;
+  GdkDisplay *display = data;
+  GArray *frame_handlers;
+  gint64 presentation_time;
+  gint64 now;
+  guint i;
+
+  source = (GdkDisplayLinkSource *)display_quartz->frame_source;
+
+  frame_handlers = display_quartz->frame_handlers;
+  display_quartz->frame_handlers = NULL;
+
+  if (frame_handlers == NULL)
+    {
+      gdk_display_link_source_pause (source);
+      return G_SOURCE_CONTINUE;
+    }
+
+  presentation_time = source->presentation_time;
+  now = g_source_get_time (display_quartz->frame_source);
+
+  for (i = 0; i < frame_handlers->len; i++)
+    {
+      FrameHandler *handler = &g_array_index (frame_handlers, FrameHandler, i);
+
+      handler->callback (display,
+                         handler->window,
+                         source->refresh_interval,
+                         now,
+                         source->presentation_time);
+    }
+
+  g_array_unref (frame_handlers);
+
+  return G_SOURCE_CONTINUE;
+}
+
+static void
+gdk_quartz_display_init_display_link (GdkDisplay *display)
+{
+  GdkQuartzDisplay *display_quartz = GDK_QUARTZ_DISPLAY (display);
+
+  display_quartz->frame_source = gdk_display_link_source_new ();
+  g_source_set_callback (display_quartz->frame_source,
+                         gdk_quartz_display_frame_cb,
+                         display,
+                         NULL);
+  g_source_attach (display_quartz->frame_source, NULL);
+}
+
 GdkDisplay *
 _gdk_quartz_display_open (const gchar *display_name)
 {
@@ -129,6 +241,8 @@ _gdk_quartz_display_open (const gchar *display_name)
 
   gdk_quartz_display_init_input (_gdk_display);
 
+  gdk_quartz_display_init_display_link (_gdk_display);
+
 #if 0
   /* FIXME: Remove the #if 0 when we have these functions */
   _gdk_quartz_dnd_init ();
@@ -284,6 +398,9 @@ gdk_quartz_display_finalize (GObject *object)
 
   g_list_free_full (display_quartz->input_devices, g_object_unref);
 
+  g_source_unref (display_quartz->frame_source);
+  g_clear_pointer (&display_quartz->frame_handlers, g_array_unref);
+
   G_OBJECT_CLASS (gdk_quartz_display_parent_class)->finalize (object);
 }
 
diff --git a/gdk/quartz/gdkdisplaylinksource.c b/gdk/quartz/gdkdisplaylinksource.c
new file mode 100644
index 0000000..202918f
--- /dev/null
+++ b/gdk/quartz/gdkdisplaylinksource.c
@@ -0,0 +1,228 @@
+/* gdkdisplaylinksource.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *   Christian Hergert <christian hergert me>
+ */
+
+#include "config.h"
+
+#include <mach/mach_time.h>
+
+#include "gdkdisplaylinksource.h"
+
+static gint64 host_to_frame_clock_time (gint64 host_time);
+
+static gboolean
+gdk_display_link_source_prepare (GSource *source,
+                                 gint    *timeout_)
+{
+  GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+
+       *timeout_ = -1;
+
+  return impl->needs_dispatch;
+}
+
+static gboolean
+gdk_display_link_source_check (GSource *source)
+{
+  GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+  return impl->needs_dispatch;
+}
+
+static gboolean
+gdk_display_link_source_dispatch (GSource     *source,
+                                  GSourceFunc  callback,
+                                  gpointer     user_data)
+{
+  GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+  gboolean ret = G_SOURCE_CONTINUE;
+
+  impl->needs_dispatch = FALSE;
+
+  if (callback != NULL)
+    ret = callback (user_data);
+
+  return ret;
+}
+
+static void
+gdk_display_link_source_finalize (GSource *source)
+{
+  GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
+
+  /*
+   * TODO: ask worker to destroy itself, and then unpause it
+   *       so that it can do so.
+   */
+
+  CVDisplayLinkStop (impl->display_link);
+  CVDisplayLinkRelease (impl->display_link);
+}
+
+static GSourceFuncs gdk_display_link_source_funcs = {
+  gdk_display_link_source_prepare,
+  gdk_display_link_source_check,
+  gdk_display_link_source_dispatch,
+  gdk_display_link_source_finalize
+};
+
+void
+gdk_display_link_source_pause (GdkDisplayLinkSource *source)
+{
+  CVDisplayLinkStop (source->display_link);
+}
+
+void
+gdk_display_link_source_unpause (GdkDisplayLinkSource *source)
+{
+  CVDisplayLinkStart (source->display_link);
+}
+
+static CVReturn
+gdk_display_link_source_frame_cb (CVDisplayLinkRef   display_link,
+                                  const CVTimeStamp *inNow,
+                                  const CVTimeStamp *inOutputTime,
+                                  CVOptionFlags      flagsIn,
+                                  CVOptionFlags     *flagsOut,
+                                  void              *user_data)
+{
+  GdkDisplayLinkSource *impl = user_data;
+  GMainContext *context;
+  gint64 presentation_time;
+
+  presentation_time = host_to_frame_clock_time (inOutputTime->hostTime);
+
+  impl->presentation_time = presentation_time;
+  impl->needs_dispatch = TRUE;
+
+  context = g_source_get_context ((GSource *)impl);
+  if (context != NULL)
+    g_main_context_wakeup (context);
+
+  return kCVReturnSuccess;
+}
+
+/**
+ * gdk_display_link_source_new:
+ *
+ * Creates a new #GSource that will activate the dispatch function upon
+ * notification from a CVDisplayLink that a new frame should be drawn.
+ *
+ * Effort is made to keep the transition from the high-priority
+ * CVDisplayLink thread into this GSource lightweight. However, this is
+ * somewhat non-ideal since the best case would be to do the drawing
+ * from the high-priority thread.
+ *
+ * Returns: (transfer full): A newly created #GSource.
+ */
+GSource *
+gdk_display_link_source_new (void)
+{
+  GdkDisplayLinkSource *impl;
+  GSource *source;
+  CVReturn ret;
+  double period;
+
+  source = g_source_new (&gdk_display_link_source_funcs, sizeof *impl);
+  impl = (GdkDisplayLinkSource *)source;
+
+  /*
+   * Create our link based on currently connected displays.
+   * If there are multiple displays, this will be something that tries
+   * to work for all of them. In the future, we may want to explore multiple
+   * links based on the connected displays.
+   */
+  ret = CVDisplayLinkCreateWithActiveCGDisplays (&impl->display_link);
+  if (ret != kCVReturnSuccess)
+    {
+      g_warning ("Failed to initialize CVDisplayLink!");
+      return source;
+    }
+
+  /*
+   * Determine our nominal period between frames.
+   */
+  period = CVDisplayLinkGetActualOutputVideoRefreshPeriod (impl->display_link);
+  if (period == 0.0)
+    period = 1.0 / 60.0;
+  impl->refresh_interval = period * 1000000L;
+
+
+  /*
+   * Wire up our callback to be executed within the high-priority thread.
+   */
+  CVDisplayLinkSetOutputCallback (impl->display_link,
+                                  gdk_display_link_source_frame_cb,
+                                  source);
+
+  g_source_set_name (source, "[gdk] quartz frame timer");
+
+  return source;
+}
+
+static gint64
+host_to_frame_clock_time (gint64 host_time)
+{
+  static mach_timebase_info_data_t timebase_info;
+
+  /*
+   * NOTE:
+   *
+   * This code is taken from GLib to match g_get_monotonic_time().
+   */
+  if (timebase_info.denom == 0)
+    {
+      /* This is a fraction that we must use to scale
+       * mach_absolute_time() by in order to reach nanoseconds.
+       *
+       * We've only ever observed this to be 1/1, but maybe it could be
+       * 1000/1 if mach time is microseconds already, or 1/1000 if
+       * picoseconds.  Try to deal nicely with that.
+       */
+      mach_timebase_info (&timebase_info);
+
+      /* We actually want microseconds... */
+      if (timebase_info.numer % 1000 == 0)
+        timebase_info.numer /= 1000;
+      else
+        timebase_info.denom *= 1000;
+
+      /* We want to make the numer 1 to avoid having to multiply... */
+      if (timebase_info.denom % timebase_info.numer == 0)
+        {
+          timebase_info.denom /= timebase_info.numer;
+          timebase_info.numer = 1;
+        }
+      else
+        {
+          /* We could just multiply by timebase_info.numer below, but why
+           * bother for a case that may never actually exist...
+           *
+           * Plus -- performing the multiplication would risk integer
+           * overflow.  If we ever actually end up in this situation, we
+           * should more carefully evaluate the correct course of action.
+           */
+          mach_timebase_info (&timebase_info); /* Get a fresh copy for a better message */
+          g_error ("Got weird mach timebase info of %d/%d.  Please file a bug against GLib.",
+                   timebase_info.numer, timebase_info.denom);
+        }
+    }
+
+  return host_time / timebase_info.denom;
+}
diff --git a/gdk/quartz/gdkdisplaylinksource.h b/gdk/quartz/gdkdisplaylinksource.h
new file mode 100644
index 0000000..805a9e0
--- /dev/null
+++ b/gdk/quartz/gdkdisplaylinksource.h
@@ -0,0 +1,48 @@
+/* gdkdisplaylinksource.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *   Christian Hergert <christian hergert me>
+ */
+
+#ifndef GDK_DISPLAY_LINK_SOURCE_H
+#define GDK_DISPLAY_LINK_SOURCE_H
+
+#include <glib.h>
+
+#include <QuartzCore/QuartzCore.h>
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+  GSource          source;
+
+  CVDisplayLinkRef display_link;
+  gint64           refresh_interval;
+
+  volatile gint64  presentation_time;
+  volatile guint   needs_dispatch : 1;
+} GdkDisplayLinkSource;
+
+GSource *gdk_display_link_source_new     (void);
+void     gdk_display_link_source_pause   (GdkDisplayLinkSource *source);
+void     gdk_display_link_source_unpause (GdkDisplayLinkSource *source);
+
+G_END_DECLS
+
+#endif /* GDK_DISPLAY_LINK_SOURCE_H */
diff --git a/gdk/quartz/gdkprivate-quartz.h b/gdk/quartz/gdkprivate-quartz.h
index 5d2fcc1..67a392a 100644
--- a/gdk/quartz/gdkprivate-quartz.h
+++ b/gdk/quartz/gdkprivate-quartz.h
@@ -125,6 +125,19 @@ void       _gdk_quartz_display_create_window_impl (GdkDisplay    *display,
                                                    GdkWindowAttr *attributes,
                                                    gint           attributes_mask);
 
+/* Display methods - frame clock */
+typedef void (*GdkQuartzFrameCallback) (GdkDisplay *display,
+                                        GdkWindow  *window,
+                                        gint64      refresh_interval,
+                                        gint64      now,
+                                        gint64      presentation_time);
+void        _gdk_quartz_display_add_frame_callback    (GdkDisplay             *display,
+                                                       GdkQuartzFrameCallback  callback,
+                                                       GdkWindow              *window);
+void        _gdk_quartz_display_remove_frame_callback (GdkDisplay             *display,
+                                                       GdkQuartzFrameCallback  callback,
+                                                       GdkWindow              *window);
+
 /* Display methods - keymap */
 GdkKeymap * _gdk_quartz_display_get_keymap (GdkDisplay *display);
 
diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c
index 62710a2..ee1a272 100644
--- a/gdk/quartz/gdkwindow-quartz.c
+++ b/gdk/quartz/gdkwindow-quartz.c
@@ -22,6 +22,7 @@
 #include <gdk/gdk.h>
 #include <gdk/gdkdeviceprivate.h>
 #include <gdk/gdkdisplayprivate.h>
+#include <gdk/gdkframeclockprivate.h>
 
 #include "gdkwindowimpl.h"
 #include "gdkprivate-quartz.h"
@@ -776,6 +777,46 @@ get_nsscreen_for_point (gint x, gint y)
   return screen;
 }
 
+static void
+frame_callback (GdkDisplay *display,
+                GdkWindow  *window,
+                gint64      refresh_interval,
+                gint64      now,
+                gint64      presentation_time)
+{
+  GdkFrameClock *frame_clock;
+  GdkFrameTimings *timings;
+
+  frame_clock = gdk_window_get_frame_clock (window);
+  if (frame_clock == NULL)
+    return;
+
+  _gdk_frame_clock_thaw (frame_clock);
+
+  timings = gdk_frame_clock_get_current_timings (frame_clock);
+  if (timings == NULL)
+    return;
+
+  timings->refresh_interval = refresh_interval;
+  timings->presentation_time = presentation_time;
+}
+
+static void
+on_frame_clock_before_paint (GdkFrameClock *frame_clock,
+                             GdkWindow     *window)
+{
+}
+
+static void
+on_frame_clock_after_paint (GdkFrameClock *frame_clock,
+                            GdkWindow     *window)
+{
+  GdkDisplay *display = gdk_window_get_display (window);
+
+  _gdk_quartz_display_add_frame_callback (display, frame_callback, window);
+  _gdk_frame_clock_freeze (frame_clock);
+}
+
 void
 _gdk_quartz_display_create_window_impl (GdkDisplay    *display,
                                         GdkWindow     *window,
@@ -787,6 +828,7 @@ _gdk_quartz_display_create_window_impl (GdkDisplay    *display,
 {
   GdkWindowImplQuartz *impl;
   GdkWindowImplQuartz *parent_impl;
+  GdkFrameClock *frame_clock;
 
   GDK_QUARTZ_ALLOC_POOL;
 
@@ -921,6 +963,13 @@ _gdk_quartz_display_create_window_impl (GdkDisplay    *display,
 
   if (attributes_mask & GDK_WA_TYPE_HINT)
     gdk_window_set_type_hint (window, attributes->type_hint);
+
+  frame_clock = gdk_window_get_frame_clock (window);
+
+  g_signal_connect (frame_clock, "before-paint",
+                    G_CALLBACK (on_frame_clock_before_paint), window);
+  g_signal_connect (frame_clock, "after-paint",
+                    G_CALLBACK (on_frame_clock_after_paint), window);
 }
 
 void


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]