diff --git a/gdk/gdkglcontext.c b/gdk/gdkglcontext.c index 373f2fefd5..be514c0ac6 100644 --- a/gdk/gdkglcontext.c +++ b/gdk/gdkglcontext.c @@ -648,6 +648,12 @@ gdk_gl_context_surface_resized (GdkDrawContext *draw_context) gdk_gl_context_clear_old_updated_area (context); } +static guint +gdk_gl_context_real_get_default_framebuffer (GdkGLContext *self) +{ + return 0; +} + static void gdk_gl_context_class_init (GdkGLContextClass *klass) { @@ -659,6 +665,7 @@ gdk_gl_context_class_init (GdkGLContextClass *klass) klass->is_shared = gdk_gl_context_real_is_shared; klass->make_current = gdk_gl_context_real_make_current; klass->clear_current = gdk_gl_context_real_clear_current; + klass->get_default_framebuffer = gdk_gl_context_real_get_default_framebuffer; draw_context_class->begin_frame = gdk_gl_context_real_begin_frame; draw_context_class->end_frame = gdk_gl_context_real_end_frame; diff --git a/gdk/gdkglcontextprivate.h b/gdk/gdkglcontextprivate.h index ff11349b3e..0a3739f090 100644 --- a/gdk/gdkglcontextprivate.h +++ b/gdk/gdkglcontextprivate.h @@ -71,6 +71,8 @@ struct _GdkGLContextClass gboolean (* is_shared) (GdkGLContext *self, GdkGLContext *other); + + guint (* get_default_framebuffer) (GdkGLContext *self); }; typedef struct { diff --git a/gdk/macos/GdkMacosCairoSubview.c b/gdk/macos/GdkMacosCairoSubview.c deleted file mode 100644 index fddb0ed0c0..0000000000 --- a/gdk/macos/GdkMacosCairoSubview.c +++ /dev/null @@ -1,167 +0,0 @@ -/* GdkMacosCairoSubview.c - * - * Copyright © 2020 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 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 - -#import "GdkMacosCairoSubview.h" -#import "GdkMacosCairoView.h" - -#include "gdkmacossurface-private.h" - -@implementation GdkMacosCairoSubview - --(void)dealloc -{ - g_clear_pointer (&self->clip, g_array_unref); - g_clear_pointer (&self->damage, g_array_unref); - g_clear_pointer (&self->image, CGImageRelease); - [super dealloc]; -} - --(BOOL)isOpaque -{ - return _isOpaque; -} - --(BOOL)isFlipped -{ - return YES; -} - --(GdkSurface *)gdkSurface -{ - return GDK_SURFACE ([(GdkMacosBaseView *)[self superview] gdkSurface]); -} - --(void)drawRect:(NSRect)rect -{ - CGContextRef cgContext; - NSView *root_view; - CGRect image_rect; - CGRect abs_bounds; - CGSize scale; - int abs_height; - - if (self->image == NULL) - return; - - cgContext = [[NSGraphicsContext currentContext] CGContext]; - root_view = [[self window] contentView]; - abs_bounds = [self convertRect:[self bounds] toView:root_view]; - abs_height = CGImageGetHeight (self->image); - - CGContextSaveGState (cgContext); - - /* Clip while our context is still using matching coordinates - * to the self->clip region. This is usually just on the views - * for the shadow areas. - */ - CGContextAddRect (cgContext, [self bounds]); - if (self->clip != NULL) - { - for (guint i = 0; i < self->clip->len; i++) - CGContextAddRect (cgContext, g_array_index (self->clip, CGRect, i)); - } - if (self->damage != NULL) - { - for (guint i = 0; i < self->damage->len; i++) - CGContextAddRect (cgContext, g_array_index (self->damage, CGRect, i)); - } - CGContextClip (cgContext); - - /* Scale/Translate so that the CGImageRef draws in proper format/placement */ - scale = CGSizeMake (1.0, 1.0); - scale = CGContextConvertSizeToDeviceSpace (cgContext, scale); - CGContextScaleCTM (cgContext, 1.0 / scale.width, 1.0 / scale.height); - CGContextTranslateCTM (cgContext, -abs_bounds.origin.x, -abs_bounds.origin.y); - image_rect = CGRectMake (-abs_bounds.origin.x, - -abs_bounds.origin.y, - CGImageGetWidth (self->image), - CGImageGetHeight (self->image)); - CGContextDrawImage (cgContext, image_rect, self->image); - - CGContextRestoreGState (cgContext); -} - --(void)setImage:(CGImageRef)theImage - withDamage:(cairo_region_t *)region -{ - if (theImage != image) - { - g_clear_pointer (&image, CGImageRelease); - if (theImage) - image = CGImageRetain (theImage); - } - - [self convertRegion:region toArray:&self->damage andDisplay:YES]; - - for (id view in [self subviews]) - [(GdkMacosCairoSubview *)view setImage:theImage withDamage:region]; -} - --(void)setOpaque:(BOOL)opaque -{ - self->_isOpaque = opaque; -} - --(void)convertRegion:(const cairo_region_t *)region - toArray:(GArray **)array - andDisplay:(BOOL)display -{ - NSView *root_view; - CGRect abs_bounds; - guint n_rects; - - if (*array == NULL) - *array = g_array_new (FALSE, FALSE, sizeof (CGRect)); - else - g_array_set_size (*array, 0); - - root_view = [[self window] contentView]; - abs_bounds = [self convertRect:[self bounds] toView:root_view]; - n_rects = cairo_region_num_rectangles (region); - - for (guint i = 0; i < n_rects; i++) - { - cairo_rectangle_int_t rect; - CGRect nsrect; - - cairo_region_get_rectangle (region, i, &rect); - nsrect = CGRectIntersection (abs_bounds, CGRectMake (rect.x, rect.y, rect.width, rect.height)); - - if (!CGRectIsNull (nsrect)) - g_array_append_val (*array, nsrect); - - if (display) - [self setNeedsDisplayInRect:CGRectMake (rect.x - abs_bounds.origin.x, - rect.y - abs_bounds.origin.y, - rect.width, rect.height)]; - } -} - --(void)setClip:(cairo_region_t*)region -{ - [self convertRegion:region toArray:&self->clip andDisplay:NO]; -} - -@end diff --git a/gdk/macos/GdkMacosCairoView.c b/gdk/macos/GdkMacosCairoView.c deleted file mode 100644 index 6596702fe8..0000000000 --- a/gdk/macos/GdkMacosCairoView.c +++ /dev/null @@ -1,267 +0,0 @@ -/* GdkMacosCairoView.c - * - * Copyright © 2020 Red Hat, Inc. - * Copyright © 2005-2007 Imendio AB - * - * 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 . - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -#include "config.h" - -#include -#include - - -#import "GdkMacosCairoView.h" -#import "GdkMacosCairoSubview.h" - -#include "gdkmacosmonitor-private.h" -#include "gdkmacossurface-private.h" - -@implementation GdkMacosCairoView - --(void)dealloc -{ - g_clear_pointer (&self->opaque, g_ptr_array_unref); - self->transparent = NULL; - - [super dealloc]; -} - --(BOOL)isOpaque -{ - if ([self window]) - return [[self window] isOpaque]; - return YES; -} - --(BOOL)isFlipped -{ - return YES; -} - --(void)setNeedsDisplay:(BOOL)needsDisplay -{ - for (id child in [self subviews]) - [child setNeedsDisplay:needsDisplay]; -} - -static void -release_surface_provider (void *info, - const void *data, - size_t size) -{ - cairo_surface_destroy (info); -} - --(void)setCairoSurface:(cairo_surface_t *)cairoSurface - withDamage:(cairo_region_t *)cairoRegion -{ - CGImageRef image = NULL; - - if (cairoSurface != NULL) - { - const CGColorRenderingIntent intent = kCGRenderingIntentDefault; - CGDataProviderRef provider; - CGColorSpaceRef rgb; - cairo_format_t format; - CGBitmapInfo bitmap = kCGBitmapByteOrder32Host; - GdkMonitor *monitor; - guint8 *framebuffer; - size_t width; - size_t height; - int rowstride; - int bpp; - int bpc; - - cairo_surface_flush (cairoSurface); - - format = cairo_image_surface_get_format (cairoSurface); - framebuffer = cairo_image_surface_get_data (cairoSurface); - rowstride = cairo_image_surface_get_stride (cairoSurface); - width = cairo_image_surface_get_width (cairoSurface); - height = cairo_image_surface_get_height (cairoSurface); - monitor = _gdk_macos_surface_get_best_monitor ([self gdkSurface]); - rgb = _gdk_macos_monitor_copy_colorspace (GDK_MACOS_MONITOR (monitor)); - - /* If we have an WCG colorspace, just take the slow path or we risk - * really screwing things up. - */ - if (CGColorSpaceIsWideGamutRGB (rgb)) - { - CGColorSpaceRelease (rgb); - rgb = CGColorSpaceCreateDeviceRGB (); - } - - /* Assert that our image surface was created correctly with - * 16-byte aligned pointers and strides. This is needed to - * ensure that we're working with fast paths in CoreGraphics. - */ - g_assert (format == CAIRO_FORMAT_ARGB32 || format == CAIRO_FORMAT_RGB24); - g_assert (framebuffer != NULL); - g_assert (((intptr_t)framebuffer & (intptr_t)~0xF) == (intptr_t)framebuffer); - g_assert ((rowstride & ~0xF) == rowstride); - - if (format == CAIRO_FORMAT_ARGB32) - { - bitmap |= kCGImageAlphaPremultipliedFirst; - bpp = 32; - bpc = 8; - } - else - { - bitmap |= kCGImageAlphaNoneSkipFirst; - bpp = 32; - bpc = 8; - } - - provider = CGDataProviderCreateWithData (cairo_surface_reference (cairoSurface), - framebuffer, - rowstride * height, - release_surface_provider); - - image = CGImageCreate (width, height, bpc, bpp, rowstride, rgb, bitmap, provider, NULL, FALSE, intent); - - CGDataProviderRelease (provider); - CGColorSpaceRelease (rgb); - } - - for (id view in [self subviews]) - [(GdkMacosCairoSubview *)view setImage:image - withDamage:cairoRegion]; - - if (image != NULL) - CGImageRelease (image); -} - --(void)removeOpaqueChildren -{ - [[self->transparent subviews] - makeObjectsPerformSelector:@selector(removeFromSuperview)]; - - if (self->opaque->len) - g_ptr_array_remove_range (self->opaque, 0, self->opaque->len); -} - --(void)setOpaqueRegion:(cairo_region_t *)region -{ - cairo_region_t *transparent_clip; - NSRect abs_bounds; - guint n_rects; - - if (region == NULL) - return; - - abs_bounds = [self convertRect:[self bounds] toView:nil]; - n_rects = cairo_region_num_rectangles (region); - - /* First, we create a clip region for the transparent region to use so that - * we dont end up exposing too much other than the corners on CSD. - */ - transparent_clip = cairo_region_create_rectangle (&(cairo_rectangle_int_t) { - abs_bounds.origin.x, abs_bounds.origin.y, - abs_bounds.size.width, abs_bounds.size.height - }); - cairo_region_subtract (transparent_clip, region); - [(GdkMacosCairoSubview *)self->transparent setClip:transparent_clip]; - cairo_region_destroy (transparent_clip); - - /* The common case (at least for opaque windows and CSD) is that we will - * have either one or two opaque rectangles. If we detect that the same - * number of them are available as the previous, we can just resize the - * previous ones to avoid adding/removing views at a fast rate while - * resizing. - */ - if (n_rects == self->opaque->len) - { - for (guint i = 0; i < n_rects; i++) - { - GdkMacosCairoSubview *child; - cairo_rectangle_int_t rect; - - child = g_ptr_array_index (self->opaque, i); - cairo_region_get_rectangle (region, i, &rect); - - [child setFrame:NSMakeRect (rect.x - abs_bounds.origin.x, - rect.y - abs_bounds.origin.y, - rect.width, - rect.height)]; - } - - return; - } - - [self removeOpaqueChildren]; - for (guint i = 0; i < n_rects; i++) - { - GdkMacosCairoSubview *child; - cairo_rectangle_int_t rect; - NSRect nsrect; - - cairo_region_get_rectangle (region, i, &rect); - nsrect = NSMakeRect (rect.x - abs_bounds.origin.x, - rect.y - abs_bounds.origin.y, - rect.width, - rect.height); - - child = [[GdkMacosCairoSubview alloc] initWithFrame:nsrect]; - [child setOpaque:YES]; - [child setWantsLayer:YES]; - [self->transparent addSubview:child]; - g_ptr_array_add (self->opaque, child); - } -} - --(NSView *)initWithFrame:(NSRect)frame -{ - if ((self = [super initWithFrame:frame])) - { - /* An array to track all the opaque children placed into - * the child self->transparent. This allows us to reuse them - * when we receive a new opaque area instead of discarding - * them on each draw. - */ - self->opaque = g_ptr_array_new (); - - /* Setup our primary subview which will render all content that is not - * within an opaque region (such as shadows for CSD windows). For opaque - * windows, this will all be obscurred by other views, so it doesn't - * matter much to have it here. - */ - self->transparent = [[GdkMacosCairoSubview alloc] initWithFrame:frame]; - [self addSubview:self->transparent]; - } - - return self; -} - --(void)setFrame:(NSRect)rect -{ - [super setFrame:rect]; - [self->transparent setFrame:NSMakeRect (0, 0, rect.size.width, rect.size.height)]; -} - --(BOOL)acceptsFirstMouse -{ - return YES; -} - --(BOOL)mouseDownCanMoveWindow -{ - return NO; -} - -@end diff --git a/gdk/macos/GdkMacosGLView.c b/gdk/macos/GdkMacosGLView.c deleted file mode 100644 index d7f6437451..0000000000 --- a/gdk/macos/GdkMacosGLView.c +++ /dev/null @@ -1,125 +0,0 @@ -/* GdkMacosGLView.c - * - * Copyright 2020 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 . - * - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -#include "config.h" - -#include -#include - -#include "gdkmacossurface-private.h" - -#import "GdkMacosGLView.h" - -@implementation GdkMacosGLView - -G_GNUC_BEGIN_IGNORE_DEPRECATIONS - --(void)lockFocus -{ - NSOpenGLContext *context; - - [super lockFocus]; - - context = [self openGLContext]; - - if ([context view] != self) - [context setView: self]; -} - --(void)drawRect:(NSRect)rect -{ -} - --(void)clearGLContext -{ - if (_openGLContext != nil) - [_openGLContext clearDrawable]; - - _openGLContext = nil; -} - --(void)setOpenGLContext:(NSOpenGLContext*)context -{ - if (_openGLContext != context) - { - if (_openGLContext != nil) - [_openGLContext clearDrawable]; - - _openGLContext = context; - - if (_openGLContext != nil) - { - [_openGLContext setView:self]; - [self setWantsLayer:YES]; - [self.layer setContentsGravity:kCAGravityBottomLeft]; - [_openGLContext update]; - } - } -} - --(NSOpenGLContext *)openGLContext -{ - return _openGLContext; -} - --(BOOL)isOpaque -{ - if ([self window]) - return [[self window] isOpaque]; - return YES; -} - --(BOOL)isFlipped -{ - return YES; -} - --(BOOL)acceptsFirstMouse -{ - return YES; -} - --(BOOL)mouseDownCanMoveWindow -{ - return NO; -} - --(void)invalidateRegion:(const cairo_region_t *)region -{ - if (region != NULL) - { - guint n_rects = cairo_region_num_rectangles (region); - - for (guint i = 0; i < n_rects; i++) - { - cairo_rectangle_int_t rect; - NSRect nsrect; - - cairo_region_get_rectangle (region, i, &rect); - nsrect = NSMakeRect (rect.x, rect.y, rect.width, rect.height); - - [self setNeedsDisplayInRect:nsrect]; - } - } -} - -G_GNUC_END_IGNORE_DEPRECATIONS - -@end diff --git a/gdk/macos/GdkMacosLayer.c b/gdk/macos/GdkMacosLayer.c new file mode 100644 index 0000000000..4de47a5658 --- /dev/null +++ b/gdk/macos/GdkMacosLayer.c @@ -0,0 +1,386 @@ +/* GdkMacosLayer.c + * + * 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 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" + +#import "GdkMacosLayer.h" +#import "GdkMacosTile.h" + +@protocol CanSetContentsOpaque +- (void)setContentsOpaque:(BOOL)mask; +@end + +@implementation GdkMacosLayer + +#define TILE_MAX_SIZE 128 +#define TILE_EDGE_MAX_SIZE 512 + +static CGAffineTransform flipTransform; +static gboolean hasFlipTransform; + +typedef struct +{ + GdkMacosTile *tile; + cairo_rectangle_int_t cr_area; + CGRect area; + guint opaque : 1; +} TileInfo; + +typedef struct +{ + const cairo_region_t *region; + guint n_rects; + guint iter; + cairo_rectangle_int_t rect; + cairo_rectangle_int_t stash; + guint finished : 1; +} Tiler; + +static void +tiler_init (Tiler *tiler, + const cairo_region_t *region) +{ + memset (tiler, 0, sizeof *tiler); + + if (region == NULL) + { + tiler->finished = TRUE; + return; + } + + tiler->region = region; + tiler->n_rects = cairo_region_num_rectangles (region); + + if (tiler->n_rects > 0) + cairo_region_get_rectangle (region, 0, &tiler->rect); + else + tiler->finished = TRUE; +} + +static gboolean +tiler_next (Tiler *tiler, + cairo_rectangle_int_t *tile, + int max_size) +{ + if (tiler->finished) + return FALSE; + + if (tiler->rect.width == 0 || tiler->rect.height == 0) + { + tiler->iter++; + + if (tiler->iter >= tiler->n_rects) + { + tiler->finished = TRUE; + return FALSE; + } + + cairo_region_get_rectangle (tiler->region, tiler->iter, &tiler->rect); + } + + /* If the next rectangle is too tall, slice the bottom off to + * leave just the height we want into tiler->stash. + */ + if (tiler->rect.height > max_size) + { + tiler->stash = tiler->rect; + tiler->stash.y += max_size; + tiler->stash.height -= max_size; + tiler->rect.height = max_size; + } + + /* Now we can take the next horizontal slice */ + tile->x = tiler->rect.x; + tile->y = tiler->rect.y; + tile->height = tiler->rect.height; + tile->width = MIN (max_size, tiler->rect.width); + + tiler->rect.x += tile->width; + tiler->rect.width -= tile->width; + + if (tiler->rect.width == 0) + { + tiler->rect = tiler->stash; + tiler->stash.width = tiler->stash.height = 0; + } + + return TRUE; +} + +static inline CGRect +toCGRect (const cairo_rectangle_int_t *rect) +{ + return CGRectMake (rect->x, rect->y, rect->width, rect->height); +} + +static inline cairo_rectangle_int_t +fromCGRect (const CGRect rect) +{ + return (cairo_rectangle_int_t) { + rect.origin.x, + rect.origin.y, + rect.size.width, + rect.size.height + }; +} + +-(id)init +{ + if (!hasFlipTransform) + { + hasFlipTransform = TRUE; + flipTransform = CGAffineTransformMakeScale (1, -1); + } + + self = [super init]; + + if (self == NULL) + return NULL; + + self->_layoutInvalid = TRUE; + + [self setContentsGravity:kCAGravityCenter]; + [self setContentsScale:1.0f]; + [self setGeometryFlipped:YES]; + + return self; +} + +-(BOOL)isOpaque +{ + return NO; +} + +-(void)_applyLayout:(GArray *)tiles +{ + CGAffineTransform transform; + GArray *prev; + gboolean exhausted; + guint j = 0; + + if (self->_isFlipped) + transform = flipTransform; + else + transform = CGAffineTransformIdentity; + + prev = g_steal_pointer (&self->_tiles); + self->_tiles = tiles; + exhausted = prev == NULL; + + /* Try to use existing CALayer to avoid creating new layers + * as that can be rather expensive. + */ + for (guint i = 0; i < tiles->len; i++) + { + TileInfo *info = &g_array_index (tiles, TileInfo, i); + + if (!exhausted) + { + TileInfo *other = NULL; + + for (; j < prev->len; j++) + { + other = &g_array_index (prev, TileInfo, j); + + if (other->opaque == info->opaque) + { + j++; + break; + } + + other = NULL; + } + + if (other != NULL) + { + info->tile = g_steal_pointer (&other->tile); + [info->tile setFrame:info->area]; + [info->tile setAffineTransform:transform]; + continue; + } + } + + info->tile = [GdkMacosTile layer]; + + [info->tile setAffineTransform:transform]; + [info->tile setContentsScale:1.0f]; + [info->tile setOpaque:info->opaque]; + [(id)info->tile setContentsOpaque:info->opaque]; + [info->tile setFrame:info->area]; + + [self addSublayer:info->tile]; + } + + /* Release all of our old layers */ + if (prev != NULL) + { + for (guint i = 0; i < prev->len; i++) + { + TileInfo *info = &g_array_index (prev, TileInfo, i); + + if (info->tile != NULL) + [info->tile removeFromSuperlayer]; + } + + g_array_unref (prev); + } +} + +-(void)layoutSublayers +{ + Tiler tiler; + GArray *ar; + cairo_region_t *transparent; + cairo_rectangle_int_t rect; + int max_size; + + if (!self->_inSwapBuffer) + return; + + self->_layoutInvalid = FALSE; + + ar = g_array_sized_new (FALSE, FALSE, sizeof (TileInfo), 32); + + rect = fromCGRect ([self bounds]); + rect.x = rect.y = 0; + + /* Calculate the transparent region (edges usually) */ + transparent = cairo_region_create_rectangle (&rect); + if (self->_opaqueRegion) + cairo_region_subtract (transparent, self->_opaqueRegion); + + self->_opaque = cairo_region_is_empty (transparent); + + /* If we have transparent borders around the opaque region, then + * we are okay with a bit larger tiles since they don't change + * all that much and are generally small in width. + */ + if (!self->_opaque && + self->_opaqueRegion && + !cairo_region_is_empty (self->_opaqueRegion)) + max_size = TILE_EDGE_MAX_SIZE; + else + max_size = TILE_MAX_SIZE; + + /* Track transparent children */ + tiler_init (&tiler, transparent); + while (tiler_next (&tiler, &rect, max_size)) + { + TileInfo *info; + + g_array_set_size (ar, ar->len+1); + + info = &g_array_index (ar, TileInfo, ar->len-1); + info->tile = NULL; + info->opaque = FALSE; + info->cr_area = rect; + info->area = toCGRect (&info->cr_area); + } + + /* Track opaque children */ + tiler_init (&tiler, self->_opaqueRegion); + while (tiler_next (&tiler, &rect, TILE_MAX_SIZE)) + { + TileInfo *info; + + g_array_set_size (ar, ar->len+1); + + info = &g_array_index (ar, TileInfo, ar->len-1); + info->tile = NULL; + info->opaque = TRUE; + info->cr_area = rect; + info->area = toCGRect (&info->cr_area); + } + + cairo_region_destroy (transparent); + + [self _applyLayout:g_steal_pointer (&ar)]; + [super layoutSublayers]; +} + +-(void)setFrame:(NSRect)frame +{ + if (frame.size.width != self.bounds.size.width || + frame.size.height != self.bounds.size.height) + { + self->_layoutInvalid = TRUE; + [self setNeedsLayout]; + } + + [super setFrame:frame]; +} + +-(void)setOpaqueRegion:(const cairo_region_t *)opaqueRegion +{ + g_clear_pointer (&self->_opaqueRegion, cairo_region_destroy); + self->_opaqueRegion = cairo_region_copy (opaqueRegion); + self->_layoutInvalid = TRUE; + + [self setNeedsLayout]; +} + +-(void)swapBuffer:(GdkMacosBuffer *)buffer withDamage:(const cairo_region_t *)damage +{ + IOSurfaceRef ioSurface = _gdk_macos_buffer_get_native (buffer); + gboolean flipped = _gdk_macos_buffer_get_flipped (buffer); + double scale = _gdk_macos_buffer_get_device_scale (buffer); + double width = _gdk_macos_buffer_get_width (buffer) / scale; + double height = _gdk_macos_buffer_get_height (buffer) / scale; + + if (flipped != self->_isFlipped) + { + self->_isFlipped = flipped; + self->_layoutInvalid = TRUE; + } + + if (self->_layoutInvalid) + { + self->_inSwapBuffer = TRUE; + [self layoutSublayers]; + self->_inSwapBuffer = FALSE; + } + + if (self->_tiles == NULL) + return; + + for (guint i = 0; i < self->_tiles->len; i++) + { + const TileInfo *info = &g_array_index (self->_tiles, TileInfo, i); + cairo_region_overlap_t overlap; + CGRect area; + + overlap = cairo_region_contains_rectangle (damage, &info->cr_area); + if (overlap == CAIRO_REGION_OVERLAP_OUT) + continue; + + area.origin.x = info->area.origin.x / width; + area.size.width = info->area.size.width / width; + area.size.height = info->area.size.height / height; + + if (flipped) + area.origin.y = (height - info->area.origin.y - info->area.size.height) / height; + else + area.origin.y = info->area.origin.y / height; + + [info->tile swapBuffer:ioSurface withRect:area]; + } +} + +@end diff --git a/gdk/macos/GdkMacosLayer.h b/gdk/macos/GdkMacosLayer.h new file mode 100644 index 0000000000..74ba14ff86 --- /dev/null +++ b/gdk/macos/GdkMacosLayer.h @@ -0,0 +1,44 @@ +/* GdkMacosLayer.h + * + * 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 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 +#include + +#include +#include + +#include "gdkmacosbuffer-private.h" + +#define GDK_IS_MACOS_LAYER(obj) ((obj) && [obj isKindOfClass:[GdkMacosLayer class]]) + +@interface GdkMacosLayer : CALayer +{ + cairo_region_t *_opaqueRegion; + GArray *_tiles; + guint _opaque : 1; + guint _layoutInvalid : 1; + guint _inSwapBuffer : 1; + guint _isFlipped : 1; +}; + +-(void)setOpaqueRegion:(const cairo_region_t *)opaqueRegion; +-(void)swapBuffer:(GdkMacosBuffer *)buffer withDamage:(const cairo_region_t *)damage; + +@end diff --git a/gdk/macos/GdkMacosGLView.h b/gdk/macos/GdkMacosTile.c similarity index 54% rename from gdk/macos/GdkMacosGLView.h rename to gdk/macos/GdkMacosTile.c index 320b1a163b..737c719e0d 100644 --- a/gdk/macos/GdkMacosGLView.h +++ b/gdk/macos/GdkMacosTile.c @@ -1,7 +1,6 @@ -/* GdkMacosGLView.h +/* GdkMacosTile.c * - * Copyright © 2020 Red Hat, Inc. - * Copyright © 2005-2007 Imendio AB + * 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 @@ -19,23 +18,34 @@ * SPDX-License-Identifier: LGPL-2.1-or-later */ -#include +#include "config.h" -#import "GdkMacosBaseView.h" +#include -#define GDK_IS_MACOS_GL_VIEW(obj) ((obj) && [obj isKindOfClass:[GdkMacosGLView class]]) +#import "GdkMacosTile.h" -G_GNUC_BEGIN_IGNORE_DEPRECATIONS +@implementation GdkMacosTile -@interface GdkMacosGLView : GdkMacosBaseView +-(id)init { - NSOpenGLContext *_openGLContext; + self = [super init]; + + [self setContentsScale:1.0]; + [self setEdgeAntialiasingMask:0]; + [self setDrawsAsynchronously:YES]; + + return self; } --(void)setOpenGLContext:(NSOpenGLContext*)context; --(NSOpenGLContext *)openGLContext; --(void)invalidateRegion:(const cairo_region_t *)region; +-(void)swapBuffer:(IOSurfaceRef)buffer withRect:(CGRect)rect +{ + if G_LIKELY ([self contents] == (id)buffer) + [(id)self setContentsChanged]; + else + [self setContents:(id)buffer]; -G_GNUC_END_IGNORE_DEPRECATIONS + if G_UNLIKELY (!CGRectEqualToRect ([self contentsRect], rect)) + self.contentsRect = rect; +} @end diff --git a/gdk/macos/GdkMacosCairoSubview.h b/gdk/macos/GdkMacosTile.h similarity index 59% rename from gdk/macos/GdkMacosCairoSubview.h rename to gdk/macos/GdkMacosTile.h index e03b47727d..ada07ebcf3 100644 --- a/gdk/macos/GdkMacosCairoSubview.h +++ b/gdk/macos/GdkMacosTile.h @@ -1,6 +1,6 @@ -/* GdkMacosCairoSubview.h +/* GdkMacosTile.h * - * Copyright © 2020 Red Hat, Inc. + * 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 @@ -18,22 +18,20 @@ * SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include -#include +#include -#define GDK_IS_MACOS_CAIRO_SUBVIEW(obj) ((obj) && [obj isKindOfClass:[GdkMacosCairoSubview class]]) +#include "gdkmacosbuffer-private.h" -@interface GdkMacosCairoSubview : NSView +#define GDK_IS_MACOS_TILE(obj) ((obj) && [obj isKindOfClass:[GdkMacosTile class]]) + +@protocol CanSetContentsChanged +-(void)setContentsChanged; +@end + +@interface GdkMacosTile : CALayer { - BOOL _isOpaque; - GArray *clip; - GArray *damage; - CGImageRef image; -} +}; --(void)setOpaque:(BOOL)opaque; --(void)setImage:(CGImageRef)theImage withDamage:(cairo_region_t *)region; --(void)setClip:(cairo_region_t*)region; +-(void)swapBuffer:(IOSurfaceRef)buffer withRect:(CGRect)rect; @end diff --git a/gdk/macos/GdkMacosView.c b/gdk/macos/GdkMacosView.c new file mode 100644 index 0000000000..52a55a7cef --- /dev/null +++ b/gdk/macos/GdkMacosView.c @@ -0,0 +1,86 @@ +/* GdkMacosView.c + * + * Copyright 2022 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 . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include + +#import "GdkMacosLayer.h" +#import "GdkMacosView.h" + +@implementation GdkMacosView + +-(id)initWithFrame:(NSRect)frame +{ + if ((self = [super initWithFrame:frame])) + { + GdkMacosLayer *layer = [GdkMacosLayer layer]; + + [self setLayerContentsRedrawPolicy:NSViewLayerContentsRedrawNever]; + [self setLayer:layer]; + [self setWantsLayer:YES]; + } + + return self; +} + +-(BOOL)isFlipped +{ + return YES; +} + +-(BOOL)acceptsFirstMouse +{ + return YES; +} + +-(BOOL)mouseDownCanMoveWindow +{ + return NO; +} + +-(void)setFrame:(NSRect)rect +{ + [super setFrame:rect]; + self->_nextFrameDirty = TRUE; +} + +-(void)setOpaqueRegion:(const cairo_region_t *)opaqueRegion +{ + [(GdkMacosLayer *)[self layer] setOpaqueRegion:opaqueRegion]; +} + +-(BOOL)wantsUpdateLayer +{ + return YES; +} + +-(void)swapBuffer:(GdkMacosBuffer *)buffer withDamage:(const cairo_region_t *)damage +{ + if (self->_nextFrameDirty) + { + self->_nextFrameDirty = FALSE; + [[self layer] setFrame:[self frame]]; + } + + [(GdkMacosLayer *)[self layer] swapBuffer:buffer withDamage:damage]; +} + +@end diff --git a/gdk/macos/GdkMacosCairoView.h b/gdk/macos/GdkMacosView.h similarity index 64% rename from gdk/macos/GdkMacosCairoView.h rename to gdk/macos/GdkMacosView.h index 1c28d83b39..db3b05efa0 100644 --- a/gdk/macos/GdkMacosCairoView.h +++ b/gdk/macos/GdkMacosView.h @@ -1,7 +1,6 @@ -/* GdkMacosCairoView.h +/* GdkMacosView.h * - * Copyright © 2020 Red Hat, Inc. - * Copyright © 2005-2007 Imendio AB + * Copyright 2022 Christian Hergert * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -23,15 +22,17 @@ #import "GdkMacosBaseView.h" -#define GDK_IS_MACOS_CAIRO_VIEW(obj) ((obj) && [obj isKindOfClass:[GdkMacosCairoView class]]) +#include "gdkmacosbuffer-private.h" -@interface GdkMacosCairoView : GdkMacosBaseView +#define GDK_IS_MACOS_VIEW(obj) ((obj) && [obj isKindOfClass:[GdkMacosView class]]) + +@interface GdkMacosView : GdkMacosBaseView { - NSView *transparent; - GPtrArray *opaque; + NSRect _nextFrame; + guint _nextFrameDirty : 1; } --(void)setCairoSurface:(cairo_surface_t *)cairoSurface - withDamage:(cairo_region_t *)region; +-(void)setOpaqueRegion:(const cairo_region_t *)opaqueRegion; +-(void)swapBuffer:(GdkMacosBuffer *)buffer withDamage:(const cairo_region_t *)damage; @end diff --git a/gdk/macos/GdkMacosWindow.c b/gdk/macos/GdkMacosWindow.c index 3f45bc8d4c..f879decbc9 100644 --- a/gdk/macos/GdkMacosWindow.c +++ b/gdk/macos/GdkMacosWindow.c @@ -24,8 +24,7 @@ #include #import "GdkMacosBaseView.h" -#import "GdkMacosCairoView.h" -#import "GdkMacosGLView.h" +#import "GdkMacosView.h" #import "GdkMacosWindow.h" #include "gdkmacosclipboard-private.h" @@ -150,8 +149,7 @@ typedef NSString *CALayerContentsGravity; _gdk_macos_display_break_all_grabs (GDK_MACOS_DISPLAY (display), time); /* Reset gravity */ - if (GDK_IS_MACOS_GL_VIEW ([self contentView])) - [[[self contentView] layer] setContentsGravity:kCAGravityBottomLeft]; + [[[self contentView] layer] setContentsGravity:kCAGravityBottomLeft]; break; } @@ -225,7 +223,7 @@ typedef NSString *CALayerContentsGravity; defer:(BOOL)flag screen:(NSScreen *)screen { - GdkMacosCairoView *view; + GdkMacosView *view; self = [super initWithContentRect:contentRect styleMask:styleMask @@ -236,8 +234,9 @@ typedef NSString *CALayerContentsGravity; [self setAcceptsMouseMovedEvents:YES]; [self setDelegate:(id)self]; [self setReleasedWhenClosed:YES]; + [self setPreservesContentDuringLiveResize:NO]; - view = [[GdkMacosCairoView alloc] initWithFrame:contentRect]; + view = [[GdkMacosView alloc] initWithFrame:contentRect]; [self setContentView:view]; [view release]; @@ -754,7 +753,7 @@ typedef NSString *CALayerContentsGravity; -(void)windowWillExitFullScreen:(NSNotification *)aNotification { - [self setFrame:lastUnfullscreenFrame display:YES]; + [self setFrame:lastUnfullscreenFrame display:NO]; } -(void)windowDidExitFullScreen:(NSNotification *)aNotification @@ -810,4 +809,9 @@ typedef NSString *CALayerContentsGravity; return NO; } +-(void)swapBuffer:(GdkMacosBuffer *)buffer withDamage:(const cairo_region_t *)damage +{ + [(GdkMacosView *)[self contentView] swapBuffer:buffer withDamage:damage]; +} + @end diff --git a/gdk/macos/GdkMacosWindow.h b/gdk/macos/GdkMacosWindow.h index 61f546a78b..cb8b2efad1 100644 --- a/gdk/macos/GdkMacosWindow.h +++ b/gdk/macos/GdkMacosWindow.h @@ -21,9 +21,11 @@ #import #import +#import #include +#include "gdkmacosbuffer-private.h" #include "gdkmacosdisplay.h" #include "gdkmacossurface.h" #include "edgesnapping.h" @@ -66,5 +68,6 @@ -(BOOL)trackManualMove; -(BOOL)trackManualResize; -(void)setDecorated:(BOOL)decorated; +-(void)swapBuffer:(GdkMacosBuffer *)buffer withDamage:(const cairo_region_t *)damage; @end diff --git a/gdk/macos/gdkmacosbuffer-private.h b/gdk/macos/gdkmacosbuffer-private.h new file mode 100644 index 0000000000..6be201147c --- /dev/null +++ b/gdk/macos/gdkmacosbuffer-private.h @@ -0,0 +1,58 @@ +/* + * Copyright © 2021 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 + */ + +#ifndef __GDK_MACOS_BUFFER_PRIVATE_H__ +#define __GDK_MACOS_BUFFER_PRIVATE_H__ + +#include +#include +#include + +#include +#include + +G_BEGIN_DECLS + +#define GDK_TYPE_MACOS_BUFFER (gdk_macos_buffer_get_type()) + +G_DECLARE_FINAL_TYPE (GdkMacosBuffer, gdk_macos_buffer, GDK, MACOS_BUFFER, GObject) + +GdkMacosBuffer *_gdk_macos_buffer_new (int width, + int height, + double device_scale, + int bytes_per_element, + int bits_per_pixel); +IOSurfaceRef _gdk_macos_buffer_get_native (GdkMacosBuffer *self); +void _gdk_macos_buffer_lock (GdkMacosBuffer *self); +void _gdk_macos_buffer_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); +double _gdk_macos_buffer_get_device_scale (GdkMacosBuffer *self); +const cairo_region_t *_gdk_macos_buffer_get_damage (GdkMacosBuffer *self); +void _gdk_macos_buffer_set_damage (GdkMacosBuffer *self, + cairo_region_t *damage); +gpointer _gdk_macos_buffer_get_data (GdkMacosBuffer *self); +gboolean _gdk_macos_buffer_get_flipped (GdkMacosBuffer *self); +void _gdk_macos_buffer_set_flipped (GdkMacosBuffer *self, + gboolean flipped); + +G_END_DECLS + +#endif /* __GDK_MACOS_BUFFER_PRIVATE_H__ */ diff --git a/gdk/macos/gdkmacosbuffer.c b/gdk/macos/gdkmacosbuffer.c new file mode 100644 index 0000000000..ac99302ee4 --- /dev/null +++ b/gdk/macos/gdkmacosbuffer.c @@ -0,0 +1,271 @@ +/* + * Copyright © 2021 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 +#include +#include + +#include "gdkmacosbuffer-private.h" + +struct _GdkMacosBuffer +{ + GObject parent_instance; + cairo_region_t *damage; + IOSurfaceRef surface; + int lock_count; + guint bytes_per_element; + guint bits_per_pixel; + guint width; + guint height; + guint stride; + double device_scale; + guint flipped : 1; +}; + +G_DEFINE_TYPE (GdkMacosBuffer, gdk_macos_buffer, G_TYPE_OBJECT) + +static void +gdk_macos_buffer_dispose (GObject *object) +{ + GdkMacosBuffer *self = (GdkMacosBuffer *)object; + + if (self->lock_count != 0) + g_critical ("Attempt to dispose %s while lock is held", + G_OBJECT_TYPE_NAME (self)); + + /* We could potentially force the unload of our surface here with + * IOSurfaceSetPurgeable (self->surface, kIOSurfacePurgeableEmpty, NULL) + * but that would cause it to empty when the layers may still be attached + * to it. Better to just let it get GC'd by the system after they have + * moved on to a new buffer. + */ + g_clear_pointer (&self->surface, CFRelease); + g_clear_pointer (&self->damage, cairo_region_destroy); + + G_OBJECT_CLASS (gdk_macos_buffer_parent_class)->dispose (object); +} + +static void +gdk_macos_buffer_class_init (GdkMacosBufferClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gdk_macos_buffer_dispose; +} + +static void +gdk_macos_buffer_init (GdkMacosBuffer *self) +{ +} + +static void +add_int (CFMutableDictionaryRef dict, + const CFStringRef key, + int value) +{ + CFNumberRef number = CFNumberCreate (NULL, kCFNumberIntType, &value); + CFDictionaryAddValue (dict, key, number); + CFRelease (number); +} + +static IOSurfaceRef +create_surface (int width, + int height, + int bytes_per_element, + guint *stride) +{ + CFMutableDictionaryRef props; + IOSurfaceRef ret; + size_t bytes_per_row; + size_t total_bytes; + + props = CFDictionaryCreateMutable (kCFAllocatorDefault, + 16, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + if (props == NULL) + return NULL; + + bytes_per_row = IOSurfaceAlignProperty (kIOSurfaceBytesPerRow, width * bytes_per_element); + total_bytes = IOSurfaceAlignProperty (kIOSurfaceAllocSize, height * bytes_per_row); + + add_int (props, kIOSurfaceAllocSize, total_bytes); + add_int (props, kIOSurfaceBytesPerElement, bytes_per_element); + add_int (props, kIOSurfaceBytesPerRow, bytes_per_row); + add_int (props, kIOSurfaceHeight, height); + add_int (props, kIOSurfacePixelFormat, (int)'BGRA'); + add_int (props, kIOSurfaceWidth, width); + + ret = IOSurfaceCreate (props); + + CFRelease (props); + + *stride = bytes_per_row; + + return ret; +} + +GdkMacosBuffer * +_gdk_macos_buffer_new (int width, + int height, + double device_scale, + int bytes_per_element, + int bits_per_pixel) +{ + GdkMacosBuffer *self; + + g_return_val_if_fail (width > 0, NULL); + g_return_val_if_fail (height > 0, NULL); + + self = g_object_new (GDK_TYPE_MACOS_BUFFER, NULL); + self->bytes_per_element = bytes_per_element; + self->bits_per_pixel = bits_per_pixel; + self->surface = create_surface (width, height, bytes_per_element, &self->stride); + self->width = width; + self->height = height; + self->device_scale = device_scale; + self->lock_count = 0; + + if (self->surface == NULL) + g_clear_object (&self); + + return self; +} + +IOSurfaceRef +_gdk_macos_buffer_get_native (GdkMacosBuffer *self) +{ + g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), NULL); + + return self->surface; +} + +/** + * _gdk_macos_buffer_lock: + * + * This function matches the IOSurfaceLock() name but what it really + * does is page the buffer back for the CPU to access from VRAM. + * + * Generally we don't want to do that, but we do need to in some + * cases such as when we are rendering with Cairo. There might + * be an opportunity later to avoid that, but since we are using + * GL pretty much everywhere already, we don't try. + */ +void +_gdk_macos_buffer_lock (GdkMacosBuffer *self) +{ + g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); + g_return_if_fail (self->lock_count == 0); + + self->lock_count++; + + IOSurfaceLock (self->surface, 0, NULL); +} + +void +_gdk_macos_buffer_unlock (GdkMacosBuffer *self) +{ + g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); + g_return_if_fail (self->lock_count == 1); + + self->lock_count--; + + IOSurfaceUnlock (self->surface, 0, NULL); +} + +guint +_gdk_macos_buffer_get_width (GdkMacosBuffer *self) +{ + g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), 0); + + return self->width; +} + +guint +_gdk_macos_buffer_get_height (GdkMacosBuffer *self) +{ + g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), 0); + + return self->height; +} + +guint +_gdk_macos_buffer_get_stride (GdkMacosBuffer *self) +{ + g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), 0); + + return self->stride; +} + +double +_gdk_macos_buffer_get_device_scale (GdkMacosBuffer *self) +{ + g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), 1.0); + + return self->device_scale; +} + +const cairo_region_t * +_gdk_macos_buffer_get_damage (GdkMacosBuffer *self) +{ + g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), NULL); + + return self->damage; +} + +void +_gdk_macos_buffer_set_damage (GdkMacosBuffer *self, + cairo_region_t *damage) +{ + g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); + + if (damage == self->damage) + return; + + g_clear_pointer (&self->damage, cairo_region_destroy); + self->damage = cairo_region_reference (damage); +} + +gpointer +_gdk_macos_buffer_get_data (GdkMacosBuffer *self) +{ + g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), NULL); + + return IOSurfaceGetBaseAddress (self->surface); +} + +gboolean +_gdk_macos_buffer_get_flipped (GdkMacosBuffer *self) +{ + g_return_val_if_fail (GDK_IS_MACOS_BUFFER (self), FALSE); + + return self->flipped; +} + +void +_gdk_macos_buffer_set_flipped (GdkMacosBuffer *self, + gboolean flipped) +{ + g_return_if_fail (GDK_IS_MACOS_BUFFER (self)); + + self->flipped = !!flipped; +} diff --git a/gdk/macos/gdkmacoscairocontext.c b/gdk/macos/gdkmacoscairocontext.c index c1dd7f677c..041f1193e6 100644 --- a/gdk/macos/gdkmacoscairocontext.c +++ b/gdk/macos/gdkmacoscairocontext.c @@ -22,19 +22,17 @@ #include "gdkconfig.h" +#include +#include #include -#import "GdkMacosCairoView.h" - +#include "gdkmacosbuffer-private.h" #include "gdkmacoscairocontext-private.h" #include "gdkmacossurface-private.h" struct _GdkMacosCairoContext { - GdkCairoContext parent_instance; - - cairo_surface_t *window_surface; - cairo_t *cr; + GdkCairoContext parent_instance; }; struct _GdkMacosCairoContextClass @@ -44,80 +42,120 @@ struct _GdkMacosCairoContextClass G_DEFINE_TYPE (GdkMacosCairoContext, _gdk_macos_cairo_context, GDK_TYPE_CAIRO_CONTEXT) -static cairo_surface_t * -create_cairo_surface_for_surface (GdkSurface *surface) +static const cairo_user_data_key_t buffer_key; + +static void +unlock_buffer (gpointer data) { - static const cairo_user_data_key_t buffer_key; - cairo_surface_t *cairo_surface; - guint8 *data; - cairo_format_t format; - size_t size; - size_t rowstride; - size_t width; - size_t height; - int scale; + GdkMacosBuffer *buffer = data; - g_assert (GDK_IS_MACOS_SURFACE (surface)); + g_assert (GDK_IS_MACOS_BUFFER (buffer)); - /* We use a cairo image surface here instead of a quartz surface because - * we get strange artifacts with the quartz surface such as empty - * cross-fades when hovering buttons. For performance, we want to be using - * GL rendering so there isn't much point here as correctness is better. - * - * Additionally, so we can take avantage of faster paths in Core - * Graphics, we want our data pointer to be 16-byte aligned and our rows - * to be 16-byte aligned or we risk errors below us. Normally, cairo - * image surface does not guarantee the later, which means we could end - * up doing some costly copies along the way to compositing. - */ - - if ([GDK_MACOS_SURFACE (surface)->window isOpaque]) - format = CAIRO_FORMAT_RGB24; - else - format = CAIRO_FORMAT_ARGB32; - - scale = gdk_surface_get_scale_factor (surface); - width = scale * gdk_surface_get_width (surface); - height = scale * gdk_surface_get_height (surface); - rowstride = (cairo_format_stride_for_width (format, width) + 0xF) & ~0xF; - size = rowstride * height; - data = g_malloc0 (size); - cairo_surface = cairo_image_surface_create_for_data (data, format, width, height, rowstride); - cairo_surface_set_user_data (cairo_surface, &buffer_key, data, g_free); - cairo_surface_set_device_scale (cairo_surface, scale, scale); - - return cairo_surface; -} - -static cairo_t * -do_cairo_create (GdkMacosCairoContext *self) -{ - GdkSurface *surface; - cairo_t *cr; - - g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self)); - - surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self)); - cr = cairo_create (self->window_surface); - - /* Draw upside down as quartz prefers */ - cairo_translate (cr, 0, surface->height); - cairo_scale (cr, 1.0, -1.0); - - return cr; + _gdk_macos_buffer_unlock (buffer); + g_clear_object (&buffer); } static cairo_t * _gdk_macos_cairo_context_cairo_create (GdkCairoContext *cairo_context) { GdkMacosCairoContext *self = (GdkMacosCairoContext *)cairo_context; + const cairo_region_t *damage; + cairo_surface_t *image_surface; + GdkMacosBuffer *buffer; + GdkSurface *surface; + NSWindow *nswindow; + cairo_t *cr; + gpointer data; + double scale; + guint width; + guint height; + guint stride; + gboolean opaque; g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self)); - if (self->cr != NULL) - return cairo_reference (self->cr); + surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self)); + nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface)); + opaque = [nswindow isOpaque]; - return do_cairo_create (self); + buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface)); + damage = _gdk_macos_buffer_get_damage (buffer); + width = _gdk_macos_buffer_get_width (buffer); + height = _gdk_macos_buffer_get_height (buffer); + scale = _gdk_macos_buffer_get_device_scale (buffer); + stride = _gdk_macos_buffer_get_stride (buffer); + data = _gdk_macos_buffer_get_data (buffer); + + /* Instead of forcing cairo to do everything through a CGContext, + * we just use an image surface backed by an IOSurfaceRef mapped + * into user-space. We can then use pixman which is quite fast as + * far as software rendering goes. + * + * Additionally, cairo_quartz_surface_t can't handle a number of + * tricks that the GSK cairo renderer does with border nodes and + * shadows, so an image surface is necessary for that. + * + * Since our IOSurfaceRef is width*scale-by-height*scale, we undo + * the scaling using cairo_surface_set_device_scale() so the renderer + * just thinks it's on a 2x scale surface for HiDPI. + */ + image_surface = cairo_image_surface_create_for_data (data, + CAIRO_FORMAT_ARGB32, + width, + height, + 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); + + if (!(cr = cairo_create (image_surface))) + goto failure; + + /* Clip to the current damage region */ + if (damage != NULL) + { + gdk_cairo_region (cr, damage); + cairo_clip (cr); + } + + /* If we have some exposed transparent area in the damage region, + * we need to clear the existing content first to leave an transparent + * area for cairo. We use (surface_bounds or damage)-(opaque) to get + * the smallest set of rectangles we need to clear as it's expensive. + */ + if (!opaque) + { + cairo_region_t *transparent; + cairo_rectangle_int_t r = { 0, 0, width/scale, height/scale }; + + cairo_save (cr); + + if (damage != NULL) + cairo_region_get_extents (damage, &r); + transparent = cairo_region_create_rectangle (&r); + if (surface->opaque_region) + cairo_region_subtract (transparent, surface->opaque_region); + + if (!cairo_region_is_empty (transparent)) + { + gdk_cairo_region (cr, transparent); + cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); + cairo_fill (cr); + } + + cairo_region_destroy (transparent); + cairo_restore (cr); + } + +failure: + cairo_surface_destroy (image_surface); + + return cr; } static void @@ -126,80 +164,53 @@ _gdk_macos_cairo_context_begin_frame (GdkDrawContext *draw_context, cairo_region_t *region) { GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context; + GdkMacosBuffer *buffer; GdkSurface *surface; - NSWindow *nswindow; g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self)); + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + surface = gdk_draw_context_get_surface (draw_context); - nswindow = _gdk_macos_surface_get_native (GDK_MACOS_SURFACE (surface)); + buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface)); - if (self->window_surface == NULL) - self->window_surface = create_cairo_surface_for_surface (surface); - - self->cr = do_cairo_create (self); - - if (![nswindow isOpaque]) - { - cairo_save (self->cr); - gdk_cairo_region (self->cr, region); - cairo_set_source_rgba (self->cr, 0, 0, 0, 0); - cairo_set_operator (self->cr, CAIRO_OPERATOR_SOURCE); - cairo_fill (self->cr); - cairo_restore (self->cr); - } + _gdk_macos_buffer_set_damage (buffer, region); + _gdk_macos_buffer_set_flipped (buffer, FALSE); } static void _gdk_macos_cairo_context_end_frame (GdkDrawContext *draw_context, cairo_region_t *painted) { - GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context; + GdkMacosBuffer *buffer; GdkSurface *surface; - NSView *nsview; - g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self)); - g_assert (self->window_surface != NULL); + g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (draw_context)); surface = gdk_draw_context_get_surface (draw_context); - nsview = _gdk_macos_surface_get_view (GDK_MACOS_SURFACE (surface)); + buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface)); - g_clear_pointer (&self->cr, cairo_destroy); + _gdk_macos_surface_swap_buffers (GDK_MACOS_SURFACE (surface), painted); + _gdk_macos_buffer_set_damage (buffer, NULL); - if (GDK_IS_MACOS_CAIRO_VIEW (nsview)) - [(GdkMacosCairoView *)nsview setCairoSurface:self->window_surface - withDamage:painted]; + [CATransaction commit]; } static void _gdk_macos_cairo_context_surface_resized (GdkDrawContext *draw_context) { - GdkMacosCairoContext *self = (GdkMacosCairoContext *)draw_context; + g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (draw_context)); - g_assert (GDK_IS_MACOS_CAIRO_CONTEXT (self)); - - g_clear_pointer (&self->window_surface, cairo_surface_destroy); -} - -static void -_gdk_macos_cairo_context_dispose (GObject *object) -{ - GdkMacosCairoContext *self = (GdkMacosCairoContext *)object; - - g_clear_pointer (&self->window_surface, cairo_surface_destroy); - - G_OBJECT_CLASS (_gdk_macos_cairo_context_parent_class)->dispose (object); + /* Do nothing, next begin_frame will get new buffer */ } static void _gdk_macos_cairo_context_class_init (GdkMacosCairoContextClass *klass) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); GdkCairoContextClass *cairo_context_class = GDK_CAIRO_CONTEXT_CLASS (klass); GdkDrawContextClass *draw_context_class = GDK_DRAW_CONTEXT_CLASS (klass); - object_class->dispose = _gdk_macos_cairo_context_dispose; - draw_context_class->begin_frame = _gdk_macos_cairo_context_begin_frame; draw_context_class->end_frame = _gdk_macos_cairo_context_end_frame; draw_context_class->surface_resized = _gdk_macos_cairo_context_surface_resized; diff --git a/gdk/macos/gdkmacosdisplay.c b/gdk/macos/gdkmacosdisplay.c index a8af833eb5..74095504a2 100644 --- a/gdk/macos/gdkmacosdisplay.c +++ b/gdk/macos/gdkmacosdisplay.c @@ -173,8 +173,7 @@ gdk_macos_display_monitors_changed_cb (CFNotificationCenterRef center, _gdk_macos_display_reload_monitors (self); /* Now we need to update all our surface positions since they - * probably just changed origins. We ignore the popup surfaces - * since we can rely on the toplevel surfaces to handle that. + * probably just changed origins. */ for (const GList *iter = _gdk_macos_display_get_surfaces (self); iter != NULL; @@ -184,8 +183,7 @@ gdk_macos_display_monitors_changed_cb (CFNotificationCenterRef center, g_assert (GDK_IS_MACOS_SURFACE (surface)); - if (GDK_IS_TOPLEVEL (surface)) - _gdk_macos_surface_configure (surface); + _gdk_macos_surface_monitor_changed (surface); } } diff --git a/gdk/macos/gdkmacosglcontext-private.h b/gdk/macos/gdkmacosglcontext-private.h index 781035677a..8b3eac2ca6 100644 --- a/gdk/macos/gdkmacosglcontext-private.h +++ b/gdk/macos/gdkmacosglcontext-private.h @@ -29,7 +29,7 @@ #include "gdkmacossurface.h" #import -#import +#import #import G_BEGIN_DECLS @@ -38,17 +38,17 @@ struct _GdkMacosGLContext { GdkGLContext parent_instance; - G_GNUC_BEGIN_IGNORE_DEPRECATIONS - NSOpenGLContext *gl_context; - G_GNUC_END_IGNORE_DEPRECATIONS - - NSWindow *dummy_window; - NSView *dummy_view; - cairo_region_t *damage; - guint is_attached : 1; - guint needs_resize : 1; + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + CGLContextObj cgl_context; + G_GNUC_END_IGNORE_DEPRECATIONS + + GLuint texture; + GLuint target; + GLuint fbo; + + guint last_opaque : 1; }; struct _GdkMacosGLContextClass @@ -56,7 +56,6 @@ struct _GdkMacosGLContextClass GdkGLContextClass parent_class; }; - G_END_DECLS #endif /* __GDK_MACOS_GL_CONTEXT_PRIVATE_H__ */ diff --git a/gdk/macos/gdkmacosglcontext.c b/gdk/macos/gdkmacosglcontext.c index 1800815786..5baff95a9b 100644 --- a/gdk/macos/gdkmacosglcontext.c +++ b/gdk/macos/gdkmacosglcontext.c @@ -19,20 +19,139 @@ #include "config.h" +#include "gdkconfig.h" + +#include +#include +#include + +#include "gdkmacosbuffer-private.h" #include "gdkmacosglcontext-private.h" #include "gdkmacossurface-private.h" -#include "gdkmacostoplevelsurface-private.h" - -#include "gdkintl.h" - -#include - -#import "GdkMacosGLView.h" G_GNUC_BEGIN_IGNORE_DEPRECATIONS G_DEFINE_TYPE (GdkMacosGLContext, gdk_macos_gl_context, GDK_TYPE_GL_CONTEXT) +#define CHECK(error,cgl_error) _CHECK_CGL(error, G_STRLOC, cgl_error) +static inline gboolean +_CHECK_CGL (GError **error, + const char *location, + CGLError cgl_error) +{ + if (cgl_error != kCGLNoError) + { + g_log ("Core OpenGL", + G_LOG_LEVEL_CRITICAL, + "%s: %s", + location, CGLErrorString (cgl_error)); + g_set_error (error, + GDK_GL_ERROR, + GDK_GL_ERROR_NOT_AVAILABLE, + "%s", + CGLErrorString (cgl_error)); + return FALSE; + } + + return TRUE; +} + +/* Apple's OpenGL implementation does not contain the extension to + * perform log handler callbacks when errors occur. Therefore, to aid in + * tracking down issues we have a CHECK_GL() macro that can wrap GL + * calls and check for an error afterwards. + * + * To make this easier, we use a statement expression, as this will + * always be using something GCC-compatible on macOS. + */ +#define CHECK_GL(error,func) _CHECK_GL(error, G_STRLOC, ({ func; glGetError(); })) +static inline gboolean +_CHECK_GL (GError **error, + const char *location, + GLenum gl_error) +{ + const char *msg; + + switch (gl_error) + { + case GL_INVALID_ENUM: + msg = "invalid enum"; + break; + case GL_INVALID_VALUE: + msg = "invalid value"; + break; + case GL_INVALID_OPERATION: + msg = "invalid operation"; + break; + case GL_INVALID_FRAMEBUFFER_OPERATION: + msg = "invalid framebuffer operation"; + break; + case GL_OUT_OF_MEMORY: + msg = "out of memory"; + break; + default: + msg = "unknown error"; + break; + } + + if (gl_error != GL_NO_ERROR) + { + g_log ("OpenGL", + G_LOG_LEVEL_CRITICAL, + "%s: %s", location, msg); + if (error != NULL) + g_set_error (error, + GDK_GL_ERROR, + GDK_GL_ERROR_NOT_AVAILABLE, + "%s", msg); + return FALSE; + } + + return TRUE; +} + +static gboolean +check_framebuffer_status (GLenum target) +{ + switch (glCheckFramebufferStatus (target)) + { + case GL_FRAMEBUFFER_COMPLETE: + return TRUE; + + case GL_FRAMEBUFFER_UNDEFINED: + g_critical ("Framebuffer is undefined"); + return FALSE; + + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + g_critical ("Framebuffer has incomplete attachment"); + return FALSE; + + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + g_critical ("Framebuffer has missing attachment"); + return FALSE; + + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: + g_critical ("Framebuffer has incomplete draw buffer"); + return FALSE; + + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: + g_critical ("Framebuffer has incomplete read buffer"); + return FALSE; + + case GL_FRAMEBUFFER_UNSUPPORTED: + g_critical ("Framebuffer is unsupported"); + return FALSE; + + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: + g_critical ("Framebuffer has incomplete multisample"); + return FALSE; + + default: + g_critical ("Framebuffer has unknown error"); + return FALSE; + } +} + static const char * get_renderer_name (GLint id) { @@ -72,97 +191,165 @@ get_renderer_name (GLint id) } } -static NSOpenGLContext * -get_ns_open_gl_context (GdkMacosGLContext *self, - GError **error) +static GLuint +create_texture (CGLContextObj cgl_context, + GLuint target, + IOSurfaceRef io_surface, + guint width, + guint height) { - g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); + GLuint texture = 0; - if (self->gl_context == nil) + if (!CHECK_GL (NULL, glActiveTexture (GL_TEXTURE0)) || + !CHECK_GL (NULL, glGenTextures (1, &texture)) || + !CHECK_GL (NULL, glBindTexture (target, texture)) || + !CHECK (NULL, CGLTexImageIOSurface2D (cgl_context, + target, + GL_RGBA, + width, + height, + GL_BGRA, + GL_UNSIGNED_INT_8_8_8_8_REV, + io_surface, + 0)) || + !CHECK_GL (NULL, glTexParameteri (target, GL_TEXTURE_BASE_LEVEL, 0)) || + !CHECK_GL (NULL, glTexParameteri (target, GL_TEXTURE_MIN_FILTER, GL_NEAREST)) || + !CHECK_GL (NULL, glTexParameteri (target, GL_TEXTURE_MAG_FILTER, GL_NEAREST)) || + !CHECK_GL (NULL, glTexParameteri (target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)) || + !CHECK_GL (NULL, glTexParameteri (target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)) || + !CHECK_GL (NULL, glTexParameteri (target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)) || + !CHECK_GL (NULL, glBindTexture (target, 0))) { - g_set_error_literal (error, - GDK_GL_ERROR, - GDK_GL_ERROR_NOT_AVAILABLE, - "Cannot access NSOpenGLContext for surface"); - return NULL; + glDeleteTextures (1, &texture); + return 0; } - return self->gl_context; + return texture; } -static NSOpenGLPixelFormat * +static void +gdk_macos_gl_context_allocate (GdkMacosGLContext *self) +{ + GdkSurface *surface; + GLint opaque; + + g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); + g_assert (self->cgl_context != NULL); + g_assert (self->target != 0); + g_assert (self->texture != 0 || self->fbo == 0); + g_assert (self->fbo != 0 || self->texture == 0); + + if (!(surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self)))) + return; + + /* Alter to an opaque surface if necessary */ + opaque = _gdk_macos_surface_is_opaque (GDK_MACOS_SURFACE (surface)); + if (opaque != self->last_opaque) + { + self->last_opaque = !!opaque; + if (!CHECK (NULL, CGLSetParameter (self->cgl_context, kCGLCPSurfaceOpacity, &opaque))) + return; + } + + if (self->texture == 0) + { + GdkMacosBuffer *buffer; + IOSurfaceRef io_surface; + guint width; + guint height; + GLuint texture = 0; + GLuint fbo = 0; + + buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface)); + io_surface = _gdk_macos_buffer_get_native (buffer); + width = _gdk_macos_buffer_get_width (buffer); + height = _gdk_macos_buffer_get_height (buffer); + + /* We might need to re-enforce our CGL context here to keep + * video playing correctly. Something, somewhere, might have + * changed the context without touching GdkGLContext. + * + * Without this, video_player often breaks in gtk-demo when using + * the GStreamer backend. + */ + CGLSetCurrentContext (self->cgl_context); + + if (!(texture = create_texture (self->cgl_context, self->target, io_surface, width, height)) || + !CHECK_GL (NULL, glGenFramebuffers (1, &fbo)) || + !CHECK_GL (NULL, glBindFramebuffer (GL_FRAMEBUFFER, fbo)) || + !CHECK_GL (NULL, glBindTexture (self->target, texture)) || + !CHECK_GL (NULL, glFramebufferTexture2D (GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + self->target, + texture, + 0)) || + !check_framebuffer_status (GL_FRAMEBUFFER)) + { + glDeleteFramebuffers (1, &fbo); + glDeleteTextures (1, &texture); + return; + } + + glBindTexture (self->target, 0); + glBindFramebuffer (GL_FRAMEBUFFER, 0); + + self->texture = texture; + self->fbo = fbo; + } +} + +static void +gdk_macos_gl_context_release (GdkMacosGLContext *self) +{ + g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); + g_assert (self->texture != 0 || self->fbo == 0); + g_assert (self->fbo != 0 || self->texture == 0); + + glBindFramebuffer (GL_FRAMEBUFFER, 0); + glActiveTexture (GL_TEXTURE0); + glBindTexture (self->target, 0); + + if (self->fbo != 0) + { + glDeleteFramebuffers (1, &self->fbo); + self->fbo = 0; + } + + if (self->texture != 0) + { + glDeleteTextures (1, &self->texture); + self->texture = 0; + } +} + +static CGLPixelFormatObj create_pixel_format (int major, int minor, GError **error) { - NSOpenGLPixelFormatAttribute attrs[] = { - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, - NSOpenGLPFAAccelerated, - NSOpenGLPFADoubleBuffer, - NSOpenGLPFABackingStore, - NSOpenGLPFAColorSize, 24, - NSOpenGLPFAAlphaSize, 8, + CGLPixelFormatAttribute attrs[] = { + kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)kCGLOGLPVersion_Legacy, + kCGLPFAAllowOfflineRenderers, /* allow sharing across GPUs */ + kCGLPFADepthSize, 0, + kCGLPFAStencilSize, 0, + kCGLPFAColorSize, 24, + kCGLPFAAlphaSize, 8, 0 }; + CGLPixelFormatObj format = NULL; + GLint n_format = 1; if (major == 3 && minor == 2) - attrs[1] = NSOpenGLProfileVersion3_2Core; + attrs[1] = (CGLPixelFormatAttribute)kCGLOGLPVersion_GL3_Core; else if (major == 4 && minor == 1) - attrs[1] = NSOpenGLProfileVersion4_1Core; + attrs[1] = (CGLPixelFormatAttribute)kCGLOGLPVersion_GL4_Core; - NSOpenGLPixelFormat *format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; - - if (format == NULL) - g_set_error (error, - GDK_GL_ERROR, - GDK_GL_ERROR_NOT_AVAILABLE, - "Failed to create pixel format"); + if (!CHECK (error, CGLChoosePixelFormat (attrs, &format, &n_format))) + return NULL; return g_steal_pointer (&format); } -static NSView * -ensure_gl_view (GdkMacosGLContext *self) -{ - GdkMacosSurface *surface; - NSWindow *nswindow; - NSView *nsview; - - g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); - - surface = GDK_MACOS_SURFACE (gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self))); - nsview = _gdk_macos_surface_get_view (surface); - nswindow = _gdk_macos_surface_get_native (surface); - - if G_UNLIKELY (!GDK_IS_MACOS_GL_VIEW (nsview)) - { - NSRect frame; - - frame = [[nswindow contentView] bounds]; - nsview = [[GdkMacosGLView alloc] initWithFrame:frame]; - [nsview setWantsBestResolutionOpenGLSurface:YES]; - [nsview setPostsFrameChangedNotifications: YES]; - [nsview setNeedsDisplay:YES]; - [nswindow setContentView:nsview]; - [nswindow makeFirstResponder:nsview]; - [nsview release]; - - if (self->dummy_view != NULL) - { - NSView *dummy_view = g_steal_pointer (&self->dummy_view); - [dummy_view release]; - } - - if (self->dummy_window != NULL) - { - NSWindow *dummy_window = g_steal_pointer (&self->dummy_window); - [dummy_window release]; - } - } - - return [nswindow contentView]; -} - static GdkGLAPI gdk_macos_gl_context_real_realize (GdkGLContext *context, GError **error) @@ -170,195 +357,130 @@ gdk_macos_gl_context_real_realize (GdkGLContext *context, GdkMacosGLContext *self = (GdkMacosGLContext *)context; GdkSurface *surface; GdkDisplay *display; - NSOpenGLContext *shared_gl_context = nil; - NSOpenGLContext *gl_context; - NSOpenGLPixelFormat *pixelFormat; + CGLPixelFormatObj pixelFormat; + CGLContextObj shared_gl_context = nil; CGLContextObj cgl_context; + CGLContextObj existing; GdkGLContext *shared; - NSOpenGLContext *existing; GLint sync_to_framerate = 1; GLint validate = 0; + GLint renderer_id = 0; GLint swapRect[4]; int major, minor; g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); - if (self->gl_context != nil) + if (self->cgl_context != nil) return GDK_GL_API_GL; if (!gdk_gl_context_is_api_allowed (context, GDK_GL_API_GL, error)) return 0; - existing = [NSOpenGLContext currentContext]; + existing = CGLGetCurrentContext (); gdk_gl_context_get_required_version (context, &major, &minor); - surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (context)); display = gdk_gl_context_get_display (context); shared = gdk_display_get_gl_context (display); if (shared != NULL) { - if (!(shared_gl_context = get_ns_open_gl_context (GDK_MACOS_GL_CONTEXT (shared), error))) - return 0; + if (!(shared_gl_context = GDK_MACOS_GL_CONTEXT (shared)->cgl_context)) + { + g_set_error_literal (error, + GDK_GL_ERROR, + GDK_GL_ERROR_NOT_AVAILABLE, + "Cannot access shared CGLContextObj"); + return 0; + } } GDK_DISPLAY_NOTE (display, OPENGL, - g_message ("Creating NSOpenGLContext (version %d.%d)", + g_message ("Creating CGLContextObj (version %d.%d)", major, minor)); if (!(pixelFormat = create_pixel_format (major, minor, error))) return 0; - gl_context = [[NSOpenGLContext alloc] initWithFormat:pixelFormat - shareContext:shared_gl_context]; - - [pixelFormat release]; - - if (gl_context == nil) + if (!CHECK (error, CGLCreateContext (pixelFormat, shared_gl_context, &cgl_context))) { - g_set_error_literal (error, - GDK_GL_ERROR, - GDK_GL_ERROR_NOT_AVAILABLE, - "Failed to create NSOpenGLContext"); + CGLReleasePixelFormat (pixelFormat); return 0; } - cgl_context = [gl_context CGLContextObj]; + CGLSetCurrentContext (cgl_context); + CGLReleasePixelFormat (pixelFormat); - swapRect[0] = 0; - swapRect[1] = 0; - swapRect[2] = surface ? surface->width : 0; - swapRect[3] = surface ? surface->height : 0; - - CGLSetParameter (cgl_context, kCGLCPSwapRectangle, swapRect); - CGLSetParameter (cgl_context, kCGLCPSwapInterval, &sync_to_framerate); - - CGLEnable (cgl_context, kCGLCESwapRectangle); if (validate) - CGLEnable (cgl_context, kCGLCEStateValidation); + CHECK (NULL, CGLEnable (cgl_context, kCGLCEStateValidation)); - self->dummy_window = [[NSWindow alloc] initWithContentRect:NSZeroRect - styleMask:0 - backing:NSBackingStoreBuffered - defer:NO - screen:nil]; - self->dummy_view = [[NSView alloc] initWithFrame:NSZeroRect]; - [self->dummy_window setContentView:self->dummy_view]; - [gl_context setView:self->dummy_view]; + if (!CHECK (error, CGLSetParameter (cgl_context, kCGLCPSwapInterval, &sync_to_framerate)) || + !CHECK (error, CGLGetParameter (cgl_context, kCGLCPCurrentRendererID, &renderer_id))) + { + CGLReleaseContext (cgl_context); + return 0; + } - GLint renderer_id = 0; - [gl_context getValues:&renderer_id forParameter:NSOpenGLContextParameterCurrentRendererID]; - GDK_DISPLAY_NOTE (display, - OPENGL, - g_message ("Created NSOpenGLContext[%p] using %s", - gl_context, - get_renderer_name (renderer_id))); + surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (context)); - self->gl_context = g_steal_pointer (&gl_context); - - if (existing != NULL) - [existing makeCurrentContext]; - - return GDK_GL_API_GL; -} - -static gboolean -opaque_region_covers_surface (GdkMacosGLContext *self) -{ - GdkSurface *surface; - cairo_region_t *region; - - g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); - - surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self)); - region = GDK_MACOS_SURFACE (surface)->opaque_region; - - if (region != NULL && - cairo_region_num_rectangles (region) == 1) + if (surface != NULL) { - cairo_rectangle_int_t extents; - - cairo_region_get_extents (region, &extents); - - if (extents.x == 0 && - extents.y == 0 && - extents.width == surface->width && - extents.height == surface->height) - return TRUE; + /* Setup initial swap rectangle. We might not actually need this + * anymore though as we are rendering to an IOSurface and we have + * a scissor clip when rendering to it. + */ + swapRect[0] = 0; + swapRect[1] = 0; + swapRect[2] = surface->width; + swapRect[3] = surface->height; + CGLSetParameter (cgl_context, kCGLCPSwapRectangle, swapRect); + CGLEnable (cgl_context, kCGLCESwapRectangle); } - return FALSE; + GDK_DISPLAY_NOTE (display, + OPENGL, + g_message ("Created CGLContextObj@%p using %s", + cgl_context, + get_renderer_name (renderer_id))); + + self->cgl_context = g_steal_pointer (&cgl_context); + + if (existing != NULL) + CGLSetCurrentContext (existing); + + return GDK_GL_API_GL; } static void gdk_macos_gl_context_begin_frame (GdkDrawContext *context, gboolean prefers_high_depth, - cairo_region_t *painted) + cairo_region_t *region) { GdkMacosGLContext *self = (GdkMacosGLContext *)context; + GdkMacosBuffer *buffer; + cairo_region_t *copy; GdkSurface *surface; g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); + copy = cairo_region_copy (region); surface = gdk_draw_context_get_surface (context); + buffer = _gdk_macos_surface_get_buffer (GDK_MACOS_SURFACE (surface)); + + _gdk_macos_buffer_set_flipped (buffer, TRUE); + + /* Create our render target and bind it */ + gdk_gl_context_make_current (GDK_GL_CONTEXT (self)); + gdk_macos_gl_context_allocate (self); + + GDK_DRAW_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->begin_frame (context, prefers_high_depth, region); g_clear_pointer (&self->damage, cairo_region_destroy); - self->damage = cairo_region_copy (painted); + self->damage = g_steal_pointer (©); - /* If begin frame is called, that means we are trying to draw to - * the NSWindow using our view. That might be a GdkMacosCairoView - * but we need it to be a GL view. Also, only in this case do we - * want to replace our damage region for the next frame (to avoid - * doing it multiple times). - */ - ensure_gl_view (self); - - if (self->needs_resize) - { - CGLContextObj cgl_context = [self->gl_context CGLContextObj]; - GLint opaque; - - self->needs_resize = FALSE; - - if (self->dummy_view != NULL) - { - NSRect frame = NSMakeRect (0, 0, surface->width, surface->height); - - [self->dummy_window setFrame:frame display:NO]; - [self->dummy_view setFrame:frame]; - } - - /* Possibly update our opaque setting depending on a resize. We can - * rely on getting a resize if decoarated is changed, so this reduces - * how much we adjust the parameter. - */ - if (GDK_IS_MACOS_TOPLEVEL_SURFACE (surface)) - opaque = GDK_MACOS_TOPLEVEL_SURFACE (surface)->decorated; - else - opaque = FALSE; - - /* If we are maximized, we might be able to make it opaque */ - if (opaque == FALSE) - opaque = opaque_region_covers_surface (self); - - CGLSetParameter (cgl_context, kCGLCPSurfaceOpacity, &opaque); - - [self->gl_context update]; - } - - GDK_DRAW_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->begin_frame (context, prefers_high_depth, painted); - - if (!self->is_attached) - { - NSView *nsview = _gdk_macos_surface_get_view (GDK_MACOS_SURFACE (surface)); - - g_assert (self->gl_context != NULL); - g_assert (GDK_IS_MACOS_GL_VIEW (nsview)); - - [(GdkMacosGLView *)nsview setOpenGLContext:self->gl_context]; - } + gdk_gl_context_make_current (GDK_GL_CONTEXT (self)); + CHECK_GL (NULL, glBindFramebuffer (GL_FRAMEBUFFER, self->fbo)); } static void @@ -366,32 +488,40 @@ gdk_macos_gl_context_end_frame (GdkDrawContext *context, cairo_region_t *painted) { GdkMacosGLContext *self = GDK_MACOS_GL_CONTEXT (context); + GdkSurface *surface; + cairo_rectangle_int_t flush_rect; + GLint swapRect[4]; g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); - g_assert (self->gl_context != nil); + g_assert (self->cgl_context != nil); GDK_DRAW_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->end_frame (context, painted); - if (!self->is_attached) - { - GdkSurface *surface = gdk_draw_context_get_surface (context); - CGLContextObj glctx = [self->gl_context CGLContextObj]; - cairo_rectangle_int_t flush_rect; - GLint swapRect[4]; + surface = gdk_draw_context_get_surface (context); + gdk_gl_context_make_current (GDK_GL_CONTEXT (self)); - /* Coordinates are in display coordinates, where as flush_rect is - * in GDK coordinates. Must flip Y to match display coordinates where - * 0,0 is the bottom-left corner. - */ - cairo_region_get_extents (painted, &flush_rect); - swapRect[0] = flush_rect.x; /* left */ - swapRect[1] = surface->height - flush_rect.y; /* bottom */ - swapRect[2] = flush_rect.width; /* width */ - swapRect[3] = flush_rect.height; /* height */ - CGLSetParameter (glctx, kCGLCPSwapRectangle, swapRect); + /* Coordinates are in display coordinates, where as flush_rect is + * in GDK coordinates. Must flip Y to match display coordinates where + * 0,0 is the bottom-left corner. + */ + cairo_region_get_extents (painted, &flush_rect); + swapRect[0] = flush_rect.x; /* left */ + swapRect[1] = surface->height - flush_rect.y; /* bottom */ + swapRect[2] = flush_rect.width; /* width */ + swapRect[3] = flush_rect.height; /* height */ + CGLSetParameter (self->cgl_context, kCGLCPSwapRectangle, swapRect); - [self->gl_context flushBuffer]; - } + gdk_macos_gl_context_release (self); + + glFlush (); + + /* Begin a Core Animation transaction so that all changes we + * make within the window are seen atomically. + */ + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + _gdk_macos_surface_swap_buffers (GDK_MACOS_SURFACE (surface), painted); + [CATransaction commit]; } static void @@ -401,31 +531,23 @@ gdk_macos_gl_context_surface_resized (GdkDrawContext *draw_context) g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); - self->needs_resize = TRUE; - g_clear_pointer (&self->damage, cairo_region_destroy); + + if (self->cgl_context != NULL) + CGLUpdateContext (self->cgl_context); } static gboolean gdk_macos_gl_context_clear_current (GdkGLContext *context) { GdkMacosGLContext *self = GDK_MACOS_GL_CONTEXT (context); - NSOpenGLContext *current; g_return_val_if_fail (GDK_IS_MACOS_GL_CONTEXT (self), FALSE); - current = [NSOpenGLContext currentContext]; - - if (self->gl_context == current) + if (self->cgl_context == CGLGetCurrentContext ()) { - /* The OpenGL mac programming guide suggests that glFlush() is called - * before switching current contexts to ensure that the drawing commands - * are submitted. - */ - if (current != NULL) - glFlush (); - - [NSOpenGLContext clearCurrentContext]; + glFlush (); + CGLSetCurrentContext (NULL); } return TRUE; @@ -436,22 +558,26 @@ gdk_macos_gl_context_make_current (GdkGLContext *context, gboolean surfaceless) { GdkMacosGLContext *self = GDK_MACOS_GL_CONTEXT (context); - NSOpenGLContext *current; + CGLContextObj current; g_return_val_if_fail (GDK_IS_MACOS_GL_CONTEXT (self), FALSE); - current = [NSOpenGLContext currentContext]; + current = CGLGetCurrentContext (); - if (self->gl_context != current) + if (self->cgl_context != current) { /* The OpenGL mac programming guide suggests that glFlush() is called * before switching current contexts to ensure that the drawing commands * are submitted. + * + * TODO: investigate if we need this because we may switch contexts + * durring composition and only need it when returning to a + * previous context that uses the other context. */ if (current != NULL) glFlush (); - [self->gl_context makeCurrentContext]; + CGLSetCurrentContext (self->cgl_context); } return TRUE; @@ -462,40 +588,35 @@ gdk_macos_gl_context_get_damage (GdkGLContext *context) { GdkMacosGLContext *self = (GdkMacosGLContext *)context; - g_assert (GDK_IS_MACOS_GL_CONTEXT (self)); - - if (self->damage != NULL) + if (self->damage) return cairo_region_copy (self->damage); return GDK_GL_CONTEXT_CLASS (gdk_macos_gl_context_parent_class)->get_damage (context); } +static guint +gdk_macos_gl_context_get_default_framebuffer (GdkGLContext *context) +{ + return GDK_MACOS_GL_CONTEXT (context)->fbo; +} + static void gdk_macos_gl_context_dispose (GObject *gobject) { GdkMacosGLContext *self = GDK_MACOS_GL_CONTEXT (gobject); - if (self->dummy_view != nil) + self->texture = 0; + self->fbo = 0; + + if (self->cgl_context != nil) { - NSView *nsview = g_steal_pointer (&self->dummy_view); - [nsview release]; - } + CGLContextObj cgl_context = g_steal_pointer (&self->cgl_context); - if (self->dummy_window != nil) - { - NSWindow *nswindow = g_steal_pointer (&self->dummy_window); - [nswindow release]; - } + if (cgl_context == CGLGetCurrentContext ()) + CGLSetCurrentContext (NULL); - if (self->gl_context != nil) - { - NSOpenGLContext *gl_context = g_steal_pointer (&self->gl_context); - - if (gl_context == [NSOpenGLContext currentContext]) - [NSOpenGLContext clearCurrentContext]; - - [gl_context clearDrawable]; - [gl_context release]; + CGLClearDrawable (cgl_context); + CGLDestroyContext (cgl_context); } g_clear_pointer (&self->damage, cairo_region_destroy); @@ -520,6 +641,7 @@ gdk_macos_gl_context_class_init (GdkMacosGLContextClass *klass) gl_class->clear_current = gdk_macos_gl_context_clear_current; gl_class->make_current = gdk_macos_gl_context_make_current; gl_class->realize = gdk_macos_gl_context_real_realize; + gl_class->get_default_framebuffer = gdk_macos_gl_context_get_default_framebuffer; gl_class->backend_type = GDK_GL_CGL; } @@ -527,6 +649,7 @@ gdk_macos_gl_context_class_init (GdkMacosGLContextClass *klass) static void gdk_macos_gl_context_init (GdkMacosGLContext *self) { + self->target = GL_TEXTURE_RECTANGLE; } G_GNUC_END_IGNORE_DEPRECATIONS diff --git a/gdk/macos/gdkmacossurface-private.h b/gdk/macos/gdkmacossurface-private.h index c8485f88dd..2abc199698 100644 --- a/gdk/macos/gdkmacossurface-private.h +++ b/gdk/macos/gdkmacossurface-private.h @@ -25,6 +25,7 @@ #include "gdksurfaceprivate.h" +#include "gdkmacosbuffer-private.h" #include "gdkmacosdisplay.h" #include "gdkmacossurface.h" @@ -45,8 +46,9 @@ struct _GdkMacosSurface GList frame; GdkMacosWindow *window; + GdkMacosBuffer *buffer; + GdkMacosBuffer *front; GPtrArray *monitors; - cairo_region_t *input_region; cairo_region_t *opaque_region; char *title; @@ -65,10 +67,14 @@ struct _GdkMacosSurface int shadow_bottom; int shadow_left; + cairo_rectangle_int_t next_frame; + gint64 pending_frame_counter; guint did_initial_present : 1; guint geometry_dirty : 1; + guint next_frame_set : 1; + guint show_on_next_swap : 1; }; struct _GdkMacosSurfaceClass @@ -76,71 +82,70 @@ struct _GdkMacosSurfaceClass GdkSurfaceClass parent_class; }; -GdkMacosSurface *_gdk_macos_surface_new (GdkMacosDisplay *display, - GdkSurfaceType surface_type, - GdkSurface *parent, - int x, - int y, - int width, - int height); -NSWindow *_gdk_macos_surface_get_native (GdkMacosSurface *self); -CGDirectDisplayID _gdk_macos_surface_get_screen_id (GdkMacosSurface *self); -const char *_gdk_macos_surface_get_title (GdkMacosSurface *self); -void _gdk_macos_surface_set_title (GdkMacosSurface *self, - const char *title); -void _gdk_macos_surface_get_shadow (GdkMacosSurface *self, - int *top, - int *right, - int *bottom, - int *left); -void _gdk_macos_surface_set_shadow (GdkMacosSurface *self, - int top, - int right, - int bottom, - int left); -NSView *_gdk_macos_surface_get_view (GdkMacosSurface *self); -gboolean _gdk_macos_surface_get_modal_hint (GdkMacosSurface *self); -void _gdk_macos_surface_set_modal_hint (GdkMacosSurface *self, - gboolean modal_hint); -void _gdk_macos_surface_set_geometry_hints (GdkMacosSurface *self, - const GdkGeometry *geometry, - GdkSurfaceHints geom_mask); -void _gdk_macos_surface_resize (GdkMacosSurface *self, - 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, - gint64 predicted_presentation_time, - gint64 refresh_interval); -CGContextRef _gdk_macos_surface_acquire_context (GdkMacosSurface *self, - gboolean clear_scale, - gboolean antialias); -void _gdk_macos_surface_release_context (GdkMacosSurface *self, - CGContextRef cg_context); -void _gdk_macos_surface_synthesize_null_key (GdkMacosSurface *self); -void _gdk_macos_surface_move (GdkMacosSurface *self, - int x, - int y); -void _gdk_macos_surface_move_resize (GdkMacosSurface *self, - int x, - int y, - int width, - int height); +GdkMacosSurface *_gdk_macos_surface_new (GdkMacosDisplay *display, + GdkSurfaceType surface_type, + GdkSurface *parent, + int x, + int y, + int width, + int height); +NSWindow *_gdk_macos_surface_get_native (GdkMacosSurface *self); +CGDirectDisplayID _gdk_macos_surface_get_screen_id (GdkMacosSurface *self); +const char *_gdk_macos_surface_get_title (GdkMacosSurface *self); +void _gdk_macos_surface_set_title (GdkMacosSurface *self, + const char *title); +void _gdk_macos_surface_get_shadow (GdkMacosSurface *self, + int *top, + int *right, + int *bottom, + int *left); +void _gdk_macos_surface_set_shadow (GdkMacosSurface *self, + int top, + int right, + int bottom, + int left); +gboolean _gdk_macos_surface_is_opaque (GdkMacosSurface *self); +NSView *_gdk_macos_surface_get_view (GdkMacosSurface *self); +gboolean _gdk_macos_surface_get_modal_hint (GdkMacosSurface *self); +void _gdk_macos_surface_set_modal_hint (GdkMacosSurface *self, + gboolean modal_hint); +void _gdk_macos_surface_set_geometry_hints (GdkMacosSurface *self, + const GdkGeometry *geometry, + GdkSurfaceHints geom_mask); +void _gdk_macos_surface_resize (GdkMacosSurface *self, + int width, + int height); +void _gdk_macos_surface_update_fullscreen_state (GdkMacosSurface *self); +void _gdk_macos_surface_update_position (GdkMacosSurface *self); +void _gdk_macos_surface_show (GdkMacosSurface *self); +void _gdk_macos_surface_publish_timings (GdkMacosSurface *self, + gint64 predicted_presentation_time, + gint64 refresh_interval); +void _gdk_macos_surface_synthesize_null_key (GdkMacosSurface *self); +void _gdk_macos_surface_move (GdkMacosSurface *self, + int x, + int y); +void _gdk_macos_surface_move_resize (GdkMacosSurface *self, + int x, + int y, + int width, + int height); void _gdk_macos_surface_configure (GdkMacosSurface *self); void _gdk_macos_surface_user_resize (GdkMacosSurface *self, CGRect new_frame); -gboolean _gdk_macos_surface_is_tracking (GdkMacosSurface *self, - NSTrackingArea *area); -void _gdk_macos_surface_monitor_changed (GdkMacosSurface *self); -GdkMonitor *_gdk_macos_surface_get_best_monitor (GdkMacosSurface *self); -void _gdk_macos_surface_reposition_children (GdkMacosSurface *self); -void _gdk_macos_surface_set_opacity (GdkMacosSurface *self, - double opacity); -void _gdk_macos_surface_get_root_coords (GdkMacosSurface *self, - int *x, - int *y); -gboolean _gdk_macos_surface_is_opaque (GdkMacosSurface *self); +gboolean _gdk_macos_surface_is_tracking (GdkMacosSurface *self, + NSTrackingArea *area); +void _gdk_macos_surface_monitor_changed (GdkMacosSurface *self); +GdkMonitor *_gdk_macos_surface_get_best_monitor (GdkMacosSurface *self); +void _gdk_macos_surface_reposition_children (GdkMacosSurface *self); +void _gdk_macos_surface_set_opacity (GdkMacosSurface *self, + double opacity); +void _gdk_macos_surface_get_root_coords (GdkMacosSurface *self, + int *x, + int *y); +GdkMacosBuffer *_gdk_macos_surface_get_buffer (GdkMacosSurface *self); +void _gdk_macos_surface_swap_buffers (GdkMacosSurface *self, + const cairo_region_t *damage); G_END_DECLS diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c index ded5c19189..b49f4bb333 100644 --- a/gdk/macos/gdkmacossurface.c +++ b/gdk/macos/gdkmacossurface.c @@ -23,7 +23,7 @@ #include #include -#import "GdkMacosCairoView.h" +#import "GdkMacosView.h" #include "gdkmacossurface-private.h" @@ -63,6 +63,7 @@ window_is_fullscreen (GdkMacosSurface *self) return ([self->window styleMask] & NSWindowStyleMaskFullScreen) != 0; } + void _gdk_macos_surface_reposition_children (GdkMacosSurface *self) { @@ -122,9 +123,8 @@ gdk_macos_surface_set_opaque_region (GdkSurface *surface, self->opaque_region = cairo_region_copy (region); } - if ((nsview = _gdk_macos_surface_get_view (GDK_MACOS_SURFACE (surface))) && - GDK_IS_MACOS_CAIRO_VIEW (nsview)) - [(GdkMacosCairoView *)nsview setOpaqueRegion:region]; + if ((nsview = _gdk_macos_surface_get_view (GDK_MACOS_SURFACE (surface)))) + [(GdkMacosView *)nsview setOpaqueRegion:region]; } static void @@ -133,12 +133,16 @@ gdk_macos_surface_hide (GdkSurface *surface) GdkMacosSurface *self = (GdkMacosSurface *)surface; GdkSeat *seat; gboolean was_mapped; + gboolean was_key; g_assert (GDK_IS_MACOS_SURFACE (self)); + 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]; seat = gdk_display_get_default_seat (surface->display); gdk_seat_ungrab (seat); @@ -147,6 +151,20 @@ gdk_macos_surface_hide (GdkSurface *surface) _gdk_surface_clear_update_area (surface); + g_clear_object (&self->buffer); + g_clear_object (&self->front); + + if (was_key) + { + /* Return key input to the parent window if necessary */ + if (surface->parent != NULL && GDK_SURFACE_IS_MAPPED (surface->parent)) + { + GdkMacosWindow *parentWindow = GDK_MACOS_SURFACE (surface->parent)->window; + + [parentWindow showAndMakeKey:YES]; + } + } + if (was_mapped) gdk_surface_freeze_updates (GDK_SURFACE (self)); } @@ -410,6 +428,9 @@ gdk_macos_surface_destroy (GdkSurface *surface, g_clear_pointer (&self->monitors, g_ptr_array_unref); + g_clear_object (&self->buffer); + g_clear_object (&self->front); + g_assert (self->sorted.prev == NULL); g_assert (self->sorted.next == NULL); g_assert (self->frame.prev == NULL); @@ -763,6 +784,9 @@ _gdk_macos_surface_configure (GdkMacosSurface *self) surface->width = content_rect.size.width; surface->height = content_rect.size.height; + g_clear_object (&self->buffer); + g_clear_object (&self->front); + _gdk_surface_update_size (surface); gdk_surface_request_layout (surface); gdk_surface_invalidate_rect (surface, NULL); @@ -823,7 +847,7 @@ _gdk_macos_surface_show (GdkMacosSurface *self) _gdk_macos_display_clear_sorting (GDK_MACOS_DISPLAY (GDK_SURFACE (self)->display)); - [self->window showAndMakeKey:YES]; + self->show_on_next_swap = TRUE; if (!was_mapped) { @@ -833,51 +857,6 @@ _gdk_macos_surface_show (GdkMacosSurface *self) gdk_surface_thaw_updates (GDK_SURFACE (self)); } } - - [[self->window contentView] setNeedsDisplay:YES]; -} - -CGContextRef -_gdk_macos_surface_acquire_context (GdkMacosSurface *self, - gboolean clear_scale, - gboolean antialias) -{ - CGContextRef cg_context; - - g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL); - - if (GDK_SURFACE_DESTROYED (self)) - return NULL; - - if (!(cg_context = [[NSGraphicsContext currentContext] CGContext])) - return NULL; - - CGContextSaveGState (cg_context); - - if (!antialias) - CGContextSetAllowsAntialiasing (cg_context, antialias); - - if (clear_scale) - { - CGSize scale; - - scale = CGSizeMake (1.0, 1.0); - scale = CGContextConvertSizeToDeviceSpace (cg_context, scale); - - CGContextScaleCTM (cg_context, 1.0 / fabs (scale.width), 1.0 / fabs (scale.height)); - } - - return cg_context; -} - -void -_gdk_macos_surface_release_context (GdkMacosSurface *self, - CGContextRef cg_context) -{ - g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); - - CGContextRestoreGState (cg_context); - CGContextSetAllowsAntialiasing (cg_context, TRUE); } void @@ -1056,6 +1035,10 @@ _gdk_macos_surface_monitor_changed (GdkMacosSurface *self) g_object_unref (monitor); } + /* We need to create a new IOSurface for this monitor */ + g_clear_object (&self->buffer); + g_clear_object (&self->front); + _gdk_macos_surface_configure (self); gdk_surface_invalidate_rect (GDK_SURFACE (self), NULL); @@ -1139,3 +1122,66 @@ _gdk_macos_surface_get_root_coords (GdkMacosSurface *self, if (y) *y = out_y; } + +GdkMacosBuffer * +_gdk_macos_surface_get_buffer (GdkMacosSurface *self) +{ + g_return_val_if_fail (GDK_IS_MACOS_SURFACE (self), NULL); + + if (GDK_SURFACE_DESTROYED (self)) + return NULL; + + if (self->buffer == NULL) + { + /* Create replacement buffer. We always use 4-byte and 32-bit BGRA for + * our surface as that can work with both Cairo and GL. The GdkMacosTile + * handles opaque regions for the compositor, so using 3-byte/24-bit is + * not a necessary optimization. + */ + double scale = gdk_surface_get_scale_factor (GDK_SURFACE (self)); + guint width = GDK_SURFACE (self)->width * scale; + guint height = GDK_SURFACE (self)->height * scale; + + self->buffer = _gdk_macos_buffer_new (width, height, scale, 4, 32); + } + + return self->buffer; +} + +static void +_gdk_macos_surface_do_delayed_show (GdkMacosSurface *self) +{ + g_assert (GDK_IS_MACOS_SURFACE (self)); + + self->show_on_next_swap = FALSE; + [self->window showAndMakeKey:YES]; + gdk_surface_request_motion (GDK_SURFACE (self)); +} + +void +_gdk_macos_surface_swap_buffers (GdkMacosSurface *self, + const cairo_region_t *damage) +{ + GdkMacosBuffer *swap; + + g_return_if_fail (GDK_IS_MACOS_SURFACE (self)); + g_return_if_fail (damage != NULL); + + swap = self->buffer; + self->buffer = self->front; + self->front = swap; + + /* This code looks like it swaps buffers, but since the IOSurfaceRef + * appears to be retained on the other side, we really just ask all + * of the GdkMacosTile CALayer's to update their contents. + */ + [self->window swapBuffer:swap withDamage:damage]; + + /* We might have delayed actually showing the window until the buffer + * contents are ready to be displayed. Doing so ensures that we don't + * get a point where we might have invalid buffer contents before we + * have content to display to the user. + */ + if G_UNLIKELY (self->show_on_next_swap) + _gdk_macos_surface_do_delayed_show (self); +} diff --git a/gdk/macos/meson.build b/gdk/macos/meson.build index 3a10dbf944..86f20bd2ea 100644 --- a/gdk/macos/meson.build +++ b/gdk/macos/meson.build @@ -2,6 +2,7 @@ gdk_macos_sources = files([ 'edgesnapping.c', 'gdkdisplaylinksource.c', + 'gdkmacosbuffer.c', 'gdkmacoscairocontext.c', 'gdkmacosclipboard.c', 'gdkmacoscursor.c', @@ -20,11 +21,10 @@ gdk_macos_sources = files([ 'gdkmacosseat.c', 'gdkmacossurface.c', 'gdkmacostoplevelsurface.c', - 'GdkMacosBaseView.c', - 'GdkMacosCairoView.c', - 'GdkMacosCairoSubview.c', - 'GdkMacosGLView.c', + 'GdkMacosLayer.c', + 'GdkMacosTile.c', + 'GdkMacosView.c', 'GdkMacosWindow.c', ]) @@ -46,6 +46,7 @@ gdk_macos_frameworks = [ 'CoreVideo', 'CoreServices', 'Foundation', + 'IOSurface', 'OpenGL', 'QuartzCore', ] diff --git a/gsk/gl/gskglcommandqueue.c b/gsk/gl/gskglcommandqueue.c index 3af8c1eb09..b7640edf87 100644 --- a/gsk/gl/gskglcommandqueue.c +++ b/gsk/gl/gskglcommandqueue.c @@ -675,9 +675,9 @@ gsk_gl_command_queue_split_draw (GskGLCommandQueue *self) } void -gsk_gl_command_queue_clear (GskGLCommandQueue *self, - guint clear_bits, - const graphene_rect_t *viewport) +gsk_gl_command_queue_clear (GskGLCommandQueue *self, + guint clear_bits, + const graphene_rect_t *viewport) { GskGLCommandBatch *batch; @@ -750,11 +750,12 @@ static inline void apply_scissor (gboolean *state, guint framebuffer, const graphene_rect_t *scissor, - gboolean has_scissor) + gboolean has_scissor, + guint default_framebuffer) { g_assert (framebuffer != (guint)-1); - if (framebuffer != 0 || !has_scissor) + if (framebuffer != default_framebuffer || !has_scissor) { if (*state != FALSE) { @@ -935,15 +936,24 @@ gsk_gl_command_queue_sort_batches (GskGLCommandQueue *self) * @self: a `GskGLCommandQueue` * @surface_height: the height of the backing surface * @scale_factor: the scale factor of the backing surface - * #scissor: (nullable): the scissor clip if any + * @scissor: (nullable): the scissor clip if any + * @default_framebuffer: the default framebuffer id if not zero * * Executes all of the batches in the command queue. + * + * Typically, the scissor rect is only applied when rendering to the default + * framebuffer (zero in most cases). However, if @default_framebuffer is not + * zero, it will be checked to see if the rendering target matches so that + * the scissor rect is applied. This should be used in cases where rendering + * to the backbuffer for display is not the default GL framebuffer of zero. + * Currently, this happens when rendering on macOS using IOSurface. */ void gsk_gl_command_queue_execute (GskGLCommandQueue *self, guint surface_height, guint scale_factor, - const cairo_region_t *scissor) + const cairo_region_t *scissor, + guint default_framebuffer) { G_GNUC_UNUSED guint count = 0; graphene_rect_t scissor_test; @@ -1049,7 +1059,7 @@ gsk_gl_command_queue_execute (GskGLCommandQueue *self, case GSK_GL_COMMAND_KIND_CLEAR: if (apply_framebuffer (&framebuffer, batch->clear.framebuffer)) { - apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor); + apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor, default_framebuffer); n_fbos++; } @@ -1073,7 +1083,7 @@ gsk_gl_command_queue_execute (GskGLCommandQueue *self, if (apply_framebuffer (&framebuffer, batch->draw.framebuffer)) { - apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor); + apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor, default_framebuffer); n_fbos++; } diff --git a/gsk/gl/gskglcommandqueueprivate.h b/gsk/gl/gskglcommandqueueprivate.h index 4147283e6d..48911305a8 100644 --- a/gsk/gl/gskglcommandqueueprivate.h +++ b/gsk/gl/gskglcommandqueueprivate.h @@ -278,7 +278,8 @@ void gsk_gl_command_queue_end_frame (GskGLCommandQueue void gsk_gl_command_queue_execute (GskGLCommandQueue *self, guint surface_height, guint scale_factor, - const cairo_region_t *scissor); + const cairo_region_t *scissor, + guint default_framebuffer); int gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self, GdkTexture *texture, int min_filter, diff --git a/gsk/gl/gskglrenderjob.c b/gsk/gl/gskglrenderjob.c index 65b5adf84d..afefe7a018 100644 --- a/gsk/gl/gskglrenderjob.c +++ b/gsk/gl/gskglrenderjob.c @@ -123,6 +123,7 @@ struct _GskGLRenderJob * GL context. */ guint framebuffer; + guint default_framebuffer; /* The viewport we are using. This state is updated as we process render * nodes in the specific visitor callbacks. @@ -4058,7 +4059,7 @@ gsk_gl_render_job_render_flipped (GskGLRenderJob *job, gsk_gl_render_job_end_draw (job); gdk_gl_context_push_debug_group (job->command_queue->context, "Executing command queue"); - gsk_gl_command_queue_execute (job->command_queue, surface_height, 1, NULL); + gsk_gl_command_queue_execute (job->command_queue, surface_height, 1, NULL, job->default_framebuffer); gdk_gl_context_pop_debug_group (job->command_queue->context); glDeleteFramebuffers (1, &framebuffer_id); @@ -4108,7 +4109,7 @@ gsk_gl_render_job_render (GskGLRenderJob *job, start_time = GDK_PROFILER_CURRENT_TIME; gsk_gl_command_queue_make_current (job->command_queue); gdk_gl_context_push_debug_group (job->command_queue->context, "Executing command queue"); - gsk_gl_command_queue_execute (job->command_queue, surface_height, scale_factor, job->region); + gsk_gl_command_queue_execute (job->command_queue, surface_height, scale_factor, job->region, job->default_framebuffer); gdk_gl_context_pop_debug_group (job->command_queue->context); gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Execute GL command queue", ""); } @@ -4157,11 +4158,23 @@ gsk_gl_render_job_new (GskGLDriver *driver, const graphene_rect_t *clip_rect = viewport; graphene_rect_t transformed_extents; GskGLRenderJob *job; + GdkGLContext *context; + GLint default_framebuffer = 0; g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL); g_return_val_if_fail (viewport != NULL, NULL); g_return_val_if_fail (scale_factor > 0, NULL); + /* Check for non-standard framebuffer binding as we might not be using + * the default framebuffer on systems like macOS where we've bound an + * IOSurface to a GL_TEXTURE_RECTANGLE. Otherwise, no scissor clip will + * be applied in the command queue causing overdrawing. + */ + context = driver->command_queue->context; + default_framebuffer = GDK_GL_CONTEXT_GET_CLASS (context)->get_default_framebuffer (context); + if (framebuffer == 0 && default_framebuffer != 0) + framebuffer = default_framebuffer; + job = g_slice_new0 (GskGLRenderJob); job->driver = g_object_ref (driver); job->command_queue = job->driver->command_queue; @@ -4169,6 +4182,7 @@ gsk_gl_render_job_new (GskGLDriver *driver, job->modelview = g_array_sized_new (FALSE, FALSE, sizeof (GskGLRenderModelview), 16); job->framebuffer = framebuffer; job->clear_framebuffer = !!clear_framebuffer; + job->default_framebuffer = default_framebuffer; job->offset_x = 0; job->offset_y = 0; job->scale_x = scale_factor; diff --git a/gtk/gtkfilechoosernativequartz.c b/gtk/gtkfilechoosernativequartz.c index c004167a23..3854937c0d 100644 --- a/gtk/gtkfilechoosernativequartz.c +++ b/gtk/gtkfilechoosernativequartz.c @@ -300,7 +300,7 @@ filechooser_quartz_launch (FileChooserQuartzData *data) if (data->filters) { // when filters have been provided, a combobox needs to be added - data->filter_combo_box = [[NSComboBox alloc] initWithFrame:NSMakeRect(0.0, 0.0, 200, 20)]; + data->filter_combo_box = [[NSComboBox alloc] initWithFrame:NSMakeRect(0, 0, 200, 24)]; [data->filter_combo_box addItemsWithObjectValues:data->filter_names]; [data->filter_combo_box setEditable:NO]; [data->filter_combo_box setDelegate:[[FilterComboBox alloc] initWithData:data]];