Commit eb4d736a authored by Matthias Clasen's avatar Matthias Clasen
Browse files

Merge branch 'quartz-redraw-pixbuf' into 'gtk-3-24'

quartz: Use CALayer to back GdkQuartzView

See merge request GNOME/gtk!4728
parents 4e3a3f05 b94ed34f
......@@ -24,16 +24,34 @@
#include "gdkprivate-quartz.h"
#include "gdkquartz.h"
#include "gdkinternal-quartz.h"
#include <cairo/cairo-quartz.h>
#import <AppKit/AppKit.h>
#import <IOSurface/IOSurface.h>
@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
{
GdkRectangle gdk_rect;
GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (gdk_window->impl);
const NSRect *drawn_rects;
NSInteger count;
int i;
cairo_region_t *region;
return YES;
}
if (GDK_WINDOW_DESTROYED (gdk_window))
return;
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;
}
if (! (gdk_window->event_mask & GDK_EXPOSURE_MASK))
return;
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;
}
if (NSEqualRects (rect, NSZeroRect))
return;
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;
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];
source_img = cairo_surface_map_to_image (source, rect);
status = cairo_surface_status (source_img);
[[NSColor windowBackgroundColor] setFill];
[NSBezierPath fillRect: rect];
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;
}
[NSGraphicsContext restoreGraphicsState];
format = cairo_image_surface_get_format (source_img);
dest_img = cairo_surface_map_to_image (dest, rect);
status = cairo_surface_status (dest_img);
return;
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;
}
/* Clear our own bookkeeping of regions that need display */
width = cairo_image_surface_get_width (source_img);
stride = cairo_format_stride_for_width (format, width);
height = cairo_image_surface_get_height (source_img);
memcpy (cairo_image_surface_get_data (dest_img),
cairo_image_surface_get_data (source_img),
stride * height);
cairo_surface_unmap_image (dest, dest_img);
CLEANUP:
cairo_surface_unmap_image (source, source_img);
return status;
}
-(void)updateLayer
{
GdkWindowImplQuartz *impl = GDK_WINDOW_IMPL_QUARTZ (gdk_window->impl);
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;
++impl->in_paint_rect_count;
CVPixelBufferLockBaseAddress (pixels, 0);
cvpb_surface =
cairo_image_surface_create_for_data (CVPixelBufferGetBaseAddress (pixels),
CAIRO_FORMAT_ARGB32,
(int)CVPixelBufferGetWidth (pixels),
(int)CVPixelBufferGetHeight (pixels),
(int)CVPixelBufferGetBytesPerRow (pixels));
cairo_rect_from_nsrect (&extents, &backing_bounds);
if (impl->needs_display_region)
{
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--;
cairo_region_destroy (region);
copy_rectangle_argb32 (cvpb_surface, impl->cairo_surface, &extents);
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])
......
......@@ -17,6 +17,7 @@
*/
#import <AppKit/AppKit.h>
#import <CoreVideo/CoreVideo.h>
#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;
......
......@@ -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 \
......
......@@ -21,6 +21,7 @@
#include <gdk/gdk.h>
#include <gdk/gdkdisplayprivate.h>
#include <gdk/gdkmonitorprivate.h>
#include <gdk/gdkframeclockprivate.h>
#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 ();
......
......@@ -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
......
/* gdkdisplaylinksource.c
*
* Copyright (C) 2015 Christian Hergert <christian@hergert.me>
*
* 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 <http://www.gnu.org/licenses/>.
*
* Authors:
* Christian Hergert <christian@hergert.me>
*/
#include "config.h"
#include <mach/mach_time.h>
#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