diff --git a/gdk/quartz/GdkQuartzView.c b/gdk/quartz/GdkQuartzView.c index c20c5de83a..f7ac47dcc0 100644 --- a/gdk/quartz/GdkQuartzView.c +++ b/gdk/quartz/GdkQuartzView.c @@ -24,16 +24,34 @@ #include "gdkprivate-quartz.h" #include "gdkquartz.h" #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; @@ -185,7 +203,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)); } @@ -256,6 +274,12 @@ trackingRect = 0; } + if (pixels) + { + CVPixelBufferRelease (pixels); + } + + [pb_props release]; [super dealloc]; } @@ -276,7 +300,7 @@ -(BOOL)isFlipped { - return YES; + return NO; } -(BOOL)isOpaque @@ -298,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 @@ -307,74 +331,128 @@ [super viewWillDraw]; } --(void)drawRect: (NSRect)rect +-(BOOL)wantsUpdateLayer +{ + return YES; +} + +static void +nsrect_from_cairo_rect (NSRect *nsrect, cairo_rectangle_int_t *rect) +{ + 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 { - GdkRectangle gdk_rect; GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (gdk_window->impl); - const NSRect *drawn_rects; - NSInteger count; - int i; - cairo_region_t *region; + CGRect layer_bounds = [self.layer bounds]; + CGRect backing_bounds = [self convertRectToBacking: layer_bounds]; + cairo_rectangle_int_t extents; + cairo_surface_t *cvpb_surface; if (GDK_WINDOW_DESTROYED (gdk_window)) return; - if (! (gdk_window->event_mask & GDK_EXPOSURE_MASK)) - 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)); - 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; - } - - /* Clear our own bookkeeping of regions that need display */ + cairo_rect_from_nsrect (&extents, &backing_bounds); if (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; } - - [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; + cairo_rectangle_int_t bounds; + cairo_region_t *region; - cairo_region_union_rectangle (region, &gdk_rect); + 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); } + + if (!impl || !impl->cairo_surface) + return; - impl->in_paint_rect_count++; - _gdk_window_process_updates_recurse (gdk_window, region); - impl->in_paint_rect_count--; + copy_rectangle_argb32 (cvpb_surface, impl->cairo_surface, &extents); - cairo_region_destroy (region); - - if (needsInvalidateShadow) - { - [[self window] invalidateShadow]; - needsInvalidateShadow = NO; - } + cairo_surface_destroy (cvpb_surface); + _gdk_quartz_unref_cairo_surface (gdk_window); + CVPixelBufferUnlockBaseAddress (pixels, 0); + --impl->in_paint_rect_count; + self.layer.contents = NULL; + self.layer.contents = (id)CVPixelBufferGetIOSurface (pixels); } -(void)setNeedsInvalidateShadow: (BOOL)invalidate @@ -432,9 +510,21 @@ -(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); + + //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/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/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/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: 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/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 aa85318e0b..4419975dc4 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" @@ -47,8 +48,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; @@ -149,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; @@ -160,38 +158,31 @@ 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 (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 + * 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 (![window_impl->view lockFocusIfCanDraw]) + 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; 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; } @@ -206,19 +197,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 +227,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 { @@ -298,12 +238,11 @@ 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; - gdk_quartz_window_release_context (surface_data->window_impl, - surface_data->cg_context); - g_free (surface_data); } @@ -312,22 +251,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; - - 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_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); @@ -345,21 +277,44 @@ gdk_quartz_ref_cairo_surface (GdkWindow *window) if (!impl->cairo_surface) { + 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; - impl->cairo_surface = - gdk_quartz_create_cairo_surface (impl, - gdk_window_get_width (impl->wrapper) * scale, - gdk_window_get_height (impl->wrapper) * 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); + { + 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) { @@ -369,6 +324,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; } @@ -419,13 +375,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); } } @@ -470,10 +427,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]; @@ -852,6 +811,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, @@ -864,6 +846,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; @@ -1008,6 +991,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 @@ -1063,9 +1056,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); @@ -1257,6 +1255,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; @@ -1304,10 +1303,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; @@ -1324,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 { @@ -3211,40 +3219,19 @@ 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); } +/* 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) @@ -3260,6 +3247,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 @@ -3289,3 +3292,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 1175f072e4..de9a5ebccf 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 @@ -99,6 +103,8 @@ typedef struct _GdkRootWindowImplQuartzClass GdkRootWindowImplQuartzClass; struct _GdkRootWindowImplQuartz { GdkWindowImplQuartz parent_instance; + CGContextRef cg_context; + GList* cg_layers; }; struct _GdkRootWindowImplQuartzClass diff --git a/gdk/quartz/meson.build b/gdk/quartz/meson.build index 619c0242e0..f4fcd8fba0 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', @@ -49,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,