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