/* 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 "gdkdebugprivate.h" #include "gdkmacoseventsource-private.h" #include "gdkmacosmonitor-private.h" #include "gdkprivate.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 (!impl->paused && callback != NULL) ret = callback (user_data); return ret; } static void gdk_display_link_source_finalize (GSource *source) { GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source; if (!impl->paused) 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) { g_return_if_fail (source->paused == FALSE); source->paused = TRUE; CVDisplayLinkStop (source->display_link); } void gdk_display_link_source_unpause (GdkDisplayLinkSource *source) { g_return_if_fail (source->paused == TRUE); source->paused = FALSE; 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: * @display_id: the identifier of the monitor * * 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 (CGDirectDisplayID display_id, CGDisplayModeRef mode) { GdkDisplayLinkSource *impl; GSource *source; char *name; source = g_source_new (&gdk_display_link_source_funcs, sizeof *impl); impl = (GdkDisplayLinkSource *)source; impl->display_id = display_id; impl->paused = TRUE; /* Create DisplayLink for timing information for the display in * question so that we can produce graphics for that display at whatever * rate it can provide. */ if (CVDisplayLinkCreateWithCGDisplay (display_id, &impl->display_link) != kCVReturnSuccess) { g_warning ("Failed to initialize CVDisplayLink!"); goto failure; } impl->refresh_rate = CGDisplayModeGetRefreshRate (mode) * 1000.0; if (impl->refresh_rate == 0) { const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod (impl->display_link); if (!(time.flags & kCVTimeIsIndefinite)) impl->refresh_rate = (double)time.timeScale / (double)time.timeValue * 1000.0; } if (impl->refresh_rate != 0) { impl->refresh_interval = 1000000.0 / (double)impl->refresh_rate * 1000.0; } else { double period = CVDisplayLinkGetActualOutputVideoRefreshPeriod (impl->display_link); if (period == 0.0) period = 1.0 / 60.0; impl->refresh_rate = 1.0 / period * 1000L; impl->refresh_interval = period * 1000000L; } name = _gdk_macos_monitor_get_connector_name (display_id); GDK_DEBUG (MISC, "Monitor \"%s\" discovered with Refresh Rate %d and Interval %"G_GINT64_FORMAT, name ? name : "unknown", impl->refresh_rate, impl->refresh_interval); g_free (name); /* 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_static_name (source, "[gdk] quartz frame clock"); return source; failure: g_source_unref (source); return NULL; } 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; }