1 /* gdkdisplaylinksource.c
2  *
3  * Copyright (C) 2015 Christian Hergert <christian@hergert.me>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Authors:
19  *   Christian Hergert <christian@hergert.me>
20  */
21 
22 #include "config.h"
23 
24 #include <AppKit/AppKit.h>
25 #include <mach/mach_time.h>
26 
27 #include "gdkdisplaylinksource.h"
28 
29 #include "gdkmacoseventsource-private.h"
30 
31 static gint64 host_to_frame_clock_time (gint64 val);
32 
33 static gboolean
gdk_display_link_source_prepare(GSource * source,int * timeout_)34 gdk_display_link_source_prepare (GSource *source,
35                                  int     *timeout_)
36 {
37   GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
38   gint64 now;
39 
40   now = g_source_get_time (source);
41 
42   if (now < impl->presentation_time)
43     *timeout_ = (impl->presentation_time - now) / 1000L;
44   else
45     *timeout_ = -1;
46 
47   return impl->needs_dispatch;
48 }
49 
50 static gboolean
gdk_display_link_source_check(GSource * source)51 gdk_display_link_source_check (GSource *source)
52 {
53   GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
54   return impl->needs_dispatch;
55 }
56 
57 static gboolean
gdk_display_link_source_dispatch(GSource * source,GSourceFunc callback,gpointer user_data)58 gdk_display_link_source_dispatch (GSource     *source,
59                                   GSourceFunc  callback,
60                                   gpointer     user_data)
61 {
62   GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
63   gboolean ret = G_SOURCE_CONTINUE;
64 
65   impl->needs_dispatch = FALSE;
66 
67   if (callback != NULL)
68     ret = callback (user_data);
69 
70   return ret;
71 }
72 
73 static void
gdk_display_link_source_finalize(GSource * source)74 gdk_display_link_source_finalize (GSource *source)
75 {
76   GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source;
77 
78   CVDisplayLinkStop (impl->display_link);
79   CVDisplayLinkRelease (impl->display_link);
80 }
81 
82 static GSourceFuncs gdk_display_link_source_funcs = {
83   gdk_display_link_source_prepare,
84   gdk_display_link_source_check,
85   gdk_display_link_source_dispatch,
86   gdk_display_link_source_finalize
87 };
88 
89 void
gdk_display_link_source_pause(GdkDisplayLinkSource * source)90 gdk_display_link_source_pause (GdkDisplayLinkSource *source)
91 {
92   CVDisplayLinkStop (source->display_link);
93 }
94 
95 void
gdk_display_link_source_unpause(GdkDisplayLinkSource * source)96 gdk_display_link_source_unpause (GdkDisplayLinkSource *source)
97 {
98   CVDisplayLinkStart (source->display_link);
99 }
100 
101 static CVReturn
gdk_display_link_source_frame_cb(CVDisplayLinkRef display_link,const CVTimeStamp * inNow,const CVTimeStamp * inOutputTime,CVOptionFlags flagsIn,CVOptionFlags * flagsOut,void * user_data)102 gdk_display_link_source_frame_cb (CVDisplayLinkRef   display_link,
103                                   const CVTimeStamp *inNow,
104                                   const CVTimeStamp *inOutputTime,
105                                   CVOptionFlags      flagsIn,
106                                   CVOptionFlags     *flagsOut,
107                                   void              *user_data)
108 {
109   GdkDisplayLinkSource *impl = user_data;
110   gint64 presentation_time;
111   gboolean needs_wakeup;
112 
113   needs_wakeup = !g_atomic_int_get (&impl->needs_dispatch);
114 
115   presentation_time = host_to_frame_clock_time (inOutputTime->hostTime);
116 
117   impl->presentation_time = presentation_time;
118   impl->needs_dispatch = TRUE;
119 
120   if (needs_wakeup)
121     {
122       NSEvent *event;
123 
124       /* Post a message so we'll break out of the message loop.
125        *
126        * We don't use g_main_context_wakeup() here because that
127        * would result in sending a message to the pipe(2) fd in
128        * the select thread which would then send this message as
129        * well. Lots of extra work.
130        */
131       event = [NSEvent otherEventWithType: NSEventTypeApplicationDefined
132                                  location: NSZeroPoint
133                             modifierFlags: 0
134                                 timestamp: 0
135                              windowNumber: 0
136                                   context: nil
137                                   subtype: GDK_MACOS_EVENT_SUBTYPE_EVENTLOOP
138                                     data1: 0
139                                     data2: 0];
140 
141       [NSApp postEvent:event atStart:YES];
142     }
143 
144   return kCVReturnSuccess;
145 }
146 
147 /**
148  * gdk_display_link_source_new:
149  *
150  * Creates a new `GSource` that will activate the dispatch function upon
151  * notification from a CVDisplayLink that a new frame should be drawn.
152  *
153  * Effort is made to keep the transition from the high-priority
154  * CVDisplayLink thread into this GSource lightweight. However, this is
155  * somewhat non-ideal since the best case would be to do the drawing
156  * from the high-priority thread.
157  *
158  * Returns: (transfer full): A newly created `GSource`
159  */
160 GSource *
gdk_display_link_source_new(void)161 gdk_display_link_source_new (void)
162 {
163   GdkDisplayLinkSource *impl;
164   GSource *source;
165   CVReturn ret;
166   double period;
167 
168   source = g_source_new (&gdk_display_link_source_funcs, sizeof *impl);
169   impl = (GdkDisplayLinkSource *)source;
170 
171   /*
172    * Create our link based on currently connected displays.
173    * If there are multiple displays, this will be something that tries
174    * to work for all of them. In the future, we may want to explore multiple
175    * links based on the connected displays.
176    */
177   ret = CVDisplayLinkCreateWithActiveCGDisplays (&impl->display_link);
178   if (ret != kCVReturnSuccess)
179     {
180       g_warning ("Failed to initialize CVDisplayLink!");
181       return source;
182     }
183 
184   /*
185    * Determine our nominal period between frames.
186    */
187   period = CVDisplayLinkGetActualOutputVideoRefreshPeriod (impl->display_link);
188   if (period == 0.0)
189     period = 1.0 / 60.0;
190   impl->refresh_interval = period * 1000000L;
191   impl->refresh_rate = 1.0 / period * 1000L;
192 
193   /*
194    * Wire up our callback to be executed within the high-priority thread.
195    */
196   CVDisplayLinkSetOutputCallback (impl->display_link,
197                                   gdk_display_link_source_frame_cb,
198                                   source);
199 
200   g_source_set_static_name (source, "[gdk] quartz frame clock");
201 
202   return source;
203 }
204 
205 static gint64
host_to_frame_clock_time(gint64 val)206 host_to_frame_clock_time (gint64 val)
207 {
208   /* NOTE: Code adapted from GLib's g_get_monotonic_time(). */
209 
210   mach_timebase_info_data_t timebase_info;
211 
212   /* we get nanoseconds from mach_absolute_time() using timebase_info */
213   mach_timebase_info (&timebase_info);
214 
215   if (timebase_info.numer != timebase_info.denom)
216     {
217 #ifdef HAVE_UINT128_T
218       val = ((__uint128_t) val * (__uint128_t) timebase_info.numer) / timebase_info.denom / 1000;
219 #else
220       guint64 t_high, t_low;
221       guint64 result_high, result_low;
222 
223       /* 64 bit x 32 bit / 32 bit with 96-bit intermediate
224        * algorithm lifted from qemu */
225       t_low = (val & 0xffffffffLL) * (guint64) timebase_info.numer;
226       t_high = (val >> 32) * (guint64) timebase_info.numer;
227       t_high += (t_low >> 32);
228       result_high = t_high / (guint64) timebase_info.denom;
229       result_low = (((t_high % (guint64) timebase_info.denom) << 32) +
230                     (t_low & 0xffffffff)) /
231                    (guint64) timebase_info.denom;
232       val = ((result_high << 32) | result_low) / 1000;
233 #endif
234     }
235   else
236     {
237       /* nanoseconds to microseconds */
238       val = val / 1000;
239     }
240 
241   return val;
242 }
243