[gtk/wip/nacho/quartz-frame-clock: 17/20] quartz: add CVDisplayLink based frame clock
- From: Ignacio Casal Quinteiro <icq src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/nacho/quartz-frame-clock: 17/20] quartz: add CVDisplayLink based frame clock
- Date: Thu, 11 Apr 2019 16:00:24 +0000 (UTC)
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]