Merge branch 'wip/chergert/macos-iosurface' into 'main'

macos: modernize rendering with CALayer and IOSurface

See merge request GNOME/gtk!4477
This commit is contained in:
Matthias Clasen 2022-02-23 03:10:43 +00:00
commit 2c88797195
26 changed files with 1642 additions and 1123 deletions

View File

@ -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;

View File

@ -71,6 +71,8 @@ struct _GdkGLContextClass
gboolean (* is_shared) (GdkGLContext *self,
GdkGLContext *other);
guint (* get_default_framebuffer) (GdkGLContext *self);
};
typedef struct {

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include <CoreGraphics/CoreGraphics.h>
#include <cairo-quartz.h>
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include <CoreGraphics/CoreGraphics.h>
#include <cairo-quartz.h>
#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

View File

@ -1,125 +0,0 @@
/* GdkMacosGLView.c
*
* Copyright 2020 Christian Hergert <chergert@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include <CoreGraphics/CoreGraphics.h>
#include <OpenGL/gl.h>
#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

386
gdk/macos/GdkMacosLayer.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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<CanSetContentsOpaque>)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

44
gdk/macos/GdkMacosLayer.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <QuartzCore/QuartzCore.h>
#include <IOSurface/IOSurface.h>
#include <cairo.h>
#include <glib.h>
#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

View File

@ -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 <cairo.h>
#include "config.h"
#import "GdkMacosBaseView.h"
#include <AppKit/AppKit.h>
#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<CanSetContentsChanged>)self setContentsChanged];
else
[self setContents:(id)buffer];
G_GNUC_END_IGNORE_DEPRECATIONS
if G_UNLIKELY (!CGRectEqualToRect ([self contentsRect], rect))
self.contentsRect = rect;
}
@end

View File

@ -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 <AppKit/AppKit.h>
#include <cairo.h>
#include <glib.h>
#include <QuartzCore/QuartzCore.h>
#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

86
gdk/macos/GdkMacosView.c Normal file
View File

@ -0,0 +1,86 @@
/* GdkMacosView.c
*
* Copyright 2022 Christian Hergert <chergert@redhat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include <CoreGraphics/CoreGraphics.h>
#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

View File

@ -1,7 +1,6 @@
/* GdkMacosCairoView.h
/* GdkMacosView.h
*
* Copyright © 2020 Red Hat, Inc.
* Copyright © 2005-2007 Imendio AB
* Copyright 2022 Christian Hergert <chergert@redhat.com>
*
* 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

View File

@ -24,8 +24,7 @@
#include <gdk/gdk.h>
#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<NSWindowDelegate>)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

View File

@ -21,9 +21,11 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#import <IOSurface/IOSurface.h>
#include <gdk/gdk.h>
#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

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef __GDK_MACOS_BUFFER_PRIVATE_H__
#define __GDK_MACOS_BUFFER_PRIVATE_H__
#include <CoreGraphics/CoreGraphics.h>
#include <Foundation/Foundation.h>
#include <IOSurface/IOSurface.h>
#include <cairo.h>
#include <glib-object.h>
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__ */

271
gdk/macos/gdkmacosbuffer.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include <IOSurface/IOSurface.h>
#include <Foundation/Foundation.h>
#include <OpenGL/CGLIOSurface.h>
#include <QuartzCore/QuartzCore.h>
#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;
}

View File

@ -22,19 +22,17 @@
#include "gdkconfig.h"
#include <cairo.h>
#include <QuartzCore/QuartzCore.h>
#include <CoreGraphics/CoreGraphics.h>
#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;

View File

@ -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);
}
}

View File

@ -29,7 +29,7 @@
#include "gdkmacossurface.h"
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl.h>
#import <OpenGL/gl3.h>
#import <AppKit/AppKit.h>
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__ */

View File

@ -19,20 +19,139 @@
#include "config.h"
#include "gdkconfig.h"
#include <OpenGL/gl3.h>
#include <OpenGL/CGLIOSurface.h>
#include <QuartzCore/QuartzCore.h>
#include "gdkmacosbuffer-private.h"
#include "gdkmacosglcontext-private.h"
#include "gdkmacossurface-private.h"
#include "gdkmacostoplevelsurface-private.h"
#include "gdkintl.h"
#include <OpenGL/gl.h>
#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 (&copy);
/* 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

View File

@ -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

View File

@ -23,7 +23,7 @@
#include <float.h>
#include <gdk/gdk.h>
#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);
}

View File

@ -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',
]

View File

@ -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++;
}

View File

@ -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,

View File

@ -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;

View File

@ -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]];