[gtk/wip/nacho/quartz-frame-clock: 17/20] quartz: add CVDisplayLink based frame clock



commit bf75111a0062e08fa690ff95914d6aeec0f578e7
Author: Christian Hergert <christian hergert me>
Date:   Wed Sep 30 18:24:26 2015 -0700

    quartz: add CVDisplayLink based frame clock
    
    This uses CVDisplayLink to drive the GdkFrameClock. A GdkWindow
    can register a frame callback to thaw their frame clock as necessary
    based on the next notification from CVDisplayLink.
    
    CVDisplayLink notifies us on a high-priority thread. We use the same
    NSEventas gdkeventloop-quartz.c to wakeup the main loop. This is done
    so that we don’t pathologically wake up the select thread to then
    continue notifying the main loop.
    
    We use an embedded GList node in the GdkWindowImplQuartz so that we
    can avoid allocating any lists or arrays for pending frame callbacks.
    Compare this to the same design in GdkWindow for children.

 gdk/quartz/Makefile.am            |   2 +
 gdk/quartz/gdkdisplay-quartz.c    | 115 +++++++++++++++++
 gdk/quartz/gdkdisplay-quartz.h    |   5 +
 gdk/quartz/gdkdisplaylinksource.c | 251 ++++++++++++++++++++++++++++++++++++++
 gdk/quartz/gdkdisplaylinksource.h |  48 ++++++++
 gdk/quartz/gdkprivate-quartz.h    |   6 +
 gdk/quartz/gdkwindow-quartz.c     |  37 ++++++
 gdk/quartz/gdkwindow-quartz.h     |   3 +
 8 files changed, 467 insertions(+)
---
diff --git a/gdk/quartz/Makefile.am b/gdk/quartz/Makefile.am
index 9646f8687c..8dc4223c98 100644
--- a/gdk/quartz/Makefile.am
+++ b/gdk/quartz/Makefile.am
@@ -30,6 +30,8 @@ libgdk_quartz_la_SOURCES =            \
        gdkdevicemanager-core-quartz.h  \
        gdkdisplay-quartz.c     \
        gdkdisplay-quartz.h     \
+       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 800f6edae1..c2964fb171 100644
--- a/gdk/quartz/gdkdisplay-quartz.c
+++ b/gdk/quartz/gdkdisplay-quartz.c
@@ -21,6 +21,7 @@
 #include <gdk/gdk.h>
 #include <gdk/gdkdisplayprivate.h>
 #include <gdk/gdkmonitorprivate.h>
+#include <gdk/gdkframeclockprivate.h>
 
 #include "gdkprivate-quartz.h"
 #include "gdkquartzscreen.h"
@@ -32,6 +33,7 @@
 #include "gdkdisplay-quartz.h"
 #include "gdkmonitor-quartz.h"
 #include "gdkglcontext-quartz.h"
