From 92a7c7cdc3f97adc8446982a4ec62c0f73ef66c9 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 12:30:08 -0800 Subject: [PATCH 01/19] macos: move feedback mechanisms into separate file We will eventually be needing additional feedback from the display server which would be nice to keep away from the rest of GdkMacosDisplay for cleanliness sake. Particularly for feedback from mission control and other environment factors that requires private API for proper integration. --- gdk/macos/gdkmacosdisplay-feedback.c | 105 +++++++++++++++++++++++++++ gdk/macos/gdkmacosdisplay-private.h | 2 + gdk/macos/gdkmacosdisplay.c | 67 +---------------- gdk/macos/meson.build | 1 + 4 files changed, 111 insertions(+), 64 deletions(-) create mode 100644 gdk/macos/gdkmacosdisplay-feedback.c diff --git a/gdk/macos/gdkmacosdisplay-feedback.c b/gdk/macos/gdkmacosdisplay-feedback.c new file mode 100644 index 0000000000..868ac0fbcd --- /dev/null +++ b/gdk/macos/gdkmacosdisplay-feedback.c @@ -0,0 +1,105 @@ +/* + * Copyright © 2022 Red Hat, Inc. + * + * 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.1 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 . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include + +#include "gdkmacosdisplay-private.h" +#include "gdkmacossurface-private.h" + +static void +gdk_macos_display_user_defaults_changed_cb (CFNotificationCenterRef center, + void *observer, + CFStringRef name, + const void *object, + CFDictionaryRef userInfo) +{ + GdkMacosDisplay *self = observer; + + g_assert (GDK_IS_MACOS_DISPLAY (self)); + + _gdk_macos_display_reload_settings (self); +} + +static void +gdk_macos_display_monitors_changed_cb (CFNotificationCenterRef center, + void *observer, + CFStringRef name, + const void *object, + CFDictionaryRef userInfo) +{ + GdkMacosDisplay *self = observer; + + g_assert (GDK_IS_MACOS_DISPLAY (self)); + + _gdk_macos_display_reload_monitors (self); + + /* Now we need to update all our surface positions since they + * probably just changed origins. + */ + for (const GList *iter = _gdk_macos_display_get_surfaces (self); + iter != NULL; + iter = iter->next) + { + GdkMacosSurface *surface = iter->data; + + g_assert (GDK_IS_MACOS_SURFACE (surface)); + + _gdk_macos_surface_monitor_changed (surface); + } +} + + +void +_gdk_macos_display_feedback_init (GdkMacosDisplay *self) +{ + g_return_if_fail (GDK_IS_MACOS_DISPLAY (self)); + + CFNotificationCenterAddObserver (CFNotificationCenterGetLocalCenter (), + self, + gdk_macos_display_monitors_changed_cb, + CFSTR ("NSApplicationDidChangeScreenParametersNotification"), + NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); + + CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (), + self, + gdk_macos_display_user_defaults_changed_cb, + CFSTR ("NSUserDefaultsDidChangeNotification"), + NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); +} + +void +_gdk_macos_display_feedback_destroy (GdkMacosDisplay *self) +{ + g_return_if_fail (GDK_IS_MACOS_DISPLAY (self)); + + CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (), + self, + CFSTR ("NSApplicationDidChangeScreenParametersNotification"), + NULL); + + CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (), + self, + CFSTR ("NSUserDefaultsDidChangeNotification"), + NULL); + +} diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h index be2290b89a..b9f33fe20e 100644 --- a/gdk/macos/gdkmacosdisplay-private.h +++ b/gdk/macos/gdkmacosdisplay-private.h @@ -124,6 +124,8 @@ GdkMonitor *_gdk_macos_display_get_monitor_at_display_coords (GdkMacosDisp int y); GdkEvent *_gdk_macos_display_translate (GdkMacosDisplay *self, NSEvent *event); +void _gdk_macos_display_feedback_init (GdkMacosDisplay *self); +void _gdk_macos_display_feedback_destroy (GdkMacosDisplay *self); void _gdk_macos_display_break_all_grabs (GdkMacosDisplay *self, guint32 time); GdkModifierType _gdk_macos_display_get_current_keyboard_modifiers (GdkMacosDisplay *self); diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index 74095504a2..bc44eb3e93 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -159,48 +159,6 @@ gdk_macos_display_update_bounds (GdkMacosDisplay *self) GDK_END_MACOS_ALLOC_POOL; } -static void -gdk_macos_display_monitors_changed_cb (CFNotificationCenterRef center, - void *observer, - CFStringRef name, - const void *object, - CFDictionaryRef userInfo) -{ - GdkMacosDisplay *self = observer; - - g_assert (GDK_IS_MACOS_DISPLAY (self)); - - _gdk_macos_display_reload_monitors (self); - - /* Now we need to update all our surface positions since they - * probably just changed origins. - */ - for (const GList *iter = _gdk_macos_display_get_surfaces (self); - iter != NULL; - iter = iter->next) - { - GdkMacosSurface *surface = iter->data; - - g_assert (GDK_IS_MACOS_SURFACE (surface)); - - _gdk_macos_surface_monitor_changed (surface); - } -} - -static void -gdk_macos_display_user_defaults_changed_cb (CFNotificationCenterRef center, - void *observer, - CFStringRef name, - const void *object, - CFDictionaryRef userInfo) -{ - GdkMacosDisplay *self = observer; - - g_assert (GDK_IS_MACOS_DISPLAY (self)); - - _gdk_macos_display_reload_settings (self); -} - void _gdk_macos_display_reload_monitors (GdkMacosDisplay *self) { @@ -686,15 +644,7 @@ gdk_macos_display_finalize (GObject *object) { GdkMacosDisplay *self = (GdkMacosDisplay *)object; - CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (), - self, - CFSTR ("NSApplicationDidChangeScreenParametersNotification"), - NULL); - - CFNotificationCenterRemoveObserver (CFNotificationCenterGetDistributedCenter (), - self, - CFSTR ("NSUserDefaultsDidChangeNotification"), - NULL); + _gdk_macos_display_feedback_destroy (self); g_clear_pointer (&self->active_drags, g_hash_table_unref); g_clear_pointer (&self->active_drops, g_hash_table_unref); @@ -779,19 +729,8 @@ _gdk_macos_display_open (const char *display_name) gdk_macos_display_load_display_link (self); _gdk_macos_display_reload_monitors (self); - CFNotificationCenterAddObserver (CFNotificationCenterGetLocalCenter (), - self, - gdk_macos_display_monitors_changed_cb, - CFSTR ("NSApplicationDidChangeScreenParametersNotification"), - NULL, - CFNotificationSuspensionBehaviorDeliverImmediately); - - CFNotificationCenterAddObserver (CFNotificationCenterGetDistributedCenter (), - self, - gdk_macos_display_user_defaults_changed_cb, - CFSTR ("NSUserDefaultsDidChangeNotification"), - NULL, - CFNotificationSuspensionBehaviorDeliverImmediately); + /* Initialize feedback from display server */ + _gdk_macos_display_feedback_init (self); if (event_source == NULL) { diff --git a/gdk/macos/meson.build b/gdk/macos/meson.build index d17a60ac09..bd7bbb5324 100644 --- a/gdk/macos/meson.build +++ b/gdk/macos/meson.build @@ -8,6 +8,7 @@ gdk_macos_sources = files([ 'gdkmacoscursor.c', 'gdkmacosdevice.c', 'gdkmacosdisplay.c', + 'gdkmacosdisplay-feedback.c', 'gdkmacosdisplay-settings.c', 'gdkmacosdisplay-translate.c', 'gdkmacosdisplay-wm.c', From 4404c43cd33864d1a8c276c08c378df77d81e8d8 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 13:09:31 -0800 Subject: [PATCH 02/19] macos: use display id when creating CVDisplayLink Currently we're using a display link that is for all active displays which is just the display server trying to find some timings that try to overlap as many as possible. That was fine for a prototype, but we really need to do better for situations with mixed frame rate (such as 60hz and 120hz promotion displays). Additionally, the 144hz external monitor I have will never reach 144hz using the current design. This is just the first step in changing this, but the goal is to have one of these attached to each GdkMacosMonitor which we can then use to thaw surfaces specific to that monitor. --- gdk/macos/gdkdisplaylinksource.c | 15 +++++++-------- gdk/macos/gdkdisplaylinksource.h | 15 ++++++++------- gdk/macos/gdkmacosdisplay.c | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/gdk/macos/gdkdisplaylinksource.c b/gdk/macos/gdkdisplaylinksource.c index 292b8be519..8b2f1a013f 100644 --- a/gdk/macos/gdkdisplaylinksource.c +++ b/gdk/macos/gdkdisplaylinksource.c @@ -147,6 +147,7 @@ gdk_display_link_source_frame_cb (CVDisplayLinkRef display_link, /** * 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. @@ -159,7 +160,7 @@ gdk_display_link_source_frame_cb (CVDisplayLinkRef display_link, * Returns: (transfer full): A newly created `GSource` */ GSource * -gdk_display_link_source_new (void) +gdk_display_link_source_new (CGDirectDisplayID display_id) { GdkDisplayLinkSource *impl; GSource *source; @@ -168,15 +169,13 @@ gdk_display_link_source_new (void) source = g_source_new (&gdk_display_link_source_funcs, sizeof *impl); impl = (GdkDisplayLinkSource *)source; + impl->display_id = display_id; - /* - * 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. + /* 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. */ - ret = CVDisplayLinkCreateWithActiveCGDisplays (&impl->display_link); - if (ret != kCVReturnSuccess) + if (CVDisplayLinkCreateWithCGDisplay (display_id, &impl->display_link) != kCVReturnSuccess) { g_warning ("Failed to initialize CVDisplayLink!"); return source; diff --git a/gdk/macos/gdkdisplaylinksource.h b/gdk/macos/gdkdisplaylinksource.h index ed769b59f8..bff1c91475 100644 --- a/gdk/macos/gdkdisplaylinksource.h +++ b/gdk/macos/gdkdisplaylinksource.h @@ -30,17 +30,18 @@ G_BEGIN_DECLS typedef struct { - GSource source; + GSource source; - CVDisplayLinkRef display_link; - gint64 refresh_interval; - guint refresh_rate; + CGDirectDisplayID display_id; + CVDisplayLinkRef display_link; + gint64 refresh_interval; + guint refresh_rate; - volatile gint64 presentation_time; - volatile guint needs_dispatch; + volatile gint64 presentation_time; + volatile guint needs_dispatch; } GdkDisplayLinkSource; -GSource *gdk_display_link_source_new (void); +GSource *gdk_display_link_source_new (CGDirectDisplayID display_id); void gdk_display_link_source_pause (GdkDisplayLinkSource *source); void gdk_display_link_source_unpause (GdkDisplayLinkSource *source); diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index bc44eb3e93..5501a16318 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -273,7 +273,7 @@ gdk_macos_display_frame_cb (gpointer data) static void gdk_macos_display_load_display_link (GdkMacosDisplay *self) { - self->frame_source = gdk_display_link_source_new (); + self->frame_source = gdk_display_link_source_new (CGMainDisplayID ()); g_source_set_callback (self->frame_source, gdk_macos_display_frame_cb, self, From 1e01444de8fd00057b2eb47bdcc08d15e54a89e7 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 13:14:22 -0800 Subject: [PATCH 03/19] macos: remove duplicated opaque_region field This can be relied upon from GdkSurface and we do not need to keep a copy of it. Just remove it and use the GdkSurface.opaque_region field. --- gdk/macos/gdkmacossurface-private.h | 1 - gdk/macos/gdkmacossurface.c | 15 +++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h index 08947f1ce3..b853211afc 100644 --- a/gdk/macos/gdkmacossurface-private.h +++ b/gdk/macos/gdkmacossurface-private.h @@ -49,7 +49,6 @@ struct _GdkMacosSurface GdkMacosBuffer *buffer; GdkMacosBuffer *front; GPtrArray *monitors; - cairo_region_t *opaque_region; char *title; int root_x; diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index f837e2c650..f106ed4f09 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -115,12 +115,6 @@ gdk_macos_surface_set_opaque_region (GdkSurface *surface, g_assert (GDK_IS_MACOS_SURFACE (self)); - if (region != self->opaque_region) - { - g_clear_pointer (&self->opaque_region, cairo_region_destroy); - self->opaque_region = cairo_region_copy (region); - } - if ((nsview = _gdk_macos_surface_get_view (GDK_MACOS_SURFACE (surface)))) [(GdkMacosView *)nsview setOpaqueRegion:region]; } @@ -417,7 +411,6 @@ gdk_macos_surface_destroy (GdkSurface *surface, } g_clear_pointer (&self->title, g_free); - g_clear_pointer (&self->opaque_region, cairo_region_destroy); if (window != NULL) [window close]; @@ -624,14 +617,16 @@ _gdk_macos_surface_get_shadow (GdkMacosSurface *self, gboolean _gdk_macos_surface_is_opaque (GdkMacosSurface *self) { + GdkSurface *surface = (GdkSurface *)self; + g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), FALSE); - if (self->opaque_region != NULL && - cairo_region_num_rectangles (self->opaque_region) == 1) + if (surface->opaque_region != NULL && + cairo_region_num_rectangles (surface->opaque_region) == 1) { cairo_rectangle_int_t extents; - cairo_region_get_extents (self->opaque_region, &extents); + cairo_region_get_extents (surface->opaque_region, &extents); return (extents.x == 0 && extents.y == 0 && From 505e10f3eaaa0ac332020aa2fe46ac7dc835276e Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 13:52:08 -0800 Subject: [PATCH 04/19] macos: calculate best monitor when changing screens When we change screens, we can keep track of the best monitor so that we can use it to register CVDisplayLink callbacks. --- gdk/macos/gdkmacossurface-private.h | 1 + gdk/macos/gdkmacossurface.c | 54 ++++++++++++++--------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h index b853211afc..5ac4201a42 100644 --- a/gdk/macos/gdkmacossurface-private.h +++ b/gdk/macos/gdkmacossurface-private.h @@ -49,6 +49,7 @@ struct _GdkMacosSurface GdkMacosBuffer *buffer; GdkMacosBuffer *front; GPtrArray *monitors; + GdkMonitor *best_monitor; char *title; int root_x; diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index f106ed4f09..cdc6054bff 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -400,6 +400,8 @@ gdk_macos_surface_destroy (GdkSurface *surface, GdkMacosWindow *window = g_steal_pointer (&self->window); GdkFrameClock *frame_clock; + g_clear_object (&self->best_monitor); + if ((frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self)))) { g_signal_handlers_disconnect_by_func (frame_clock, @@ -1011,11 +1013,13 @@ void _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) { GListModel *monitors; + GdkMonitor *best = NULL; GdkRectangle rect; GdkRectangle intersect; GdkDisplay *display; GdkMonitor *monitor; guint n_monitors; + int best_area = 0; g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); @@ -1058,6 +1062,28 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) g_clear_object (&self->buffer); g_clear_object (&self->front); + /* Determine the best-fit monitor */ + for (guint i = 0; i < self->monitors->len; i++) + { + monitor = g_ptr_array_index (self->monitors, i); + + if (gdk_rectangle_intersect (&monitor->geometry, &rect, &intersect)) + { + int area = intersect.width * intersect.height; + + if (area > best_area) + { + best_area = area; + best = monitor; + } + } + } + + if (g_set_object (&self->best_monitor, best)) + { + /* TODO: change frame clock to new monitor */ + } + _gdk_macos_surface_configure (self); gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL); @@ -1066,35 +1092,9 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) GdkMonitor * _gdk_macos_surface_get_best_monitor (GdkMacosSurface *self) { - GdkMonitor *best = NULL; - GdkRectangle rect; - int best_area = 0; - g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL); - rect.x = self->root_x; - rect.y = self->root_y; - rect.width = GDK_SURFACE (self)->width; - rect.height = GDK_SURFACE (self)->height; - - for (guint i = 0; i < self->monitors->len; i++) - { - GdkMonitor *monitor = g_ptr_array_index (self->monitors, i); - GdkRectangle intersect; - - if (gdk_rectangle_intersect (&monitor->geometry, &rect, &intersect)) - { - int area = intersect.width * intersect.height; - - if (area > best_area) - { - best = monitor; - best_area = area; - } - } - } - - return best; + return self->best_monitor; } NSView * From 03882ef8e5ca7c391d482d0ca1c51d142e7ec472 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 14:02:06 -0800 Subject: [PATCH 05/19] macos: do not inherit parents frame clock Windows can end up on different monitors despite having a parent or transient-for ancestor. We want them to be driven by the CVDisplayLink for the best-monitor, and so this needs to be unshared. --- gdk/macos/gdkmacossurface.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index cdc6054bff..d032c37e21 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -558,10 +558,7 @@ _gdk_macos_surface_new (GdkMacosDisplay *display, g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (display), NULL); - if (parent != NULL) - frame_clock = g_object_ref (gdk_surface_get_frame_clock (parent)); - else - frame_clock = _gdk_frame_clock_idle_new (); + frame_clock = _gdk_frame_clock_idle_new (); switch (surface_type) { From 3a0077f65f28a9e7888471cf0485c80623c33a09 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Fri, 25 Feb 2022 23:22:05 -0800 Subject: [PATCH 06/19] macos: remove emulated scroll events We don't appear to actually need the emulated scroll events and they get in the way of proper scrolling with the touchpad. Fixes #4734 --- gdk/macos/gdkmacosdisplay-translate.c | 32 ++++++++------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index 646ba0cd9e..db1cfa2527 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -678,31 +678,17 @@ fill_scroll_event (GdkMacosDisplay *self, dy = 0.0; } - if (dx != 0.0 || dy != 0.0) + if ((dx != 0.0 || dy != 0.0) && ![nsevent hasPreciseScrollingDeltas]) { - if ([nsevent hasPreciseScrollingDeltas]) - { - GdkEvent *emulated; + g_assert (ret == NULL); - emulated = gdk_scroll_event_new_discrete (GDK_SURFACE (surface), - pointer, - NULL, - get_time_from_ns_event (nsevent), - state, - direction, - TRUE); - _gdk_event_queue_append (GDK_DISPLAY (self), emulated); - } - else - { - ret = gdk_scroll_event_new_discrete (GDK_SURFACE (surface), - pointer, - NULL, - get_time_from_ns_event (nsevent), - state, - direction, - FALSE); - } + ret = gdk_scroll_event_new_discrete (GDK_SURFACE (surface), + pointer, + NULL, + get_time_from_ns_event (nsevent), + state, + direction, + FALSE); } if (phase == NSEventPhaseEnded || phase == NSEventPhaseCancelled) From d14987e8195f8b588648ba95791797e84663b408 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Sat, 26 Feb 2022 13:31:18 -0800 Subject: [PATCH 07/19] macos: send stop event when fingers touch When the fingers are placed on the touchpad, we get a scroll event with the phase NSEventPhaseMayBegin. We can use this to synthesize an is_stop event. This results in the scrolledwindow stopping scroll with stop gestures. This can cause another warning as well, however, which should be addressed from #4730. Fixes #4733 --- gdk/macos/gdkmacosdisplay-translate.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index db1cfa2527..e261b8a4ae 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -635,6 +635,20 @@ fill_scroll_event (GdkMacosDisplay *self, state = _gdk_macos_display_get_current_mouse_modifiers (self) | _gdk_macos_display_get_current_keyboard_modifiers (self); + /* If we are starting a new phase, send a stop so any previous + * scrolling immediately stops. + */ + if (phase == NSEventPhaseMayBegin) + { + ret = gdk_scroll_event_new (GDK_SURFACE (surface), + pointer, + NULL, + get_time_from_ns_event (nsevent), + state, + 0.0, 0.0, TRUE); + _gdk_event_queue_append (GDK_DISPLAY (self), g_steal_pointer (&ret)); + } + dx = [nsevent deltaX]; dy = [nsevent deltaY]; From dac0b7d609740f0f330c59bd9696c55302ec434f Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Sun, 27 Feb 2022 23:17:40 -0800 Subject: [PATCH 08/19] macos: use video mode for refresh rate and interval Using the mode allows better detection of refresh rate and refresh interval for the CVDisplayLink bridge to GdkFrameClock. Using it can help ensure that our 144hz displays can actually reach that rather than falling back to just 60hz. This will also need future commits to rework the displaylink source to be per-monitor. --- gdk/macos/gdkdisplaylinksource.c | 59 +++++++++++++++++++++++--------- gdk/macos/gdkdisplaylinksource.h | 4 ++- 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/gdk/macos/gdkdisplaylinksource.c b/gdk/macos/gdkdisplaylinksource.c index 8b2f1a013f..fe75eb3a55 100644 --- a/gdk/macos/gdkdisplaylinksource.c +++ b/gdk/macos/gdkdisplaylinksource.c @@ -65,7 +65,7 @@ gdk_display_link_source_dispatch (GSource *source, impl->needs_dispatch = FALSE; - if (callback != NULL) + if (!impl->paused && callback != NULL) ret = callback (user_data); return ret; @@ -76,7 +76,9 @@ gdk_display_link_source_finalize (GSource *source) { GdkDisplayLinkSource *impl = (GdkDisplayLinkSource *)source; - CVDisplayLinkStop (impl->display_link); + if (!impl->paused) + CVDisplayLinkStop (impl->display_link); + CVDisplayLinkRelease (impl->display_link); } @@ -90,12 +92,18 @@ static GSourceFuncs gdk_display_link_source_funcs = { 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); } @@ -160,16 +168,16 @@ gdk_display_link_source_frame_cb (CVDisplayLinkRef display_link, * Returns: (transfer full): A newly created `GSource` */ GSource * -gdk_display_link_source_new (CGDirectDisplayID display_id) +gdk_display_link_source_new (CGDirectDisplayID display_id, + CGDisplayModeRef mode) { GdkDisplayLinkSource *impl; GSource *source; - CVReturn ret; - double period; 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 @@ -178,21 +186,34 @@ gdk_display_link_source_new (CGDirectDisplayID display_id) if (CVDisplayLinkCreateWithCGDisplay (display_id, &impl->display_link) != kCVReturnSuccess) { g_warning ("Failed to initialize CVDisplayLink!"); - return source; + goto failure; } - /* - * 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; + impl->refresh_rate = CGDisplayModeGetRefreshRate (mode) * 1000.0; - /* - * Wire up our callback to be executed within the high-priority thread. - */ + 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; + } + + /* Wire up our callback to be executed within the high-priority thread. */ CVDisplayLinkSetOutputCallback (impl->display_link, gdk_display_link_source_frame_cb, source); @@ -200,6 +221,10 @@ gdk_display_link_source_new (CGDirectDisplayID display_id) g_source_set_static_name (source, "[gdk] quartz frame clock"); return source; + +failure: + g_source_unref (source); + return NULL; } static gint64 diff --git a/gdk/macos/gdkdisplaylinksource.h b/gdk/macos/gdkdisplaylinksource.h index bff1c91475..6f465907a2 100644 --- a/gdk/macos/gdkdisplaylinksource.h +++ b/gdk/macos/gdkdisplaylinksource.h @@ -36,12 +36,14 @@ typedef struct CVDisplayLinkRef display_link; gint64 refresh_interval; guint refresh_rate; + guint paused : 1; volatile gint64 presentation_time; volatile guint needs_dispatch; } GdkDisplayLinkSource; -GSource *gdk_display_link_source_new (CGDirectDisplayID display_id); +GSource *gdk_display_link_source_new (CGDirectDisplayID display_id, + CGDisplayModeRef mode); void gdk_display_link_source_pause (GdkDisplayLinkSource *source); void gdk_display_link_source_unpause (GdkDisplayLinkSource *source); From 478bf453208247cd1159cee3ea4863f0ba3b109b Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Sun, 27 Feb 2022 23:31:28 -0800 Subject: [PATCH 09/19] macos: support mix-rate monitors Previously, a single CVDisplayLink was used to drive updates for all surfaces across all monitors. It used a 'best guess' rate which would allow for updates across monitors of mixed rates. This is undesirable for situations where you might have a 144hz monitor as it does not allow for reaching up to that frame rate. Instead, we want to use a per-monitor CVDisplayLink which will fire at the rate of the monitor down to the level of updates we require. This commit does just that. When a surface crosses onto a new monitor, that monitor is used to drive the GdkFrameClock. Fixes #4732 --- gdk/macos/gdkmacosdisplay-private.h | 15 --- gdk/macos/gdkmacosdisplay.c | 105 ------------------- gdk/macos/gdkmacosmonitor-private.h | 14 ++- gdk/macos/gdkmacosmonitor.c | 139 ++++++++++++++++++++++++- gdk/macos/gdkmacossurface-private.h | 7 +- gdk/macos/gdkmacossurface.c | 153 +++++++++++++++++----------- 6 files changed, 243 insertions(+), 190 deletions(-) diff --git a/gdk/macos/gdkmacosdisplay-private.h b/gdk/macos/gdkmacosdisplay-private.h index b9f33fe20e..ef17618d61 100644 --- a/gdk/macos/gdkmacosdisplay-private.h +++ b/gdk/macos/gdkmacosdisplay-private.h @@ -68,16 +68,6 @@ struct _GdkMacosDisplay */ GQueue sorted_surfaces; - /* Our CVDisplayLink based GSource which we use to freeze/thaw the - * GdkFrameClock for the surface. - */ - GSource *frame_source; - - /* A queue of surfaces which we know are awaiting frames to be drawn. This - * uses the GdkMacosSurface.frame link. - */ - GQueue awaiting_frames; - /* The surface that is receiving keyboard events */ GdkMacosSurface *keyboard_surface; @@ -138,10 +128,6 @@ GdkMacosSurface *_gdk_macos_display_get_surface_at_display_coords (GdkMacosDisp void _gdk_macos_display_reload_monitors (GdkMacosDisplay *self); void _gdk_macos_display_surface_removed (GdkMacosDisplay *self, GdkMacosSurface *surface); -void _gdk_macos_display_add_frame_callback (GdkMacosDisplay *self, - GdkMacosSurface *surface); -void _gdk_macos_display_remove_frame_callback (GdkMacosDisplay *self, - GdkMacosSurface *surface); NSWindow *_gdk_macos_display_find_native_under_pointer (GdkMacosDisplay *self, int *x, int *y); @@ -157,7 +143,6 @@ void _gdk_macos_display_surface_resigned_key (GdkMacosDisp GdkMacosSurface *surface); void _gdk_macos_display_surface_became_key (GdkMacosDisplay *self, GdkMacosSurface *surface); -int _gdk_macos_display_get_nominal_refresh_rate (GdkMacosDisplay *self); void _gdk_macos_display_clear_sorting (GdkMacosDisplay *self); const GList *_gdk_macos_display_get_surfaces (GdkMacosDisplay *self); void _gdk_macos_display_send_button_event (GdkMacosDisplay *self, diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index 5501a16318..cb0331419d 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -231,56 +231,6 @@ gdk_macos_display_load_seat (GdkMacosDisplay *self) g_object_unref (seat); } -static gboolean -gdk_macos_display_frame_cb (gpointer data) -{ - GdkMacosDisplay *self = data; - GdkDisplayLinkSource *source; - gint64 presentation_time; - gint64 now; - GList *iter; - - g_assert (GDK_IS_MACOS_DISPLAY (self)); - - source = (GdkDisplayLinkSource *)self->frame_source; - - presentation_time = source->presentation_time; - now = g_source_get_time ((GSource *)source); - - iter = self->awaiting_frames.head; - - while (iter != NULL) - { - GdkMacosSurface *surface = iter->data; - - g_assert (GDK_IS_MACOS_SURFACE (surface)); - - iter = iter->next; - - _gdk_macos_surface_publish_timings (surface, - source->presentation_time, - source->refresh_interval); - - _gdk_macos_display_remove_frame_callback (self, surface); - - if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (surface))) - gdk_surface_thaw_updates (GDK_SURFACE (surface)); - } - - return G_SOURCE_CONTINUE; -} - -static void -gdk_macos_display_load_display_link (GdkMacosDisplay *self) -{ - self->frame_source = gdk_display_link_source_new (CGMainDisplayID ()); - g_source_set_callback (self->frame_source, - gdk_macos_display_frame_cb, - self, - NULL); - g_source_attach (self->frame_source, NULL); -} - static const char * gdk_macos_display_get_name (GdkDisplay *display) { @@ -384,7 +334,6 @@ _gdk_macos_display_surface_added (GdkMacosDisplay *self, g_assert (GDK_IS_MACOS_SURFACE (surface)); g_assert (!queue_contains (&self->sorted_surfaces, &surface->sorted)); g_assert (!queue_contains (&self->main_surfaces, &surface->main)); - g_assert (!queue_contains (&self->awaiting_frames, &surface->frame)); g_assert (surface->sorted.data == surface); g_assert (surface->main.data == surface); g_assert (surface->frame.data == surface); @@ -411,9 +360,6 @@ _gdk_macos_display_surface_removed (GdkMacosDisplay *self, if (queue_contains (&self->main_surfaces, &surface->main)) _gdk_macos_display_surface_resigned_main (self, surface); - if (queue_contains (&self->awaiting_frames, &surface->frame)) - g_queue_unlink (&self->awaiting_frames, &surface->frame); - g_return_if_fail (self->keyboard_surface != surface); } @@ -649,7 +595,6 @@ gdk_macos_display_finalize (GObject *object) g_clear_pointer (&self->active_drags, g_hash_table_unref); g_clear_pointer (&self->active_drops, g_hash_table_unref); g_clear_object (&GDK_DISPLAY (self)->clipboard); - g_clear_pointer (&self->frame_source, g_source_unref); g_clear_object (&self->monitors); g_clear_pointer (&self->name, g_free); @@ -724,9 +669,6 @@ _gdk_macos_display_open (const char *display_name) gdk_macos_display_load_seat (self); gdk_macos_display_load_clipboard (self); - - /* Load CVDisplayLink before monitors to access refresh rates */ - gdk_macos_display_load_display_link (self); _gdk_macos_display_reload_monitors (self); /* Initialize feedback from display server */ @@ -972,42 +914,6 @@ _gdk_macos_display_get_surface_at_display_coords (GdkMacosDisplay *self, return _gdk_macos_display_get_surface_at_coords (self, x_gdk, y_gdk, surface_x, surface_y); } -void -_gdk_macos_display_add_frame_callback (GdkMacosDisplay *self, - GdkMacosSurface *surface) -{ - g_return_if_fail (GDK_IS_MACOS_DISPLAY (self)); - g_return_if_fail (GDK_IS_MACOS_SURFACE (surface)); - - if (!queue_contains (&self->awaiting_frames, &surface->frame)) - { - /* Processing frames is always head to tail, so push to the - * head so that we don't possibly re-enter this right after - * adding to the queue. - */ - g_queue_push_head_link (&self->awaiting_frames, &surface->frame); - - if (self->awaiting_frames.length == 1) - gdk_display_link_source_unpause ((GdkDisplayLinkSource *)self->frame_source); - } -} - -void -_gdk_macos_display_remove_frame_callback (GdkMacosDisplay *self, - GdkMacosSurface *surface) -{ - g_return_if_fail (GDK_IS_MACOS_DISPLAY (self)); - g_return_if_fail (GDK_IS_MACOS_SURFACE (surface)); - - if (queue_contains (&self->awaiting_frames, &surface->frame)) - { - g_queue_unlink (&self->awaiting_frames, &surface->frame); - - if (self->awaiting_frames.length == 0) - gdk_display_link_source_pause ((GdkDisplayLinkSource *)self->frame_source); - } -} - NSWindow * _gdk_macos_display_find_native_under_pointer (GdkMacosDisplay *self, int *x, @@ -1027,17 +933,6 @@ _gdk_macos_display_find_native_under_pointer (GdkMacosDisplay *self, return NULL; } -int -_gdk_macos_display_get_nominal_refresh_rate (GdkMacosDisplay *self) -{ - g_return_val_if_fail (GDK_IS_MACOS_DISPLAY (self), 60 * 1000); - - if (self->frame_source == NULL) - return 60 * 1000; - - return ((GdkDisplayLinkSource *)self->frame_source)->refresh_rate; -} - void _gdk_macos_display_clear_sorting (GdkMacosDisplay *self) { diff --git a/gdk/macos/gdkmacosmonitor-private.h b/gdk/macos/gdkmacosmonitor-private.h index e15f17352d..fee511d057 100644 --- a/gdk/macos/gdkmacosmonitor-private.h +++ b/gdk/macos/gdkmacosmonitor-private.h @@ -29,11 +29,15 @@ G_BEGIN_DECLS -GdkMacosMonitor *_gdk_macos_monitor_new (GdkMacosDisplay *display, - CGDirectDisplayID screen_id); -CGDirectDisplayID _gdk_macos_monitor_get_screen_id (GdkMacosMonitor *self); -gboolean _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self); -CGColorSpaceRef _gdk_macos_monitor_copy_colorspace (GdkMacosMonitor *self); +GdkMacosMonitor *_gdk_macos_monitor_new (GdkMacosDisplay *display, + CGDirectDisplayID screen_id); +CGDirectDisplayID _gdk_macos_monitor_get_screen_id (GdkMacosMonitor *self); +gboolean _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self); +CGColorSpaceRef _gdk_macos_monitor_copy_colorspace (GdkMacosMonitor *self); +void _gdk_macos_monitor_add_frame_callback (GdkMacosMonitor *self, + GdkMacosSurface *surface); +void _gdk_macos_monitor_remove_frame_callback (GdkMacosMonitor *self, + GdkMacosSurface *surface); G_END_DECLS diff --git a/gdk/macos/gdkmacosmonitor.c b/gdk/macos/gdkmacosmonitor.c index 6df1da0edc..3773ec5355 100644 --- a/gdk/macos/gdkmacosmonitor.c +++ b/gdk/macos/gdkmacosmonitor.c @@ -22,16 +22,21 @@ #include #include +#include "gdkdisplaylinksource.h" #include "gdkmacosdisplay-private.h" #include "gdkmacosmonitor-private.h" +#include "gdkmacossurface-private.h" #include "gdkmacosutils-private.h" struct _GdkMacosMonitor { - GdkMonitor parent_instance; - CGDirectDisplayID screen_id; - NSRect workarea; - guint has_opengl : 1; + GdkMonitor parent_instance; + CGDirectDisplayID screen_id; + GdkDisplayLinkSource *display_link; + NSRect workarea; + GQueue awaiting_frames; + guint has_opengl : 1; + guint in_frame : 1; }; struct _GdkMacosMonitorClass @@ -75,9 +80,26 @@ gdk_macos_monitor_get_workarea (GdkMonitor *monitor, geometry->height = self->workarea.size.height; } +static void +gdk_macos_monitor_dispose (GObject *object) +{ + GdkMacosMonitor *self = (GdkMacosMonitor *)object; + + if (self->display_link) + { + g_source_destroy ((GSource *)self->display_link); + g_clear_pointer ((GSource **)&self->display_link, g_source_unref); + } + + G_OBJECT_CLASS (gdk_macos_monitor_parent_class)->dispose (object); +} + static void gdk_macos_monitor_class_init (GdkMacosMonitorClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gdk_macos_monitor_dispose; } static void @@ -188,6 +210,68 @@ find_screen (CGDirectDisplayID screen_id) return screen; } +static gboolean +gdk_macos_monitor_display_link_cb (GdkMacosMonitor *self) +{ + gint64 presentation_time; + gint64 refresh_interval; + gint64 now; + GList *iter; + + g_assert (GDK_IS_MACOS_MONITOR (self)); + g_assert (!self->display_link->paused); + + self->in_frame = TRUE; + + presentation_time = self->display_link->presentation_time; + refresh_interval = self->display_link->refresh_interval; + now = g_source_get_time ((GSource *)self->display_link); + + iter = self->awaiting_frames.head; + + while (iter != NULL) + { + GdkMacosSurface *surface = iter->data; + + g_assert (GDK_IS_MACOS_SURFACE (surface)); + + iter = iter->next; + + g_queue_unlink (&self->awaiting_frames, &surface->frame); + _gdk_macos_surface_frame_presented (surface, presentation_time, refresh_interval); + } + + if (self->awaiting_frames.length == 0 && !self->display_link->paused) + gdk_display_link_source_pause (self->display_link); + + self->in_frame = FALSE; + + return G_SOURCE_CONTINUE; +} + +static void +_gdk_macos_monitor_reset_display_link (GdkMacosMonitor *self, + CGDisplayModeRef mode) +{ + GSource *source; + + g_assert (GDK_IS_MACOS_MONITOR (self)); + + if (self->display_link) + { + g_source_destroy ((GSource *)self->display_link); + g_clear_pointer ((GSource **)&self->display_link, g_source_unref); + } + + source = gdk_display_link_source_new (self->screen_id, mode); + self->display_link = (GdkDisplayLinkSource *)source; + g_source_set_callback (source, + (GSourceFunc) gdk_macos_monitor_display_link_cb, + self, + NULL); + g_source_attach (source, NULL); +} + gboolean _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self) { @@ -241,7 +325,7 @@ _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self) * setting (which is also used by the frame clock). */ if (!(refresh_rate = CGDisplayModeGetRefreshRate (mode))) - refresh_rate = _gdk_macos_display_get_nominal_refresh_rate (display); + refresh_rate = 60 * 1000; gdk_monitor_set_connector (GDK_MONITOR (self), connector); gdk_monitor_set_model (GDK_MONITOR (self), name); @@ -261,6 +345,9 @@ _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self) */ self->has_opengl = !!has_opengl; + /* Create a new display link to receive feedback about when to render */ + _gdk_macos_monitor_reset_display_link (self, mode); + CGDisplayModeRelease (mode); g_free (name); g_free (connector); @@ -302,3 +389,45 @@ _gdk_macos_monitor_copy_colorspace (GdkMacosMonitor *self) return CGDisplayCopyColorSpace (self->screen_id); } + +void +_gdk_macos_monitor_add_frame_callback (GdkMacosMonitor *self, + GdkMacosSurface *surface) +{ + g_return_if_fail (GDK_IS_MACOS_MONITOR (self)); + g_return_if_fail (GDK_IS_MACOS_SURFACE (surface)); + g_return_if_fail (surface->frame.data == (gpointer)surface); + g_return_if_fail (surface->frame.prev == NULL); + g_return_if_fail (surface->frame.next == NULL); + g_return_if_fail (self->awaiting_frames.head != &surface->frame); + g_return_if_fail (self->awaiting_frames.tail != &surface->frame); + + /* Processing frames is always head to tail, so push to the + * head so that we don't possibly re-enter this right after + * adding to the queue. + */ + if (!queue_contains (&self->awaiting_frames, &surface->frame)) + { + g_queue_push_head_link (&self->awaiting_frames, &surface->frame); + + if (!self->in_frame && self->awaiting_frames.length == 1) + gdk_display_link_source_unpause (self->display_link); + } +} + +void +_gdk_macos_monitor_remove_frame_callback (GdkMacosMonitor *self, + GdkMacosSurface *surface) +{ + g_return_if_fail (GDK_IS_MACOS_MONITOR (self)); + g_return_if_fail (GDK_IS_MACOS_SURFACE (surface)); + g_return_if_fail (surface->frame.data == (gpointer)surface); + + if (queue_contains (&self->awaiting_frames, &surface->frame)) + { + g_queue_unlink (&self->awaiting_frames, &surface->frame); + + if (!self->in_frame && self->awaiting_frames.length == 0) + gdk_display_link_source_pause (self->display_link); + } +} diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h index 5ac4201a42..15a7442c17 100644 --- a/gdk/macos/gdkmacossurface-private.h +++ b/gdk/macos/gdkmacossurface-private.h @@ -75,6 +75,8 @@ struct _GdkMacosSurface guint geometry_dirty : 1; guint next_frame_set : 1; guint show_on_next_swap : 1; + guint in_frame : 1; + guint awaiting_frame : 1; }; struct _GdkMacosSurfaceClass @@ -116,10 +118,11 @@ void _gdk_macos_surface_resize (GdkMacosSurface int width, int height); void _gdk_macos_surface_update_fullscreen_state (GdkMacosSurface *self); -void _gdk_macos_surface_show (GdkMacosSurface *self); -void _gdk_macos_surface_publish_timings (GdkMacosSurface *self, +void _gdk_macos_surface_request_frame (GdkMacosSurface *self); +void _gdk_macos_surface_frame_presented (GdkMacosSurface *self, gint64 predicted_presentation_time, gint64 refresh_interval); +void _gdk_macos_surface_show (GdkMacosSurface *self); void _gdk_macos_surface_synthesize_null_key (GdkMacosSurface *self); void _gdk_macos_surface_move (GdkMacosSurface *self, int x, diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index d032c37e21..6eb23f2807 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -63,6 +63,73 @@ window_is_fullscreen (GdkMacosSurface *self) return ([self->window styleMask] & NSWindowStyleMaskFullScreen) != 0; } +void +_gdk_macos_surface_request_frame (GdkMacosSurface *self) +{ + g_assert (GDK_IS_MACOS_SURFACE (self)); + + if (self->awaiting_frame) + return; + + self->awaiting_frame = TRUE; + _gdk_macos_monitor_add_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); + gdk_surface_freeze_updates (GDK_SURFACE (self)); +} + +static void +_gdk_macos_surface_cancel_frame (GdkMacosSurface *self) +{ + g_assert (GDK_IS_MACOS_SURFACE (self)); + + if (!self->awaiting_frame) + return; + + self->awaiting_frame = FALSE; + _gdk_macos_monitor_remove_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); + gdk_surface_thaw_updates (GDK_SURFACE (self)); +} + +void +_gdk_macos_surface_frame_presented (GdkMacosSurface *self, + gint64 presentation_time, + gint64 refresh_interval) +{ + GdkFrameTimings *timings; + GdkFrameClock *frame_clock; + + g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); + + self->awaiting_frame = FALSE; + + if (GDK_SURFACE_DESTROYED (self)) + return; + + frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self)); + + if (self->pending_frame_counter) + { + timings = gdk_frame_clock_get_timings (frame_clock, self->pending_frame_counter); + + if (timings != NULL) + { + timings->presentation_time = presentation_time - refresh_interval; + timings->complete = TRUE; + } + + self->pending_frame_counter = 0; + } + + timings = gdk_frame_clock_get_current_timings (frame_clock); + + if (timings != NULL) + { + timings->refresh_interval = refresh_interval; + timings->predicted_presentation_time = presentation_time; + } + + if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self))) + gdk_surface_thaw_updates (GDK_SURFACE (self)); +} void _gdk_macos_surface_reposition_children (GdkMacosSurface *self) @@ -131,8 +198,6 @@ gdk_macos_surface_hide (GdkSurface *surface) self->show_on_next_swap = FALSE; - _gdk_macos_display_remove_frame_callback (GDK_MACOS_DISPLAY (surface->display), self); - was_mapped = GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self)); was_key = [self->window isKeyWindow]; @@ -157,8 +222,12 @@ gdk_macos_surface_hide (GdkSurface *surface) } } - if (was_mapped) - gdk_surface_freeze_updates (GDK_SURFACE (self)); + if (self->awaiting_frame) + { + self->awaiting_frame = FALSE; + _gdk_macos_monitor_remove_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); + gdk_surface_freeze_updates (surface); + } } static int @@ -202,6 +271,7 @@ gdk_macos_surface_begin_frame (GdkMacosSurface *self) { g_assert (GDK_IS_MACOS_SURFACE (self)); + self->in_frame = TRUE; } static void @@ -222,11 +292,9 @@ gdk_macos_surface_end_frame (GdkMacosSurface *self) if ((timings = gdk_frame_clock_get_current_timings (frame_clock))) self->pending_frame_counter = timings->frame_counter; - if (GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self))) - { - _gdk_macos_display_add_frame_callback (GDK_MACOS_DISPLAY (display), self); - gdk_surface_freeze_updates (GDK_SURFACE (self)); - } + self->in_frame = FALSE; + + _gdk_macos_surface_request_frame (self); } static void @@ -400,7 +468,16 @@ gdk_macos_surface_destroy (GdkSurface *surface, GdkMacosWindow *window = g_steal_pointer (&self->window); GdkFrameClock *frame_clock; - g_clear_object (&self->best_monitor); + if (self->best_monitor) + { + if (self->awaiting_frame) + { + _gdk_macos_monitor_remove_frame_callback (GDK_MACOS_MONITOR (self->best_monitor), self); + self->awaiting_frame = FALSE; + } + + g_clear_object (&self->best_monitor); + } if ((frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self)))) { @@ -813,41 +890,6 @@ _gdk_macos_surface_configure (GdkMacosSurface *self) _gdk_macos_surface_reposition_children (self); } -void -_gdk_macos_surface_publish_timings (GdkMacosSurface *self, - gint64 presentation_time, - gint64 refresh_interval) -{ - GdkFrameTimings *timings; - GdkFrameClock *frame_clock; - - g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); - - if (!(frame_clock = gdk_surface_get_frame_clock (GDK_SURFACE (self)))) - return; - - if (self->pending_frame_counter) - { - timings = gdk_frame_clock_get_timings (frame_clock, self->pending_frame_counter); - - if (timings != NULL) - { - timings->presentation_time = presentation_time - refresh_interval; - timings->complete = TRUE; - } - - self->pending_frame_counter = 0; - } - - timings = gdk_frame_clock_get_current_timings (frame_clock); - - if (timings != NULL) - { - timings->refresh_interval = refresh_interval; - timings->predicted_presentation_time = presentation_time; - } -} - void _gdk_macos_surface_show (GdkMacosSurface *self) { @@ -858,22 +900,15 @@ _gdk_macos_surface_show (GdkMacosSurface *self) if (GDK_SURFACE_DESTROYED (self)) return; + _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display)); + self->show_on_next_swap = TRUE; + was_mapped = GDK_SURFACE_IS_MAPPED (GDK_SURFACE (self)); - if (!was_mapped) - gdk_surface_set_is_mapped (GDK_SURFACE (self), TRUE); - - _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display)); - - self->show_on_next_swap = TRUE; - if (!was_mapped) { - if (gdk_surface_get_mapped (GDK_SURFACE (self))) - { - _gdk_macos_surface_configure (self); - gdk_surface_thaw_updates (GDK_SURFACE (self)); - } + gdk_surface_set_is_mapped (GDK_SURFACE (self), TRUE); + gdk_surface_thaw_updates (GDK_SURFACE (self)); } } @@ -1020,6 +1055,8 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); + _gdk_macos_surface_cancel_frame (self); + rect.x = self->root_x; rect.y = self->root_y; rect.width = GDK_SURFACE (self)->width; @@ -1082,8 +1119,8 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) } _gdk_macos_surface_configure (self); - gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL); + _gdk_macos_surface_request_frame (self); } GdkMonitor * From 1e40033852633b62a751510f6c1aa6b83445e5b3 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 00:42:47 -0800 Subject: [PATCH 10/19] macos: short-circuit on NSEventPhaseMayBegin We only need to send a single event in this case, so just short-circuit instead of trying to return an additional event. --- gdk/macos/gdkmacosdisplay-translate.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/gdk/macos/gdkmacosdisplay-translate.c b/gdk/macos/gdkmacosdisplay-translate.c index e261b8a4ae..e3fb03fea5 100644 --- a/gdk/macos/gdkmacosdisplay-translate.c +++ b/gdk/macos/gdkmacosdisplay-translate.c @@ -639,15 +639,12 @@ fill_scroll_event (GdkMacosDisplay *self, * scrolling immediately stops. */ if (phase == NSEventPhaseMayBegin) - { - ret = gdk_scroll_event_new (GDK_SURFACE (surface), - pointer, - NULL, - get_time_from_ns_event (nsevent), - state, - 0.0, 0.0, TRUE); - _gdk_event_queue_append (GDK_DISPLAY (self), g_steal_pointer (&ret)); - } + return gdk_scroll_event_new (GDK_SURFACE (surface), + pointer, + NULL, + get_time_from_ns_event (nsevent), + state, + 0.0, 0.0, TRUE); dx = [nsevent deltaX]; dy = [nsevent deltaY]; From dbede0b11599b284bf57154a334254789fc16ab1 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 01:29:22 -0800 Subject: [PATCH 11/19] macos: add readonly IOSurfaceLock helper This can be used to lock a surface for reading to avoid causing the surface contents to be invalidated. This is needed when reading back from a front-buffer to the back-buffer as is needed when using Cairo surfaces to implement something similar to BufferAge. --- gdk/macos/gdkmacosbuffer-private.h | 2 ++ gdk/macos/gdkmacosbuffer.c | 39 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/gdk/macos/gdkmacosbuffer-private.h b/gdk/macos/gdkmacosbuffer-private.h index 6be201147c..4b446a7212 100644 --- a/gdk/macos/gdkmacosbuffer-private.h +++ b/gdk/macos/gdkmacosbuffer-private.h @@ -41,6 +41,8 @@ GdkMacosBuffer *_gdk_macos_buffer_new (int width IOSurfaceRef _gdk_macos_buffer_get_native (GdkMacosBuffer *self); void _gdk_macos_buffer_lock (GdkMacosBuffer *self); void _gdk_macos_buffer_unlock (GdkMacosBuffer *self); +void _gdk_macos_buffer_read_lock (GdkMacosBuffer *self); +void _gdk_macos_buffer_read_unlock (GdkMacosBuffer *self); guint _gdk_macos_buffer_get_width (GdkMacosBuffer *self); guint _gdk_macos_buffer_get_height (GdkMacosBuffer *self); guint _gdk_macos_buffer_get_stride (GdkMacosBuffer *self); diff --git a/gdk/macos/gdkmacosbuffer.c b/gdk/macos/gdkmacosbuffer.c index ac99302ee4..46a0504461 100644 --- a/gdk/macos/gdkmacosbuffer.c +++ b/gdk/macos/gdkmacosbuffer.c @@ -192,6 +192,45 @@ _gdk_macos_buffer_unlock (GdkMacosBuffer *self) IOSurfaceUnlock (self->surface, 0, NULL); } +/** + * _gdk_macos_buffer_lock_readonly: + * + * Like _gdk_macos_buffer_lock() but uses the read-only flag to + * indicate we are not interested in retrieving the updates from + * the GPU before modifying the CPU-side cache. + * + * Must be used with _gdk_macos_buffer_unlock_readonly(). + */ +void +_gdk_macos_buffer_read_lock (GdkMacosBuffer *self) +{ + kern_return_t ret; + + g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); + g_return_if_fail (self->lock_count == 0); + + self->lock_count++; + + ret = IOSurfaceLock (self->surface, kIOSurfaceLockReadOnly, NULL); + + g_return_if_fail (ret == KERN_SUCCESS); +} + +void +_gdk_macos_buffer_read_unlock (GdkMacosBuffer *self) +{ + kern_return_t ret; + + g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); + g_return_if_fail (self->lock_count == 1); + + self->lock_count--; + + ret = IOSurfaceUnlock (self->surface, kIOSurfaceLockReadOnly, NULL); + + g_return_if_fail (ret == KERN_SUCCESS); +} + guint _gdk_macos_buffer_get_width (GdkMacosBuffer *self) { From fc4d36e50afe446797aa422382dfa9fb53d37d0b Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 01:42:48 -0800 Subject: [PATCH 12/19] macos: fix cairo renderer with double buffering If we are double buffering surfaces with IOSurface then we need to copy the area that was damaged in the previous frame to the back buffer. This can be done with IOSurface but we need to hold the read-only lock so that we don't cause the underlying IOSurface contents to be invalidated. Additionally, since this is only used in the context of rendering to a GdkMacosSurface, we know the life-time of the cairo_surface_t and can simply lock/unlock the IOSurface buffer from begin_frame/end_frame to have the buffer flushing semantics we want. To ensure that we don't over damage, we store the damage in begin_frame (and copy it) and then subtract it from the next frames damage to determine the smallest amount we need to copy (taking scale factor into account). We don't care to modify the damage region to swapBuffers because they already have the right contents and could potentially fall into another tile anyway and we'd like to avoid damaging that. Fixes #4735 --- gdk/macos/gdkmacosbuffer.c | 2 +- gdk/macos/gdkmacoscairocontext.c | 120 +++++++++++++++++++++++-------- 2 files changed, 93 insertions(+), 29 deletions(-) diff --git a/gdk/macos/gdkmacosbuffer.c b/gdk/macos/gdkmacosbuffer.c index 46a0504461..eb8a719dbc 100644 --- a/gdk/macos/gdkmacosbuffer.c +++ b/gdk/macos/gdkmacosbuffer.c @@ -281,7 +281,7 @@ _gdk_macos_buffer_set_damage (GdkMacosBuffer *self, return; g_clear_pointer (&self->damage, cairo_region_destroy); - self->damage = cairo_region_reference (damage); + self->damage = cairo_region_copy (damage); } gpointer diff --git a/gdk/macos/gdkmacoscairocontext.c b/gdk/macos/gdkmacoscairocontext.c index 041f1193e6..31eecd5df6 100644 --- a/gdk/macos/gdkmacoscairocontext.c +++ b/gdk/macos/gdkmacoscairocontext.c @@ -42,19 +42,6 @@ struct _GdkMacosCairoContextClass G_DEFINE_TYPE (GdkMacosCairoContext, _gdk_macos_cairo_context, GDK_TYPE_CAIRO_CONTEXT) -static const cairo_user_data_key_t buffer_key; - -static void -unlock_buffer (gpointer data) -{ - GdkMacosBuffer *buffer = data; - - g_assert (GDK_IS_MACOS_BUFFER (buffer)); - - _gdk_macos_buffer_unlock (buffer); - g_clear_object (&buffer); -} - static cairo_t * _gdk_macos_cairo_context_cairo_create (GdkCairoContext *cairo_context) { @@ -106,12 +93,9 @@ _gdk_macos_cairo_context_cairo_create (GdkCairoContext *cairo_context) stride); cairo_surface_set_device_scale (image_surface, scale, scale); - /* Lock the buffer so we can modify it safely */ - _gdk_macos_buffer_lock (buffer); - cairo_surface_set_user_data (image_surface, - &buffer_key, - g_object_ref (buffer), - unlock_buffer); + /* The buffer should already be locked at this point, and will + * be unlocked as part of end_frame. + */ if (!(cr = cairo_create (image_surface))) goto failure; @@ -158,6 +142,52 @@ failure: return cr; } +static void +copy_surface_data (GdkMacosBuffer *from, + GdkMacosBuffer *to, + const cairo_region_t *region, + int scale) +{ + const guint8 *from_base; + guint8 *to_base; + guint from_stride; + guint to_stride; + guint n_rects; + + g_assert (GDK_IS_MACOS_BUFFER (from)); + g_assert (GDK_IS_MACOS_BUFFER (to)); + g_assert (region != NULL); + g_assert (!cairo_region_is_empty (region)); + + from_base = _gdk_macos_buffer_get_data (from); + from_stride = _gdk_macos_buffer_get_stride (from); + + to_base = _gdk_macos_buffer_get_data (to); + to_stride = _gdk_macos_buffer_get_stride (to); + + n_rects = cairo_region_num_rectangles (region); + + for (guint i = 0; i < n_rects; i++) + { + cairo_rectangle_int_t rect; + int y2; + + cairo_region_get_rectangle (region, i, &rect); + + rect.y *= scale; + rect.height *= scale; + rect.x *= scale; + rect.width *= scale; + + y2 = rect.y + rect.height; + + for (int y = rect.y; y < y2; y++) + memcpy (&to_base[y * to_stride + rect.x * 4], + &from_base[y * from_stride + rect.x * 4], + rect.width * 4); + } +} + static void _gdk_macos_cairo_context_begin_frame (GdkDrawContext *draw_context, gboolean prefers_high_depth, @@ -165,34 +195,68 @@ _gdk_macos_cairo_context_begin_frame (GdkDrawContext *draw_context, { GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context; GdkMacosBuffer *buffer; - GdkSurface *surface; + GdkMacosSurface *surface; g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self)); [CATransaction begin]; [CATransaction setDisableActions:YES]; - surface = gdk_draw_context_get_surface (draw_context); - buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface)); + surface = GDK_MACOS_SURFACE (gdk_draw_context_get_surface (draw_context)); + buffer = _gdk_macos_surface_get_buffer (surface); _gdk_macos_buffer_set_damage (buffer, region); _gdk_macos_buffer_set_flipped (buffer, FALSE); + + _gdk_macos_buffer_lock (buffer); + + /* If there is damage that was on the previous frame that is not on + * this frame, we need to copy that rendered region over to the back + * buffer so that when swapping buffers, we still have that content. + * This is done with a read-only lock on the IOSurface to avoid + * invalidating the buffer contents. + */ + if (surface->front != NULL) + { + const cairo_region_t *previous = _gdk_macos_buffer_get_damage (surface->front); + + if (previous != NULL) + { + cairo_region_t *copy; + + copy = cairo_region_copy (previous); + cairo_region_subtract (copy, region); + + if (!cairo_region_is_empty (copy)) + { + int scale = gdk_surface_get_scale_factor (GDK_SURFACE (surface)); + + _gdk_macos_buffer_read_lock (surface->front); + copy_surface_data (surface->front, buffer, copy, scale); + _gdk_macos_buffer_read_unlock (surface->front); + } + + cairo_region_destroy (copy); + } + } } static void _gdk_macos_cairo_context_end_frame (GdkDrawContext *draw_context, cairo_region_t *painted) { + GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context; + GdkMacosSurface *surface; GdkMacosBuffer *buffer; - GdkSurface *surface; - g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (draw_context)); + g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self)); - surface = gdk_draw_context_get_surface (draw_context); - buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface)); + surface = GDK_MACOS_SURFACE (gdk_draw_context_get_surface (draw_context)); + buffer = _gdk_macos_surface_get_buffer (surface); - _gdk_macos_surface_swap_buffers (GDK_MACOS_SURFACE (surface), painted); - _gdk_macos_buffer_set_damage (buffer, NULL); + _gdk_macos_buffer_unlock (buffer); + + _gdk_macos_surface_swap_buffers (surface, painted); [CATransaction commit]; } From 25b3bd64af352fcda8aa64f59593a3279c49bce8 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 02:10:30 -0800 Subject: [PATCH 13/19] macos: fix redisplay of GdkPopup This broke recently during the configure cleanups and also needed to have the tail directions fixed again. --- gdk/macos/gdkmacospopupsurface.c | 7 ++++--- gdk/macos/gdkmacossurface.c | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gdk/macos/gdkmacospopupsurface.c b/gdk/macos/gdkmacospopupsurface.c index 477961503f..5a2d7ed481 100644 --- a/gdk/macos/gdkmacospopupsurface.c +++ b/gdk/macos/gdkmacospopupsurface.c @@ -87,6 +87,9 @@ gdk_macos_popup_surface_layout (GdkMacosPopupSurface *self, gdk_surface_get_origin (GDK_SURFACE (self)->parent, &x, &y); + GDK_SURFACE (self)->x = final_rect.x; + GDK_SURFACE (self)->y = final_rect.y; + x += final_rect.x; y += final_rect.y; @@ -391,9 +394,7 @@ _gdk_macos_popup_surface_reposition (GdkMacosPopupSurface *self) { g_return_if_fail (GDK_IS_MACOS_POPUP_SURFACE (self)); - if (self->layout == NULL || - !gdk_surface_get_mapped (GDK_SURFACE (self)) || - GDK_SURFACE (self)->parent == NULL) + if (self->layout == NULL || GDK_SURFACE (self)->parent == NULL) return; gdk_macos_popup_surface_layout (self, diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 6eb23f2807..af14681890 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -908,6 +908,7 @@ _gdk_macos_surface_show (GdkMacosSurface *self) if (!was_mapped) { gdk_surface_set_is_mapped (GDK_SURFACE (self), TRUE); + gdk_surface_request_layout (GDK_SURFACE (self)); gdk_surface_thaw_updates (GDK_SURFACE (self)); } } From 63f20b173dd1f45e4677a151d562ee33e5ad88ce Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 11:29:24 -0800 Subject: [PATCH 14/19] macos: external access to display name helpers These can be handy to print debug information when we don't have a GdkMacosMonitor to work with. --- gdk/macos/gdkmacosmonitor-private.h | 2 ++ gdk/macos/gdkmacosmonitor.c | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/gdk/macos/gdkmacosmonitor-private.h b/gdk/macos/gdkmacosmonitor-private.h index fee511d057..88f586813e 100644 --- a/gdk/macos/gdkmacosmonitor-private.h +++ b/gdk/macos/gdkmacosmonitor-private.h @@ -29,6 +29,8 @@ G_BEGIN_DECLS +char *_gdk_macos_monitor_get_localized_name (NSScreen *screen); +char *_gdk_macos_monitor_get_connector_name (CGDirectDisplayID screen_id); GdkMacosMonitor *_gdk_macos_monitor_new (GdkMacosDisplay *display, CGDirectDisplayID screen_id); CGDirectDisplayID _gdk_macos_monitor_get_screen_id (GdkMacosMonitor *self); diff --git a/gdk/macos/gdkmacosmonitor.c b/gdk/macos/gdkmacosmonitor.c index 3773ec5355..bfec76b7ef 100644 --- a/gdk/macos/gdkmacosmonitor.c +++ b/gdk/macos/gdkmacosmonitor.c @@ -160,8 +160,8 @@ GetSubpixelLayout (CGDirectDisplayID screen_id) return GDK_SUBPIXEL_LAYOUT_UNKNOWN; } -static char * -GetLocalizedName (NSScreen *screen) +char * +_gdk_macos_monitor_get_localized_name (NSScreen *screen) { #ifdef AVAILABLE_MAC_OS_X_VERSION_10_15_AND_LATER GDK_BEGIN_MACOS_ALLOC_POOL; @@ -182,8 +182,8 @@ GetLocalizedName (NSScreen *screen) #endif } -static char * -GetConnectorName (CGDirectDisplayID screen_id) +char * +_gdk_macos_monitor_get_connector_name (CGDirectDisplayID screen_id) { guint unit = CGDisplayUnitNumber (screen_id); return g_strdup_printf ("unit-%u", unit); @@ -306,8 +306,8 @@ _gdk_macos_monitor_reconfigure (GdkMacosMonitor *self) pixel_width = CGDisplayModeGetPixelWidth (mode); has_opengl = CGDisplayUsesOpenGLAcceleration (self->screen_id); subpixel_layout = GetSubpixelLayout (self->screen_id); - name = GetLocalizedName (screen); - connector = GetConnectorName (self->screen_id); + name = _gdk_macos_monitor_get_localized_name (screen); + connector = _gdk_macos_monitor_get_connector_name (self->screen_id); if (width != 0 && pixel_width != 0) scale_factor = MAX (1, pixel_width / width); From 91f5bfd21100d7ea7f87642a042360e950c581a5 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 11:20:48 -0800 Subject: [PATCH 15/19] macos: leave note about monitor discovery --- gdk/macos/gdkdisplaylinksource.c | 11 +++++++++++ gdk/macos/gdkmacosmonitor-private.h | 1 + 2 files changed, 12 insertions(+) diff --git a/gdk/macos/gdkdisplaylinksource.c b/gdk/macos/gdkdisplaylinksource.c index fe75eb3a55..6a613b40a4 100644 --- a/gdk/macos/gdkdisplaylinksource.c +++ b/gdk/macos/gdkdisplaylinksource.c @@ -26,7 +26,9 @@ #include "gdkdisplaylinksource.h" +#include "gdkdebug.h" #include "gdkmacoseventsource-private.h" +#include "gdkmacosmonitor-private.h" #include "gdk-private.h" static gint64 host_to_frame_clock_time (gint64 val); @@ -173,6 +175,7 @@ gdk_display_link_source_new (CGDirectDisplayID display_id, { GdkDisplayLinkSource *impl; GSource *source; + char *name; source = g_source_new (&gdk_display_link_source_funcs, sizeof *impl); impl = (GdkDisplayLinkSource *)source; @@ -213,6 +216,14 @@ gdk_display_link_source_new (CGDirectDisplayID display_id, impl->refresh_interval = period * 1000000L; } + name = _gdk_macos_monitor_get_connector_name (display_id); + GDK_NOTE (MISC, + g_message ("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, diff --git a/gdk/macos/gdkmacosmonitor-private.h b/gdk/macos/gdkmacosmonitor-private.h index 88f586813e..dfde4142c0 100644 --- a/gdk/macos/gdkmacosmonitor-private.h +++ b/gdk/macos/gdkmacosmonitor-private.h @@ -24,6 +24,7 @@ #include "gdkmacosdisplay.h" #include "gdkmacosmonitor.h" +#include "gdkmacossurface.h" #include "gdkmonitorprivate.h" From 92261b50225d3fb7a56648e275b881992e171d60 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 11:50:21 -0800 Subject: [PATCH 16/19] macos: add GDK_NOTE for surface sizing and placement This can be useful to debug sizing issues with the surface as well as the "window manager" placement code. --- gdk/macos/gdkmacostoplevelsurface.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/gdk/macos/gdkmacostoplevelsurface.c b/gdk/macos/gdkmacostoplevelsurface.c index c84ebb704b..d89434593b 100644 --- a/gdk/macos/gdkmacostoplevelsurface.c +++ b/gdk/macos/gdkmacostoplevelsurface.c @@ -174,6 +174,14 @@ _gdk_macos_toplevel_surface_present (GdkToplevel *toplevel, _gdk_macos_surface_set_geometry_hints (GDK_MACOS_SURFACE (self), &geometry, mask); gdk_surface_constrain_size (&geometry, mask, width, height, &width, &height); + + GDK_NOTE (MISC, + g_message ("Resizing \"%s\" to %dx%d", + GDK_MACOS_SURFACE (self)->title ? + GDK_MACOS_SURFACE (self)->title : + "untitled", + width, height)); + _gdk_macos_surface_resize (GDK_MACOS_SURFACE (self), width, height); /* Maximized state */ @@ -202,6 +210,13 @@ _gdk_macos_toplevel_surface_present (GdkToplevel *toplevel, GDK_MACOS_SURFACE (self), &x, &y); + GDK_NOTE (MISC, + g_message ("Placing new toplevel \"%s\" at %d,%d", + GDK_MACOS_SURFACE (self)->title ? + GDK_MACOS_SURFACE (self)->title : + "untitled", + x, y)); + _gdk_macos_surface_move (GDK_MACOS_SURFACE (self), x, y); } From f9faecd5b7a62937f82e4bca515e6c71bc9c2fa4 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 12:13:29 -0800 Subject: [PATCH 17/19] macos: add GDK_NOTE when surface changes monitor --- gdk/macos/gdkmacossurface.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index af14681890..4cd9994800 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -27,6 +27,7 @@ #include "gdkmacossurface-private.h" +#include "gdkdebug.h" #include "gdkdeviceprivate.h" #include "gdkdisplay.h" #include "gdkeventsprivate.h" @@ -1116,7 +1117,10 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) if (g_set_object (&self->best_monitor, best)) { - /* TODO: change frame clock to new monitor */ + GDK_NOTE (MISC, + g_message ("Surface \"%s\" moved to monitor \"%s\"", + self->title ? self->title : "unknown", + gdk_monitor_get_connector (best))); } _gdk_macos_surface_configure (self); From 66284cd24547a8adc56ca3d24558b83465bd7986 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 12:20:24 -0800 Subject: [PATCH 18/19] macos: start application in foreground We need to bring the application to the foreground in multiple ways, and this call to [NSApp activateIgnoringOtherApps:YES] ensures that we become foreground before the first window is opened. Otherwise we end up starting applications in the background. Fixes #4736 --- gdk/macos/gdkmacosdisplay.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index cb0331419d..ddcc274372 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -684,6 +684,8 @@ _gdk_macos_display_open (const char *display_name) gdk_display_emit_opened (GDK_DISPLAY (self)); + [NSApp activateIgnoringOtherApps:YES]; + return GDK_DISPLAY (self); } From 51607ce93c2d27fe1314ee54685a122a2abffac3 Mon Sep 17 00:00:00 2001 From: Christian Hergert Date: Mon, 28 Feb 2022 13:09:57 -0800 Subject: [PATCH 19/19] macos: avoid size/origin changes when possible If _gdk_macos_surface_move_resize() was called with various -1 parameters we really want to avoid changing anything even if we think we know what the value might be. Otherwise, we risk messing up in-flight operations that we have not yet been notified of yet. This improves the chances we place windows in an appropriate location as they don't et screwed up before window-manager placement. --- gdk/macos/gdkmacossurface.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index 4cd9994800..cb900a7328 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -963,13 +963,17 @@ _gdk_macos_surface_move_resize (GdkMacosSurface *self, GdkDisplay *display; NSRect content_rect; NSRect frame_rect; + gboolean ignore_move; + gboolean ignore_size; g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); - if ((x == -1 || (x == self->root_x)) && - (y == -1 || (y == self->root_y)) && - (width == -1 || (width == surface->width)) && - (height == -1 || (height == surface->height))) + ignore_move = (x == -1 || (x == self->root_x)) && + (y == -1 || (y == self->root_y)); + ignore_size = (width == -1 || (width == surface->width)) && + (height == -1 || (height == surface->height)); + + if (ignore_move && ignore_size) return; display = gdk_surface_get_display (surface); @@ -990,7 +994,14 @@ _gdk_macos_surface_move_resize (GdkMacosSurface *self, x, y + height, &x, &y); - content_rect = NSMakeRect (x, y, width, height); + content_rect = [self->window contentRectForFrameRect:[self->window frame]]; + + if (!ignore_move) + content_rect.origin = NSMakePoint (x, y); + + if (!ignore_size) + content_rect.size = NSMakeSize (width, height); + frame_rect = [self->window frameRectForContentRect:content_rect]; [self->window setFrame:frame_rect display:YES]; }