/* gdkdisplaylinksource.c * * Copyright (C) 2015 Christian Hergert * * 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 . * * Authors: * Christian Hergert */ #include "config.h" #include #include #include "gdkdisplaylinksource.h" #include "gdkmacoseventsource-private.h" static gint64 host_to_frame_clock_time (gint64 val); static gboolean gdk_display_link_source_prepare (GSource *source, int *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_MACOS_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; impl->refresh_rate = 1.0 / period * 1000L; /* * 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 val) { /* NOTE: Code adapted from GLib's g_get_monotonic_time(). */ mach_timebase_info_data_t timebase_info; /* we get nanoseconds from mach_absolute_time() using timebase_info */ mach_timebase_info (&timebase_info); if (timebase_info.numer != timebase_info.denom) { #ifdef HAVE_UINT128_T val = ((__uint128_t) val * (__uint128_t) timebase_info.numer) / timebase_info.denom / 1000; #else guint64 t_high, t_low; guint64 result_high, result_low; /* 64 bit x 32 bit / 32 bit with 96-bit intermediate * algorithm lifted from qemu */ t_low = (val & 0xffffffffLL) * (guint64) timebase_info.numer; t_high = (val >> 32) * (guint64) timebase_info.numer; t_high += (t_low >> 32); result_high = t_high / (guint64) timebase_info.denom; result_low = (((t_high % (guint64) timebase_info.denom) << 32) + (t_low & 0xffffffff)) / (guint64) timebase_info.denom; val = ((result_high << 32) | result_low) / 1000; #endif } else { /* nanoseconds to microseconds */ val = val / 1000; } return val; }