+#include "gdkdisplaylinksource.h"
 
 /* Note about coordinates: There are three coordinate systems at play:
  *
@@ -82,6 +84,112 @@ _gdk_device_manager_new (GdkDisplay *display)
                        NULL);
 }
 
+void
+_gdk_quartz_display_add_frame_callback (GdkDisplay             *display,
+                                        GdkWindow              *window)
+{
+  GdkQuartzDisplay *display_quartz;
+  GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window);
+
+  display_quartz = GDK_QUARTZ_DISPLAY (display);
+
+  impl->frame_link.data = window;
+  impl->frame_link.prev = NULL;
+  impl->frame_link.next = display_quartz->windows_awaiting_frame;
+
+  display_quartz->windows_awaiting_frame = &impl->frame_link;
+
+  if (impl->frame_link.next == NULL)
+    gdk_display_link_source_unpause ((GdkDisplayLinkSource *)display_quartz->frame_source);
+}
+
+void
+_gdk_quartz_display_remove_frame_callback (GdkDisplay             *display,
+                                           GdkWindow              *window)
+{
+  GdkQuartzDisplay *display_quartz = GDK_QUARTZ_DISPLAY (display);
+  GList *link;
+
+  link = g_list_find (display_quartz->windows_awaiting_frame, window);
+
+  if (link != NULL)
+    {
+      display_quartz->windows_awaiting_frame =
+        g_list_remove_link (display_quartz->windows_awaiting_frame, link);
+    }
+
+  if (display_quartz->windows_awaiting_frame == NULL)
+    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;
+  GList *iter;
+  gint64 presentation_time;
+  gint64 now;
+
+  source = (GdkDisplayLinkSource *)display_quartz->frame_source;
+
+  iter = display_quartz->windows_awaiting_frame;
+  display_quartz->windows_awaiting_frame = NULL;
+
+  if (iter == 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 (; iter != NULL; iter = iter->next)
+    {
+      GdkWindow *window = iter->data;
+      GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl);
+      GdkFrameClock *frame_clock = gdk_window_get_frame_clock (window);
+      GdkFrameTimings *timings;
+
+      if (frame_clock == NULL)
+        continue;
+
+      _gdk_frame_clock_thaw (frame_clock);
+
+      if (impl->pending_frame_counter)
+        {
+          timings = gdk_frame_clock_get_timings (frame_clock, impl->pending_frame_counter);
+          if (timings != NULL)
+            timings->presentation_time = presentation_time - source->refresh_interval;
+          impl->pending_frame_counter = 0;
+        }
+
+      timings = gdk_frame_clock_get_current_timings (frame_clock);
+
+      if (timings != NULL)
+        {
+          timings->refresh_interval = source->refresh_interval;
+          timings->predicted_presentation_time = source->presentation_time;
+        }
+    }
+
+  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)
 {
@@ -101,6 +209,8 @@ _gdk_quartz_display_open (const gchar *display_name)
 
   _gdk_quartz_events_init ();
 
+  gdk_quartz_display_init_display_link (_gdk_display);
+
 #if 0
   /* FIXME: Remove the #if 0 when we have these functions */
   _gdk_quartz_dnd_init ();
@@ -519,6 +629,11 @@ gdk_quartz_display_dispose (GObject *object)
 static void
 gdk_quartz_display_finalize (GObject *object)
 {
+  GdkQuartzDisplay *display_quartz = GDK_QUARTZ_DISPLAY (object);
+
+  g_source_unref (display_quartz->frame_source);
+  display_quartz->windows_awaiting_frame = NULL;
+
   G_OBJECT_CLASS (gdk_quartz_display_parent_class)->finalize (object);
 }
 
diff --git a/gdk/quartz/gdkdisplay-quartz.h b/gdk/quartz/gdkdisplay-quartz.h
index c9533a91db..6419347b94 100644
--- a/gdk/quartz/gdkdisplay-quartz.h
+++ b/gdk/quartz/gdkdisplay-quartz.h
@@ -35,6 +35,11 @@ struct _GdkQuartzDisplay
   NSRect geometry; /* In AppKit coordinates. */
   NSSize size; /* Aggregate size of displays in millimeters. */
   GPtrArray *monitors;
+
+  /* This structure is not allocated. It points to an embedded
+   * GList in the GdkWindow. */
+  GList   *windows_awaiting_frame;
+  GSource *frame_source;
 };
 
 struct _GdkQuartzDisplayClass
