From acc1eb917ba800e0067fc1a20f841273769261f6 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Sun, 30 Jan 2022 13:17:43 -0800 Subject: [PATCH 1/9] Quartz: Don't crash trying to release a NULL CGContext. --- gdk/quartz/gdkwindow-quartz.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c index aa85318e0b..5f5f67eb9c 100644 --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -301,8 +301,9 @@ gdk_quartz_cairo_surface_destroy (void *data) surface_data->window_impl->cairo_surface = NULL; - gdk_quartz_window_release_context (surface_data->window_impl, - surface_data->cg_context); + if (surface_data->cg_context) + gdk_quartz_window_release_context (surface_data->window_impl, + surface_data->cg_context); g_free (surface_data); } @@ -3211,6 +3212,7 @@ gdk_quartz_window_release_context (GdkWindowImplQuartz *window, return; } + g_return_if_fail (cg_context); GDK_WINDOW_IMPL_QUARTZ_GET_CLASS (window)->release_context (window, cg_context); } From b72e4437a20609acf03e720e7a8c461512bff285 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Tue, 1 Feb 2022 12:06:23 -0800 Subject: [PATCH 2/9] quartz: Clean up two macOS deprecation warnings. --- gdk/quartz/gdkwindow-quartz.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c index 5f5f67eb9c..c851a84ace 100644 --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -160,27 +160,27 @@ gdk_window_impl_quartz_get_context (GdkWindowImplQuartz *window_impl, * and for widgets that send fake expose events like the arrow * buttons in spinbuttons or the position marker in rulers. */ +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 if (window_impl->in_paint_rect_count == 0) { /* The NSView focus-locking API set was deprecated in MacOS 10.14 and - * has a significant cost in MacOS 11 - every lock/unlock seems to + * has a significant cost in MacOS 11 - every lock/unlock seems to * trigger a drawRect: call for the entire window. To return the * lost performance, do not use the locking API in MacOS 11+ */ - if(gdk_quartz_osx_version() < GDK_OSX_BIGSUR) + if(gdk_quartz_osx_version() < GDK_OSX_MOJAVE) { if (![window_impl->view lockFocusIfCanDraw]) return NULL; } } -#if MAC_OS_X_VERSION_MAX_ALLOWED < 101000 - cg_context = [[NSGraphicsContext currentContext] graphicsPort]; -#else +#endif +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 if (gdk_quartz_osx_version () < GDK_OSX_YOSEMITE) cg_context = [[NSGraphicsContext currentContext] graphicsPort]; else - cg_context = [[NSGraphicsContext currentContext] CGContext]; #endif + cg_context = [[NSGraphicsContext currentContext] CGContext]; if (!cg_context) return NULL; From 0c439e4b838cefe3d69a5bb39476acb735f112c7 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Wed, 30 Sep 2015 18:25:07 -0700 Subject: [PATCH 3/9] quartz: squash compiler warning about enums MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don’t care about the other enums, fine to squash the warning. --- gdk/quartz/gdkevents-quartz.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gdk/quartz/gdkevents-quartz.c b/gdk/quartz/gdkevents-quartz.c index 0d492c1619..1b43add865 100644 --- a/gdk/quartz/gdkevents-quartz.c +++ b/gdk/quartz/gdkevents-quartz.c @@ -396,7 +396,7 @@ get_window_point_from_screen_point (GdkWindow *window, static gboolean is_mouse_button_press_event (NSEventType type) { - switch (type) + switch ((int)type) { case GDK_QUARTZ_LEFT_MOUSE_DOWN: case GDK_QUARTZ_RIGHT_MOUSE_DOWN: @@ -1029,7 +1029,7 @@ fill_button_event (GdkWindow *window, state = get_keyboard_modifiers_from_ns_event (nsevent) | _gdk_quartz_events_get_current_mouse_modifiers (); - switch ([nsevent type]) + switch ((int)[nsevent type]) { case GDK_QUARTZ_LEFT_MOUSE_DOWN: case GDK_QUARTZ_RIGHT_MOUSE_DOWN: From 3c721c5a809865853e42714ab1608460e8d6b9d8 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Wed, 30 Sep 2015 18:33:08 -0700 Subject: [PATCH 4/9] quartz: drop beam sync penalty code Now that we have a frame clock in place, we should be able to drop the beam-sync penalty prevention code as we should be aligning our draws with CVDisplayLink. Originally by Christian Hergert , flushWindow corrections and improvements by John Ralls. --- gdk/quartz/gdkwindow-quartz.c | 88 +++++++---------------------------- 1 file changed, 18 insertions(+), 70 deletions(-) diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c index c851a84ace..cd96e254ff 100644 --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -47,8 +47,6 @@ static gboolean in_process_all_updates = FALSE; static GSList *main_window_stack; -void _gdk_quartz_window_flush (GdkWindowImplQuartz *window_impl); - typedef struct { gint x, y; @@ -160,19 +158,17 @@ gdk_window_impl_quartz_get_context (GdkWindowImplQuartz *window_impl, * and for widgets that send fake expose events like the arrow * buttons in spinbuttons or the position marker in rulers. */ -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - if (window_impl->in_paint_rect_count == 0) +#if MAC_OS_X_VERSION_MAX_ALLOWED < 101400 + if (gdk_quartz_osx_version() < GDK_OSX_MOJAVE && + window_impl->in_paint_rect_count == 0) { /* The NSView focus-locking API set was deprecated in MacOS 10.14 and * has a significant cost in MacOS 11 - every lock/unlock seems to * trigger a drawRect: call for the entire window. To return the * lost performance, do not use the locking API in MacOS 11+ */ - if(gdk_quartz_osx_version() < GDK_OSX_MOJAVE) - { - if (![window_impl->view lockFocusIfCanDraw]) + if (![window_impl->view lockFocusIfCanDraw]) return NULL; - } } #endif #if MAC_OS_X_VERSION_MIN_REQUIRED < 101000 @@ -206,19 +202,14 @@ gdk_window_impl_quartz_release_context (GdkWindowImplQuartz *window_impl, } /* See comment in gdk_quartz_window_get_context(). */ - if (window_impl->in_paint_rect_count == 0) +#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 + if (gdk_quartz_osx_version() < GDK_OSX_MOJAVE && + window_impl->in_paint_rect_count == 0) { - _gdk_quartz_window_flush (window_impl); - - /* As per gdk_window_impl_quartz_get_context(), the NSView - * focus-locking API set was deprecated in MacOS 10.14 and has - * a significant cost in MacOS 11 - every lock/unlock seems to - * trigger a drawRect: call for the entire window. To return the - * lost performance, do not use the locking API in MacOS 11+ - */ - if(gdk_quartz_osx_version() < GDK_OSX_BIGSUR) - [window_impl->view unlockFocus]; + [window_impl->toplevel flushWindow]; + [window_impl->view unlockFocus]; } +#endif } static void @@ -241,52 +232,6 @@ gdk_window_impl_quartz_finalize (GObject *object) G_OBJECT_CLASS (parent_class)->finalize (object); } -/* Help preventing "beam sync penalty" where CG makes all graphics code - * block until the next vsync if we try to flush (including call display on - * a view) too often. We do this by limiting the manual flushing done - * outside of expose calls to less than some frequency when measured over - * the last 4 flushes. This is a bit arbitray, but seems to make it possible - * for some quick manual flushes (such as gtkruler or gimp’s marching ants) - * without hitting the max flush frequency. - * - * If drawable NULL, no flushing is done, only registering that a flush was - * done externally. - * - * Note: As of MacOS 10.14 NSWindow flushWindow is deprecated because - * Quartz has the ability to handle deferred drawing on its own. - */ -void -_gdk_quartz_window_flush (GdkWindowImplQuartz *window_impl) -{ -#if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - static struct timeval prev_tv; - static gint intervals[4]; - static gint index; - struct timeval tv; - gint ms; - - gettimeofday (&tv, NULL); - ms = (tv.tv_sec - prev_tv.tv_sec) * 1000 + (tv.tv_usec - prev_tv.tv_usec) / 1000; - intervals[index++ % 4] = ms; - - if (window_impl) - { - ms = intervals[0] + intervals[1] + intervals[2] + intervals[3]; - - /* ~25Hz on average. */ - if (ms > 4*40) - { - if (window_impl) - [window_impl->toplevel flushWindow]; - - prev_tv = tv; - } - } - else - prev_tv = tv; -#endif -} - static cairo_user_data_key_t gdk_quartz_cairo_key; typedef struct { @@ -420,13 +365,14 @@ _gdk_quartz_window_process_updates_recurse (GdkWindow *window, /* In theory, we could skip the flush disabling, since we only * have one NSView. */ - if (nswindow && ![nswindow isFlushWindowDisabled]) + if (gdk_quartz_osx_version() < GDK_OSX_MOJAVE && + nswindow && ![nswindow isFlushWindowDisabled]) { [nswindow retain]; [nswindow disableFlushWindow]; - update_nswindows = g_slist_prepend (update_nswindows, nswindow); } #endif + update_nswindows = g_slist_prepend (update_nswindows, nswindow); } } @@ -471,10 +417,12 @@ _gdk_quartz_display_after_process_all_updates (GdkDisplay *display) [[nswindow contentView] displayIfNeeded]; - _gdk_quartz_window_flush (NULL); #if MAC_OS_X_VERSION_MIN_REQUIRED < 101400 - [nswindow enableFlushWindow]; - [nswindow flushWindow]; + if(gdk_quartz_osx_version() < GDK_OSX_BIGSUR) + { + [nswindow enableFlushWindow]; + [nswindow flushWindow]; + } #endif [nswindow release]; From 8182c978db26edf8021cf1140fc4b8bce8effc62 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Tue, 1 Feb 2022 16:21:18 -0800 Subject: [PATCH 5/9] quartz: add CVDisplayLink based frame clock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 | 110 +++++++++++++ gdk/quartz/gdkdisplay-quartz.h | 4 + gdk/quartz/gdkdisplaylinksource.c | 251 ++++++++++++++++++++++++++++++ gdk/quartz/gdkdisplaylinksource.h | 48 ++++++ gdk/quartz/gdkinternal-quartz.h | 5 + gdk/quartz/gdkwindow-quartz.c | 40 +++++ gdk/quartz/gdkwindow-quartz.h | 4 + gdk/quartz/meson.build | 1 + 9 files changed, 465 insertions(+) create mode 100644 gdk/quartz/gdkdisplaylinksource.c create mode 100644 gdk/quartz/gdkdisplaylinksource.h diff --git a/gdk/quartz/Makefile.am b/gdk/quartz/Makefile.am index 4782c5aab6..bbe74c57eb 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 0587ca8f2f..53726e38b6 100644 --- a/gdk/quartz/gdkdisplay-quartz.c +++ b/gdk/quartz/gdkdisplay-quartz.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "gdkprivate-quartz.h" #include "gdkquartzscreen.h" @@ -29,6 +30,7 @@ #include "gdkquartzdevicemanager-core.h" #include "gdkscreen.h" #include "gdkmonitorprivate.h" +#include "gdkdisplaylinksource.h" #include "gdkdisplay-quartz.h" #include "gdkmonitor-quartz.h" #include "gdkglcontext-quartz.h" @@ -84,6 +86,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->impl); + + 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) { @@ -102,6 +210,8 @@ _gdk_quartz_display_open (const gchar *display_name) /* Initialize application */ [NSApplication sharedApplication]; + gdk_quartz_display_init_display_link (_gdk_display); + #if 0 /* FIXME: Remove the #if 0 when we have these functions */ _gdk_quartz_dnd_init (); diff --git a/gdk/quartz/gdkdisplay-quartz.h b/gdk/quartz/gdkdisplay-quartz.h index 66cfe4271e..59245da7ca 100644 --- a/gdk/quartz/gdkdisplay-quartz.h +++ b/gdk/quartz/gdkdisplay-quartz.h @@ -37,6 +37,10 @@ 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..b59c121945 --- /dev/null +++ b/gdk/quartz/gdkdisplaylinksource.c @@ -0,0 +1,251 @@ +/* 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 "gdkinternal-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: NSEventTypeApplicationDefined + 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 + * + * 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 + */ + +#ifndef GDK_DISPLAY_LINK_SOURCE_H +#define GDK_DISPLAY_LINK_SOURCE_H + +#include + +#include + +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/gdkinternal-quartz.h b/gdk/quartz/gdkinternal-quartz.h index 7f2566ae3f..646bef3ad7 100644 --- a/gdk/quartz/gdkinternal-quartz.h +++ b/gdk/quartz/gdkinternal-quartz.h @@ -284,5 +284,10 @@ void _gdk_quartz_window_change_property (GdkWindow *window, void _gdk_quartz_window_delete_property (GdkWindow *window, GdkAtom property); +/* 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); #endif /* __GDK_INTERNAL_QUARTZ_H__ */ diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c index cd96e254ff..ce740d426b 100644 --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "gdkwindowimpl.h" #include "gdkwindow-quartz.h" @@ -801,6 +802,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, @@ -813,6 +837,7 @@ _gdk_quartz_display_create_window_impl (GdkDisplay *display, GdkWindowImplQuartz *impl; GdkWindowImplQuartz *parent_impl; GdkWindowTypeHint type_hint = GDK_WINDOW_TYPE_HINT_NORMAL; + GdkFrameClock *frame_clock; GDK_QUARTZ_ALLOC_POOL; @@ -957,6 +982,16 @@ _gdk_quartz_display_create_window_impl (GdkDisplay *display, } GDK_QUARTZ_RELEASE_POOL; + + 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 @@ -1012,9 +1047,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 1175f072e4..e93c5abfed 100644 --- a/gdk/quartz/gdkwindow-quartz.h +++ b/gdk/quartz/gdkwindow-quartz.h @@ -64,6 +64,10 @@ struct _GdkWindowImplQuartz gint shadow_top; gint shadow_max; + + gboolean use_cg_context; + GList frame_link; + gint pending_frame_counter; }; struct _GdkWindowImplQuartzClass diff --git a/gdk/quartz/meson.build b/gdk/quartz/meson.build index 619c0242e0..2affac9922 100644 --- a/gdk/quartz/meson.build +++ b/gdk/quartz/meson.build @@ -8,6 +8,7 @@ gdk_quartz_sources = files( 'gdkdevice-core-quartz.c', 'gdkdevicemanager-core-quartz.c', 'gdkdisplay-quartz.c', + 'gdkdisplaylinksource.c', 'gdkdisplaymanager-quartz.c', 'gdkdnd-quartz.c', 'gdkevents-quartz.c', From 2d3eb0c6a77a7480f17ceda5f6dfa0dd03f985e1 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Sun, 8 May 2022 11:32:59 -0700 Subject: [PATCH 6/9] [quartz] Draw needs_display_region if available --- gdk/quartz/GdkQuartzView.c | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/gdk/quartz/GdkQuartzView.c b/gdk/quartz/GdkQuartzView.c index c20c5de83a..557451606c 100644 --- a/gdk/quartz/GdkQuartzView.c +++ b/gdk/quartz/GdkQuartzView.c @@ -344,32 +344,34 @@ return; } - /* Clear our own bookkeeping of regions that need display */ if (impl->needs_display_region) { + _gdk_window_process_updates_recurse (gdk_window, impl->needs_display_region); cairo_region_destroy (impl->needs_display_region); impl->needs_display_region = NULL; } - - [self getRectsBeingDrawn: &drawn_rects count: &count]; - region = cairo_region_create (); - - for (i = 0; i < count; i++) + else { - gdk_rect.x = drawn_rects[i].origin.x; - gdk_rect.y = drawn_rects[i].origin.y; - gdk_rect.width = drawn_rects[i].size.width; - gdk_rect.height = drawn_rects[i].size.height; + [self getRectsBeingDrawn: &drawn_rects count: &count]; + cairo_region_t* region = cairo_region_create (); - cairo_region_union_rectangle (region, &gdk_rect); + for (i = 0; i < count; i++) + { + gdk_rect.x = drawn_rects[i].origin.x; + gdk_rect.y = drawn_rects[i].origin.y; + gdk_rect.width = drawn_rects[i].size.width; + gdk_rect.height = drawn_rects[i].size.height; + + cairo_region_union_rectangle (region, &gdk_rect); + } + + impl->in_paint_rect_count++; + _gdk_window_process_updates_recurse (gdk_window, region); + impl->in_paint_rect_count--; + + cairo_region_destroy (region); } - impl->in_paint_rect_count++; - _gdk_window_process_updates_recurse (gdk_window, region); - impl->in_paint_rect_count--; - - cairo_region_destroy (region); - if (needsInvalidateShadow) { [[self window] invalidateShadow]; From be60902805d577e2bfdfab4fc6d31ee655b0ec44 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Thu, 28 Oct 2021 15:15:20 -0700 Subject: [PATCH 7/9] Draw window to image_surface and apply to CALayer. --- gdk/quartz/GdkQuartzView.c | 128 ++++++++++++++++++++-------------- gdk/quartz/gdkwindow-quartz.c | 115 ++++++++++++++++-------------- gdk/quartz/gdkwindow-quartz.h | 2 + 3 files changed, 141 insertions(+), 104 deletions(-) diff --git a/gdk/quartz/GdkQuartzView.c b/gdk/quartz/GdkQuartzView.c index 557451606c..3e85d35880 100644 --- a/gdk/quartz/GdkQuartzView.c +++ b/gdk/quartz/GdkQuartzView.c @@ -24,6 +24,8 @@ #include "gdkprivate-quartz.h" #include "gdkquartz.h" #include "gdkinternal-quartz.h" +#include +#import @implementation GdkQuartzView @@ -185,7 +187,7 @@ -(void)doCommandBySelector: (SEL)aSelector { - GDK_NOTE (EVENTS, g_message ("doCommandBySelector %s", aSelector)); + GDK_NOTE (EVENTS, g_message ("doCommandBySelector %s", [NSStringFromSelector (aSelector) UTF8String])); g_object_set_data (G_OBJECT (gdk_window), GIC_FILTER_KEY, GUINT_TO_POINTER (GIC_FILTER_PASSTHRU)); } @@ -307,43 +309,32 @@ [super viewWillDraw]; } --(void)drawRect: (NSRect)rect +-(BOOL)wantsUpdateLayer +{ + return YES; +} + +static void +provider_release_cb (void* info, const void* data, size_t size) +{ + g_free (info); +} + +-(void)updateLayer { - GdkRectangle gdk_rect; GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (gdk_window->impl); - const NSRect *drawn_rects; - NSInteger count; - int i; - cairo_region_t *region; + CALayer *ca_layer = [self layer]; + CGRect layer_bounds = [ca_layer bounds]; + CGRect backing_bounds = [self convertRectToBacking: layer_bounds]; + cairo_rectangle_int_t surface_extents = { layer_bounds.origin.x, + layer_bounds.origin.y, + backing_bounds.size.width, + backing_bounds.size.height }; + cairo_surface_t *image_surface = NULL; if (GDK_WINDOW_DESTROYED (gdk_window)) return; - if (! (gdk_window->event_mask & GDK_EXPOSURE_MASK)) - return; - - if (NSEqualRects (rect, NSZeroRect)) - return; - - if (!GDK_WINDOW_IS_MAPPED (gdk_window)) - { - /* If the window is not yet mapped, clip_region_with_children - * will be empty causing the usual code below to draw nothing. - * To not see garbage on the screen, we draw an aesthetic color - * here. The garbage would be visible if any widget enabled - * the NSView's CALayer in order to add sublayers for custom - * native rendering. - */ - [NSGraphicsContext saveGraphicsState]; - - [[NSColor windowBackgroundColor] setFill]; - [NSBezierPath fillRect: rect]; - - [NSGraphicsContext restoreGraphicsState]; - - return; - } - if (impl->needs_display_region) { _gdk_window_process_updates_recurse (gdk_window, impl->needs_display_region); @@ -352,31 +343,66 @@ } else { - [self getRectsBeingDrawn: &drawn_rects count: &count]; - cairo_region_t* region = cairo_region_create (); - - for (i = 0; i < count; i++) - { - gdk_rect.x = drawn_rects[i].origin.x; - gdk_rect.y = drawn_rects[i].origin.y; - gdk_rect.width = drawn_rects[i].size.width; - gdk_rect.height = drawn_rects[i].size.height; - - cairo_region_union_rectangle (region, &gdk_rect); - } - - impl->in_paint_rect_count++; + cairo_region_t *region = cairo_region_create_rectangle (&surface_extents); + ++impl->in_paint_rect_count; _gdk_window_process_updates_recurse (gdk_window, region); - impl->in_paint_rect_count--; - cairo_region_destroy (region); } + + if (!impl || !impl->cairo_surface) + return; - if (needsInvalidateShadow) + image_surface = cairo_surface_map_to_image (impl->cairo_surface, + &surface_extents); + if (!cairo_surface_status (image_surface)) { - [[self window] invalidateShadow]; - needsInvalidateShadow = NO; + cairo_format_t image_format = cairo_image_surface_get_format (image_surface); + if (image_format == CAIRO_FORMAT_ARGB32) + { + int image_width = cairo_image_surface_get_width (image_surface); + int image_height = cairo_image_surface_get_height (image_surface); + int image_stride = cairo_image_surface_get_stride (image_surface); + void* image_data = g_malloc (image_height * image_stride); + int color_bits = 8; + int pixel_bits = 32; + CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB (); + CGBitmapInfo bitinfo = + kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; + CGDataProviderRef provider = + CGDataProviderCreateWithData (image_data, image_data, + image_height * image_stride, + provider_release_cb); + const CGFloat *decode = NULL; + bool interpolate = YES; + CGSize image_size = {image_height, image_width}; + NSImage* ns_image = NULL; + + if (ca_layer.contents) + [(NSImage*)ca_layer.contents release]; + + memcpy (image_data, cairo_image_surface_get_data (image_surface), + image_height * image_stride); + + ns_image = [[NSImage alloc] + initWithCGImage:CGImageCreate (image_width, + image_height, + color_bits, + pixel_bits, + image_stride, + color_space, + bitinfo, + provider, + decode, + interpolate, + kCGRenderingIntentDefault) + size:image_size]; + ca_layer.contents = ns_image; + } } + cairo_surface_unmap_image (impl->cairo_surface, image_surface); + cairo_surface_destroy (impl->cairo_surface); + --impl->in_paint_rect_count; + } -(void)setNeedsInvalidateShadow: (BOOL)invalidate diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c index ce740d426b..d9d449bb19 100644 --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -148,7 +148,7 @@ gdk_window_impl_quartz_get_context (GdkWindowImplQuartz *window_impl, gboolean antialias) { CGContextRef cg_context = NULL; - CGSize scale; + // CGSize scale; if (GDK_WINDOW_DESTROYED (window_impl->wrapper)) return NULL; @@ -186,9 +186,8 @@ gdk_window_impl_quartz_get_context (GdkWindowImplQuartz *window_impl, /* Undo the default scaling transform, since we apply our own * in gdk_quartz_ref_cairo_surface () */ - scale = CGContextConvertSizeToDeviceSpace (cg_context, - CGSizeMake (1.0, 1.0)); - CGContextScaleCTM (cg_context, 1.0 / fabs(scale.width), 1.0 / fabs(scale.height)); + // scale = CGContextConvertSizeToDeviceSpace (cg_context, CGSizeMake (1.0, 1.0)); + // CGContextScaleCTM (cg_context, 1.0 / fabs(scale.width), 1.0 / fabs(scale.height)); return cg_context; } @@ -247,10 +246,6 @@ gdk_quartz_cairo_surface_destroy (void *data) surface_data->window_impl->cairo_surface = NULL; - if (surface_data->cg_context) - gdk_quartz_window_release_context (surface_data->window_impl, - surface_data->cg_context); - g_free (surface_data); } @@ -259,21 +254,15 @@ gdk_quartz_create_cairo_surface (GdkWindowImplQuartz *impl, int width, int height) { - CGContextRef cg_context; GdkQuartzCairoSurfaceData *surface_data; cairo_surface_t *surface; - cg_context = gdk_quartz_window_get_context (impl, TRUE); surface_data = g_new (GdkQuartzCairoSurfaceData, 1); surface_data->window_impl = impl; - surface_data->cg_context = cg_context; + surface_data->cg_context = NULL; - if (cg_context) - surface = cairo_quartz_surface_create_for_cg_context (cg_context, - width, height); - else - surface = cairo_quartz_surface_create(CAIRO_FORMAT_ARGB32, width, height); + surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); cairo_surface_set_user_data (surface, &gdk_quartz_cairo_key, surface_data, @@ -286,19 +275,17 @@ static cairo_surface_t * gdk_quartz_ref_cairo_surface (GdkWindow *window) { GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl); + gint scale = gdk_window_get_scale_factor (impl->wrapper); if (GDK_WINDOW_DESTROYED (window)) return NULL; if (!impl->cairo_surface) { - gint scale = gdk_window_get_scale_factor (impl->wrapper); - - impl->cairo_surface = - gdk_quartz_create_cairo_surface (impl, - gdk_window_get_width (impl->wrapper) * scale, - gdk_window_get_height (impl->wrapper) * scale); - + impl->cairo_surface = + gdk_quartz_create_cairo_surface (impl, + gdk_window_get_width (impl->wrapper) * scale, + gdk_window_get_height (impl->wrapper) * scale); cairo_surface_set_device_scale (impl->cairo_surface, scale, scale); } else @@ -316,6 +303,7 @@ gdk_window_impl_quartz_init (GdkWindowImplQuartz *impl) static gboolean gdk_window_impl_quartz_begin_paint (GdkWindow *window) { + gdk_quartz_ref_cairo_surface (window); return FALSE; } @@ -3204,37 +3192,15 @@ gdk_quartz_window_release_context (GdkWindowImplQuartz *window, GDK_WINDOW_IMPL_QUARTZ_GET_CLASS (window)->release_context (window, cg_context); } +/* macOS doesn't define a root window, but Gdk needs one for two + * purposes: To be a parent reference for some toplevels and to be a + * fallback window when gdk_window_create_image_surface is called with + * a NULL GdkWindow. + * + */ - -static CGContextRef -gdk_root_window_impl_quartz_get_context (GdkWindowImplQuartz *window, - gboolean antialias) -{ - CGColorSpaceRef colorspace; - CGContextRef cg_context; - GdkWindowImplQuartz *window_impl = GDK_WINDOW_IMPL_QUARTZ (window); - - if (GDK_WINDOW_DESTROYED (window_impl->wrapper)) - return NULL; - - /* We do not have the notion of a root window on OS X. We fake this - * by creating a 1x1 bitmap and return a context to that. - */ - colorspace = CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB); - cg_context = CGBitmapContextCreate (NULL, - 1, 1, 8, 4, colorspace, - (CGBitmapInfo)kCGImageAlphaPremultipliedLast); - CGColorSpaceRelease (colorspace); - - return cg_context; -} - -static void -gdk_root_window_impl_quartz_release_context (GdkWindowImplQuartz *window, - CGContextRef cg_context) -{ - CGContextRelease (cg_context); -} +static CGContextRef gdk_root_window_impl_quartz_get_context (GdkWindowImplQuartz *window, gboolean antialias); +static void gdk_root_window_impl_quartz_release_context (GdkWindowImplQuartz *window, CGContextRef cg_context); static void gdk_root_window_impl_quartz_class_init (GdkRootWindowImplQuartzClass *klass) @@ -3250,6 +3216,22 @@ gdk_root_window_impl_quartz_class_init (GdkRootWindowImplQuartzClass *klass) static void gdk_root_window_impl_quartz_init (GdkRootWindowImplQuartz *impl) { + CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB (); + /* Alpha channel Info: Cairo, CGImage, and CVPixelBuffer all use + * kCGImageAlphaPremultipliedFirst, CALayer.contents wants + * kCGImageAlphaPremultipliedLast. + */ + CGBitmapInfo info = (CGBitmapInfo)kCGImageAlphaPremultipliedLast; + impl->cg_context = CGBitmapContextCreate (NULL, 1, 1, 8, 4, + colorspace, info); + CGColorSpaceRelease (colorspace); + impl->cg_layers = NULL; +} + +static void +gdk_root_window_impl_quartz_dispose (GdkRootWindowImplQuartz *impl) +{ + g_list_free_full (impl->cg_layers, (GDestroyNotify)CGLayerRelease); } GType @@ -3279,3 +3261,30 @@ _gdk_root_window_impl_quartz_get_type (void) return object_type; } + + +static CGContextRef +gdk_root_window_impl_quartz_get_context (GdkWindowImplQuartz *window, + gboolean antialias) +{ + GdkWindowImplQuartz *window_impl = GDK_WINDOW_IMPL_QUARTZ (window); + GdkRootWindowImplQuartz *impl = GDK_ROOT_WINDOW_IMPL_QUARTZ (window); + CGSize size; + CGLayerRef layer; + + if (GDK_WINDOW_DESTROYED (window_impl->wrapper)) + return NULL; + + size.width = gdk_window_get_width (window_impl->wrapper); + size.height = gdk_window_get_height (window_impl->wrapper); + layer = CGLayerCreateWithContext(impl->cg_context, size, NULL); + impl->cg_layers = g_list_prepend(impl->cg_layers, CGLayerRetain (layer)); + return CGContextRetain (CGLayerGetContext (layer)); +} + +static void +gdk_root_window_impl_quartz_release_context (GdkWindowImplQuartz *window, + CGContextRef cg_context) +{ + CGContextRelease (cg_context); +} diff --git a/gdk/quartz/gdkwindow-quartz.h b/gdk/quartz/gdkwindow-quartz.h index e93c5abfed..de9a5ebccf 100644 --- a/gdk/quartz/gdkwindow-quartz.h +++ b/gdk/quartz/gdkwindow-quartz.h @@ -103,6 +103,8 @@ typedef struct _GdkRootWindowImplQuartzClass GdkRootWindowImplQuartzClass; struct _GdkRootWindowImplQuartz { GdkWindowImplQuartz parent_instance; + CGContextRef cg_context; + GList* cg_layers; }; struct _GdkRootWindowImplQuartzClass From df94d0168d1ee7494cd4349eb426abf0c5e03fc7 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Wed, 19 Jan 2022 17:07:04 -0800 Subject: [PATCH 8/9] Use a CVPixelBuffer instead of a CGImage. --- gdk/quartz/GdkQuartzView.c | 187 ++++++++++++++++++++++------------ gdk/quartz/GdkQuartzView.h | 4 + gdk/quartz/gdkwindow-quartz.c | 35 ++++--- gdk/quartz/meson.build | 3 +- 4 files changed, 152 insertions(+), 77 deletions(-) diff --git a/gdk/quartz/GdkQuartzView.c b/gdk/quartz/GdkQuartzView.c index 3e85d35880..c08519db71 100644 --- a/gdk/quartz/GdkQuartzView.c +++ b/gdk/quartz/GdkQuartzView.c @@ -26,16 +26,32 @@ #include "gdkinternal-quartz.h" #include #import +#import @implementation GdkQuartzView + + -(id)initWithFrame: (NSRect)frameRect { if ((self = [super initWithFrame: frameRect])) { + CVReturn rv; + pb_props = @{ + (id)kCVPixelBufferIOSurfaceCoreAnimationCompatibilityKey: @1, + (id)kCVPixelBufferBytesPerRowAlignmentKey: @64, + }; + [pb_props retain]; + cfpb_props = (__bridge CFDictionaryRef)pb_props; + markedRange = NSMakeRange (NSNotFound, 0); selectedRange = NSMakeRange (0, 0); + rv = CVPixelBufferCreate (NULL, frameRect.size.width, + frameRect.size.height, + kCVPixelFormatType_32ARGB, + cfpb_props, &pixels); } + [self setValue: @(YES) forKey: @"postsFrameChangedNotifications"]; return self; @@ -258,6 +274,12 @@ trackingRect = 0; } + if (pixels) + { + CVPixelBufferRelease (pixels); + } + + [pb_props release]; [super dealloc]; } @@ -278,7 +300,7 @@ -(BOOL)isFlipped { - return YES; + return NO; } -(BOOL)isOpaque @@ -300,7 +322,7 @@ */ if(gdk_quartz_osx_version() >= GDK_OSX_BIGSUR) { -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101100 CALayer* layer = self.layer; layer.contentsFormat = kCAContentsFormatRGBA8Uint; #endif @@ -315,36 +337,107 @@ } static void -provider_release_cb (void* info, const void* data, size_t size) +nsrect_from_cairo_rect (NSRect *nsrect, cairo_rectangle_int_t *rect) { - g_free (info); + nsrect->origin.x = (CGFloat)rect->x; + nsrect->origin.y = (CGFloat)rect->y; + nsrect->size.width = (CGFloat)rect->width; + nsrect->size.height = (CGFloat)rect->height; +} + +static void +cairo_rect_from_nsrect (cairo_rectangle_int_t *rect, NSRect *nsrect) +{ + rect->x = (int)nsrect->origin.x; + rect->y = (int)nsrect->origin.y; + rect->width = (int)nsrect->size.width; + rect->height = (int)nsrect->size.height; +} + +static cairo_status_t +copy_rectangle_argb32 (cairo_surface_t *dest, cairo_surface_t *source, + cairo_rectangle_int_t *rect) +{ + cairo_surface_t *source_img, *dest_img; + cairo_status_t status; + cairo_format_t format; + int height, width, stride; + + source_img = cairo_surface_map_to_image (source, rect); + status = cairo_surface_status (source_img); + + if (status) + { + g_warning ("Failed to map source image surface, %d %d %d %d on %d %d: %s\n", + rect->x, rect->y, rect->width, rect->height, + cairo_image_surface_get_width (source), + cairo_image_surface_get_height (source), + cairo_status_to_string (status)); + return status; + } + + format = cairo_image_surface_get_format (source_img); + dest_img = cairo_surface_map_to_image (dest, rect); + status = cairo_surface_status (dest_img); + + if (status) + { + g_warning ("Failed to map destination image surface, %d %d %d %d on %d %d: %s\n", + rect->x, rect->y, rect->width, rect->height, + cairo_image_surface_get_width (source), + cairo_image_surface_get_height (source), + cairo_status_to_string (status)); + goto CLEANUP; + } + + width = cairo_image_surface_get_width (source_img); + stride = cairo_format_stride_for_width (format, width); + height = cairo_image_surface_get_height (source_img); + memcpy (cairo_image_surface_get_data (dest_img), + cairo_image_surface_get_data (source_img), + stride * height); + cairo_surface_unmap_image (dest, dest_img); + + CLEANUP: + cairo_surface_unmap_image (source, source_img); + return status; } -(void)updateLayer { GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (gdk_window->impl); - CALayer *ca_layer = [self layer]; - CGRect layer_bounds = [ca_layer bounds]; + CGRect layer_bounds = [self.layer bounds]; CGRect backing_bounds = [self convertRectToBacking: layer_bounds]; - cairo_rectangle_int_t surface_extents = { layer_bounds.origin.x, - layer_bounds.origin.y, - backing_bounds.size.width, - backing_bounds.size.height }; - cairo_surface_t *image_surface = NULL; + cairo_rectangle_int_t extents; + cairo_surface_t *cvpb_surface; if (GDK_WINDOW_DESTROYED (gdk_window)) return; + ++impl->in_paint_rect_count; + CVPixelBufferLockBaseAddress (pixels, 0); + cvpb_surface = + cairo_image_surface_create_for_data (CVPixelBufferGetBaseAddress (pixels), + CAIRO_FORMAT_ARGB32, + (int)CVPixelBufferGetWidth (pixels), + (int)CVPixelBufferGetHeight (pixels), + (int)CVPixelBufferGetBytesPerRow (pixels)); + + cairo_rect_from_nsrect (&extents, &backing_bounds); if (impl->needs_display_region) { - _gdk_window_process_updates_recurse (gdk_window, impl->needs_display_region); - cairo_region_destroy (impl->needs_display_region); + cairo_region_t *region = impl->needs_display_region; + _gdk_window_process_updates_recurse (gdk_window, region); + cairo_region_destroy (region); impl->needs_display_region = NULL; } else { - cairo_region_t *region = cairo_region_create_rectangle (&surface_extents); - ++impl->in_paint_rect_count; + cairo_rectangle_int_t bounds; + cairo_region_t *region; + + cairo_rect_from_nsrect (&bounds, &layer_bounds); + region = cairo_region_create_rectangle (&bounds); _gdk_window_process_updates_recurse (gdk_window, region); cairo_region_destroy (region); } @@ -352,57 +445,13 @@ provider_release_cb (void* info, const void* data, size_t size) if (!impl || !impl->cairo_surface) return; - image_surface = cairo_surface_map_to_image (impl->cairo_surface, - &surface_extents); - if (!cairo_surface_status (image_surface)) - { - cairo_format_t image_format = cairo_image_surface_get_format (image_surface); - if (image_format == CAIRO_FORMAT_ARGB32) - { - int image_width = cairo_image_surface_get_width (image_surface); - int image_height = cairo_image_surface_get_height (image_surface); - int image_stride = cairo_image_surface_get_stride (image_surface); - void* image_data = g_malloc (image_height * image_stride); - int color_bits = 8; - int pixel_bits = 32; - CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB (); - CGBitmapInfo bitinfo = - kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst; - CGDataProviderRef provider = - CGDataProviderCreateWithData (image_data, image_data, - image_height * image_stride, - provider_release_cb); - const CGFloat *decode = NULL; - bool interpolate = YES; - CGSize image_size = {image_height, image_width}; - NSImage* ns_image = NULL; + copy_rectangle_argb32 (cvpb_surface, impl->cairo_surface, &extents); - if (ca_layer.contents) - [(NSImage*)ca_layer.contents release]; - - memcpy (image_data, cairo_image_surface_get_data (image_surface), - image_height * image_stride); - - ns_image = [[NSImage alloc] - initWithCGImage:CGImageCreate (image_width, - image_height, - color_bits, - pixel_bits, - image_stride, - color_space, - bitinfo, - provider, - decode, - interpolate, - kCGRenderingIntentDefault) - size:image_size]; - ca_layer.contents = ns_image; - } - } - cairo_surface_unmap_image (impl->cairo_surface, image_surface); - cairo_surface_destroy (impl->cairo_surface); + cairo_surface_destroy (cvpb_surface); + CVPixelBufferUnlockBaseAddress (pixels, 0); --impl->in_paint_rect_count; - + self.layer.contents = NULL; + self.layer.contents = (id)CVPixelBufferGetIOSurface (pixels); } -(void)setNeedsInvalidateShadow: (BOOL)invalidate @@ -460,9 +509,19 @@ provider_release_cb (void* info, const void* data, size_t size) -(void)setFrame: (NSRect)frame { + CVReturn rv; + NSRect rect = self.layer ? self.layer.bounds : frame; + NSRect backing_rect = [self convertRectToBacking: rect]; + if (GDK_WINDOW_DESTROYED (gdk_window)) return; + CVPixelBufferRelease (pixels); + rv = CVPixelBufferCreate (NULL, backing_rect.size.width, + backing_rect.size.height, + kCVPixelFormatType_32BGRA, + cfpb_props, &pixels); + [super setFrame: frame]; if ([self window]) diff --git a/gdk/quartz/GdkQuartzView.h b/gdk/quartz/GdkQuartzView.h index da06a8bdce..0c1558a7ec 100644 --- a/gdk/quartz/GdkQuartzView.h +++ b/gdk/quartz/GdkQuartzView.h @@ -17,6 +17,7 @@ */ #import +#import #include "gdk/gdk.h" /* Text Input Client */ @@ -40,6 +41,9 @@ BOOL needsInvalidateShadow; NSRange markedRange; NSRange selectedRange; + CVPixelBufferRef pixels; + NSDictionary *pb_props; + CFDictionaryRef cfpb_props; } - (void)setGdkWindow: (GdkWindow *)window; diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c index d9d449bb19..bdb668498c 100644 --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -148,7 +148,6 @@ gdk_window_impl_quartz_get_context (GdkWindowImplQuartz *window_impl, gboolean antialias) { CGContextRef cg_context = NULL; - // CGSize scale; if (GDK_WINDOW_DESTROYED (window_impl->wrapper)) return NULL; @@ -184,10 +183,6 @@ gdk_window_impl_quartz_get_context (GdkWindowImplQuartz *window_impl, CGContextSaveGState (cg_context); CGContextSetAllowsAntialiasing (cg_context, antialias); - /* Undo the default scaling transform, since we apply our own - * in gdk_quartz_ref_cairo_surface () */ - // scale = CGContextConvertSizeToDeviceSpace (cg_context, CGSizeMake (1.0, 1.0)); - // CGContextScaleCTM (cg_context, 1.0 / fabs(scale.width), 1.0 / fabs(scale.height)); return cg_context; } @@ -275,18 +270,25 @@ static cairo_surface_t * gdk_quartz_ref_cairo_surface (GdkWindow *window) { GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl); - gint scale = gdk_window_get_scale_factor (impl->wrapper); if (GDK_WINDOW_DESTROYED (window)) return NULL; if (!impl->cairo_surface) { - impl->cairo_surface = - gdk_quartz_create_cairo_surface (impl, - gdk_window_get_width (impl->wrapper) * scale, - gdk_window_get_height (impl->wrapper) * scale); + gint width = gdk_window_get_width (impl->wrapper); + gint height = gdk_window_get_height (impl->wrapper); + gint scale = gdk_window_get_scale_factor (impl->wrapper); + gint scaled_width = width * scale; + + if (scaled_width % 16) + scaled_width += 16 - scaled_width % 16; // Surface widths must be 4-pixel aligned + + impl->cairo_surface = gdk_quartz_create_cairo_surface (impl, + scaled_width, + height * scale); cairo_surface_set_device_scale (impl->cairo_surface, scale, scale); + cairo_surface_reference (impl->cairo_surface); // The caller will destroy the returned one. } else cairo_surface_reference (impl->cairo_surface); @@ -1234,6 +1236,7 @@ move_resize_window_internal (GdkWindow *window, cairo_region_t *old_region; cairo_region_t *expose_region; NSSize delta; + gboolean resized = FALSE; if (GDK_WINDOW_DESTROYED (window)) return; @@ -1281,10 +1284,18 @@ move_resize_window_internal (GdkWindow *window, } if (width != -1) - window->width = width; + { + if (window->width != width) + resized = TRUE; + window->width = width; + } if (height != -1) - window->height = height; + { + if (window->height != height) + resized = TRUE; + window->height = height; + } GDK_QUARTZ_ALLOC_POOL; diff --git a/gdk/quartz/meson.build b/gdk/quartz/meson.build index 2affac9922..f4fcd8fba0 100644 --- a/gdk/quartz/meson.build +++ b/gdk/quartz/meson.build @@ -50,8 +50,9 @@ appkit_dep = dependency('appleframeworks', modules : 'AppKit', required : true) cocoa_dep = dependency('appleframeworks', modules : 'Cocoa', required : true) carbon_dep = dependency('appleframeworks', modules : 'Carbon', required : true) quartzcore_dep = dependency('appleframeworks', modules : 'QuartzCore', required : true) +iosurface_dep = dependency('appleframeworks', modules: 'IOSurface', required: true) -gdk_quartz_deps = [ core_graphics_dep, appkit_dep, cocoa_dep, carbon_dep, quartzcore_dep ] +gdk_quartz_deps = [ core_graphics_dep, appkit_dep, cocoa_dep, carbon_dep, quartzcore_dep, iosurface_dep ] libgdk_quartz = static_library('gdk-quartz', gdk_quartz_sources, gdkconfig, gdkenum_h, From b94ed34f9c1ada2b5a19a1f7f85761ce74d9a6a6 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Fri, 13 May 2022 12:59:09 -0700 Subject: [PATCH 9/9] [quartz] Manage implementation surface lifetime. To prevent leaking cairo surfaces while persisting the parts that don't need to be redrawn. --- gdk/quartz/GdkQuartzView.c | 3 +++ gdk/quartz/gdkprivate-quartz.h | 2 ++ gdk/quartz/gdkwindow-quartz.c | 24 ++++++++++++++++++++++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/gdk/quartz/GdkQuartzView.c b/gdk/quartz/GdkQuartzView.c index c08519db71..f7ac47dcc0 100644 --- a/gdk/quartz/GdkQuartzView.c +++ b/gdk/quartz/GdkQuartzView.c @@ -448,6 +448,7 @@ copy_rectangle_argb32 (cairo_surface_t *dest, cairo_surface_t *source, copy_rectangle_argb32 (cvpb_surface, impl->cairo_surface, &extents); cairo_surface_destroy (cvpb_surface); + _gdk_quartz_unref_cairo_surface (gdk_window); CVPixelBufferUnlockBaseAddress (pixels, 0); --impl->in_paint_rect_count; self.layer.contents = NULL; @@ -522,6 +523,8 @@ copy_rectangle_argb32 (cairo_surface_t *dest, cairo_surface_t *source, kCVPixelFormatType_32BGRA, cfpb_props, &pixels); + //Force a new cairo_surface for drawing + _gdk_quartz_unref_cairo_surface (gdk_window); [super setFrame: frame]; if ([self window]) diff --git a/gdk/quartz/gdkprivate-quartz.h b/gdk/quartz/gdkprivate-quartz.h index 7900c01b17..a6e644b870 100644 --- a/gdk/quartz/gdkprivate-quartz.h +++ b/gdk/quartz/gdkprivate-quartz.h @@ -33,5 +33,7 @@ GdkDisplay * _gdk_quartz_display_open (const gchar *name); +/* Window Impl */ +void _gdk_quartz_unref_cairo_surface (GdkWindow *window); #endif /* __GDK_PRIVATE_QUARTZ_H__ */ diff --git a/gdk/quartz/gdkwindow-quartz.c b/gdk/quartz/gdkwindow-quartz.c index bdb668498c..4419975dc4 100644 --- a/gdk/quartz/gdkwindow-quartz.c +++ b/gdk/quartz/gdkwindow-quartz.c @@ -238,7 +238,9 @@ static void gdk_quartz_cairo_surface_destroy (void *data) { GdkQuartzCairoSurfaceData *surface_data = data; + cairo_surface_t *surface = surface_data->window_impl->cairo_surface; + if (!cairo_surface_get_reference_count (surface)) surface_data->window_impl->cairo_surface = NULL; g_free (surface_data); @@ -258,7 +260,6 @@ gdk_quartz_create_cairo_surface (GdkWindowImplQuartz *impl, surface_data->cg_context = NULL; surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); - cairo_surface_set_user_data (surface, &gdk_quartz_cairo_key, surface_data, gdk_quartz_cairo_surface_destroy); @@ -291,11 +292,29 @@ gdk_quartz_ref_cairo_surface (GdkWindow *window) cairo_surface_reference (impl->cairo_surface); // The caller will destroy the returned one. } else - cairo_surface_reference (impl->cairo_surface); + { + cairo_surface_reference (impl->cairo_surface); + } return impl->cairo_surface; } +void +_gdk_quartz_unref_cairo_surface (GdkWindow *window) +{ + GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (window->impl); + if (GDK_WINDOW_DESTROYED (window)) + return; + + if (impl->cairo_surface) + { + cairo_surface_destroy (impl->cairo_surface); + if (impl->cairo_surface && + !cairo_surface_get_reference_count (impl->cairo_surface)) + impl->cairo_surface = NULL; + } +} + static void gdk_window_impl_quartz_init (GdkWindowImplQuartz *impl) { @@ -1312,6 +1331,7 @@ move_resize_window_internal (GdkWindow *window, frame_rect = [impl->toplevel frameRectForContentRect:content_rect]; [impl->toplevel setFrame:frame_rect display:YES]; + impl->cairo_surface = gdk_quartz_ref_cairo_surface (window); } else {