[gtk/gtk-3-24: 5/10] quartz: add CVDisplayLink based frame clock




commit 8182c978db26edf8021cf1140fc4b8bce8effc62
Author: John Ralls <jralls ceridwen us>
Date:   Tue Feb 1 16:21:18 2022 -0800

    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    | 110 +++++++++++++++++
 gdk/quartz/gdkdisplay-quartz.h    |   4 +
 gdk/quartz/gdkdisplaylinksource.c | 251 ++++++++++++++++++++++++++++++++++++++
 gdk/quartz/gdkdisplaylinksource.h |  48 ++++++++
 gdk/quartz/gdkinternal-quartz.h   |   5 +
 gdk/quartz/gdkwindow-quartz.c     |  40 ++++++
 gdk/quartz/gdkwindow-quartz.h     |   4 +
 gdk/quartz/meson.build            |   1 +
 9 files changed, 465 insertions(+)
---
diff --git a/gdk/quartz/Makefile.am b/gdk/quartz/Makefile.am
index 4782c5aab6..bbe74c57eb 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 0587ca8f2f..53726e38b6 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"
@@ -29,6 +30,7 @@
 #include "gdkquartzdevicemanager-core.h"
 #include "gdkscreen.h"
 #include "gdkmonitorprivate.h"
+#include "gdkdisplaylinksource.h"
 #include "gdkdisplay-quartz.h"
 #include "gdkmonitor-quartz.h"
 #include "gdkglcontext-quartz.h"
@@ -84,6 +86,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->impl);
+
+  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)
 {
@@ -102,6 +210,8 @@ _gdk_quartz_display_open (const gchar *display_name)
 
   /* Initialize application */
   [NSApplication sharedApplication];
+  gdk_quartz_display_init_display_link (_gdk_display);
+
 #if 0
   /* FIXME: Remove the #if 0 when we have these functions */
   _gdk_quartz_dnd_init ();
diff --git a/gdk/quartz/gdkdisplay-quartz.h b/gdk/quartz/gdkdisplay-quartz.h
index 66cfe4271e..59245da7ca 100644
--- a/gdk/quartz/gdkdisplay-quartz.h
+++ b/gdk/quartz/gdkdisplay-quartz.h
@@ -37,6 +37,10 @@ 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..b59c121945
--- /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 "gdkinternal-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: NSEventTypeApplicationDefined
+                                 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/gdkinternal-quartz.h b/gdk/quartz/gdkinternal-quartz.h
index 7f2566ae3f..646bef3ad7 100644
--- a/gdk/quartz/gdkinternal-quartz.h
+++ b/gdk/quartz/gdkinternal-quartz.h
@@ -284,5 +284,10 @@ void     _gdk_quartz_window_change_property   (GdkWindow    *window,
 void     _gdk_quartz_window_delete_property   (GdkWindow    *window,
                                                GdkAtom       property);
 
+/* 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);
 
 #endif /* __GDK_INTERNAL_QUARTZ_H__ */
diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c
index cd96e254ff..ce740d426b 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 "gdkwindow-quartz.h"
@@ -801,6 +802,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,
@@ -813,6 +837,7 @@ _gdk_quartz_display_create_window_impl (GdkDisplay    *display,
   GdkWindowImplQuartz *impl;
   GdkWindowImplQuartz *parent_impl;
   GdkWindowTypeHint    type_hint = GDK_WINDOW_TYPE_HINT_NORMAL;
+  GdkFrameClock *frame_clock;
 
   GDK_QUARTZ_ALLOC_POOL;
 
@@ -957,6 +982,16 @@ _gdk_quartz_display_create_window_impl (GdkDisplay    *display,
     }
 
   GDK_QUARTZ_RELEASE_POOL;
+
+  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
@@ -1012,9 +1047,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 1175f072e4..e93c5abfed 100644
--- a/gdk/quartz/gdkwindow-quartz.h
+++ b/gdk/quartz/gdkwindow-quartz.h
@@ -64,6 +64,10 @@ struct _GdkWindowImplQuartz
   gint shadow_top;
 
   gint shadow_max;
+
+  gboolean use_cg_context;
+  GList frame_link;
+  gint pending_frame_counter;
 };
  
 struct _GdkWindowImplQuartzClass 
diff --git a/gdk/quartz/meson.build b/gdk/quartz/meson.build
index 619c0242e0..2affac9922 100644
--- a/gdk/quartz/meson.build
+++ b/gdk/quartz/meson.build
@@ -8,6 +8,7 @@ gdk_quartz_sources = files(
   'gdkdevice-core-quartz.c',
   'gdkdevicemanager-core-quartz.c',
   'gdkdisplay-quartz.c',
+  'gdkdisplaylinksource.c',
   'gdkdisplaymanager-quartz.c',
   'gdkdnd-quartz.c',
   'gdkevents-quartz.c',


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