diff --git a/gdk/quartz/gdkdisplaylinksource.c b/gdk/quartz/gdkdisplaylinksource.c
new file mode 100644
index 0000000000..b3b0942678
--- /dev/null
+++ b/gdk/quartz/gdkdisplaylinksource.c
@@ -0,0 +1,251 @@
+/* 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 "gdkprivate-quartz.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;
+  gint64 now;
+
+  now = g_source_get_time (source);
+
+  if (now < impl->presentation_time)
+    *timeout_ = (impl->presentation_time - now) / 1000L;
+  else
+    *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;
+
+  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;
+  gint64 presentation_time;
+  gboolean needs_wakeup;
+
+  needs_wakeup = !g_atomic_int_get (&impl->needs_dispatch);
+
+  presentation_time = host_to_frame_clock_time (inOutputTime->hostTime);
+
+  impl->presentation_time = presentation_time;
+  impl->needs_dispatch = TRUE;
+
+  if (needs_wakeup)
+     {
+      NSEvent *event;
+
+      /* Post a message so we'll break out of the message loop.
+       *
+       * We don't use g_main_context_wakeup() here because that
+       * would result in sending a message to the pipe(2) fd in
+       * the select thread which would then send this message as
+       * well. Lots of extra work.
+       */
+      event = [NSEvent otherEventWithType: NSApplicationDefined
+                                 location: NSZeroPoint
+                            modifierFlags: 0
+                                timestamp: 0
+                             windowNumber: 0
+                                  context: nil
+                                  subtype: GDK_QUARTZ_EVENT_SUBTYPE_EVENTLOOP
+                                    data1: 0
+                                    data2: 0];
+
+      [NSApp postEvent:event atStart:YES];
+     }
+
+  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 clock");
+
+  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 (G_UNLIKELY (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 0000000000..7493b0c0d4
--- /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;
+} 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 8fca4300e4..46e59442d3 100644
--- a/gdk/quartz/gdkprivate-quartz.h
+++ b/gdk/quartz/gdkprivate-quartz.h
@@ -116,6 +116,12 @@ void       _gdk_quartz_display_get_maximal_cursor_size (GdkDisplay *display,
                                                         guint      *width,
                                                         guint      *height);
 
+/* Display methods - frame clock */
+void        _gdk_quartz_display_add_frame_callback    (GdkDisplay             *display,
+                                                       GdkWindow              *window);
+void        _gdk_quartz_display_remove_frame_callback (GdkDisplay             *display,
+                                                       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 12bac9798a..440c9112fc 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"
@@ -805,6 +806,29 @@ get_nsscreen_for_point (gint x, gint y)
   return screen;
 }
 
+static void
+on_frame_clock_before_paint (GdkFrameClock *frame_clock,
+                             GdkWindow     *window)
+{
+}
+
+static void
+on_frame_clock_after_paint (GdkFrameClock *frame_clock,
+                            GdkWindow     *window)
+{
+  GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl);
+  GdkDisplay *display = gdk_window_get_display (window);
+  GdkFrameTimings *timings;
+
+  timings = gdk_frame_clock_get_current_timings (frame_clock);
+  if (timings != NULL)
+    impl->pending_frame_counter = timings->frame_counter;
+
+  _gdk_quartz_display_add_frame_callback (display, window);
+
+  _gdk_frame_clock_freeze (frame_clock);
+}
+
 void
 _gdk_quartz_display_create_window_impl (GdkDisplay    *display,
                                         GdkWindow     *window,
@@ -816,6 +840,7 @@ _gdk_quartz_display_create_window_impl (GdkDisplay    *display,
 {
   GdkWindowImplQuartz *impl;
   GdkWindowImplQuartz *parent_impl;
+  GdkFrameClock *frame_clock;
 
   GDK_QUARTZ_ALLOC_POOL;
 
@@ -954,6 +979,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
@@ -1009,9 +1041,14 @@ gdk_quartz_window_destroy (GdkWindow *window,
 {
   GdkWindowImplQuartz *impl;
   GdkWindow *parent;
+  GdkDisplay *display;
 
   impl = GDK_WINDOW_IMPL_QUARTZ (window->impl);
 
+  display = gdk_window_get_display (window);
+
+  _gdk_quartz_display_remove_frame_callback (display, window);
+
   main_window_stack = g_slist_remove (main_window_stack, window);
 
   g_list_free (impl->sorted_children);
diff --git a/gdk/quartz/gdkwindow-quartz.h b/gdk/quartz/gdkwindow-quartz.h
index 4c8347c9e5..aa18c75c92 100644
--- a/gdk/quartz/gdkwindow-quartz.h
+++ b/gdk/quartz/gdkwindow-quartz.h
@@ -64,6 +64,9 @@ struct _GdkWindowImplQuartz
   gint shadow_top;
 
   gint shadow_max;
+
+  GList frame_link;
+  gint pending_frame_counter;
 };
  
 struct _GdkWindowImplQuartzClass 


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