forked from AuroraMiddleware/gtk
macos: use CALayer and IOSurface for rendering
This provides a major shift in how we draw both when accelerated OpenGL as well as software rendering with Cairo. In short, it uses tiles of Core Animation's CALayer to display contents from an OpenGL or Cairo rendering so that the window can provide partial damage updates. Partial damage is not generally available when using OpenGL as the whole buffer is flipped even if you only submitted a small change using a scissor rect. Thankfully, this speeds up Cairo rendering a bit too by using IOSurface to upload contents to the display server. We use the tiling system we do for OpenGL which reduces overall complexity and differences between them. A New Buffer ============ GdkMacosBuffer is a wrapper around an IOSurfaceRef. The term buffer was used because 1) surface is already used and 2) it loosely maps to a front/back buffer semantic. However, it appears that IOSurfaceRef contents are being retained in some fashion (likely in the compositor result) so we can update the same IOSurfaceRef without flipping as long as we're fast. This appears to be what Chromium does as well, but Firefox uses two IOSurfaceRef and flips between them. We would like to avoid two surfaces because it doubles the GPU VRAM requirements of the application. Changes to Windows ================== Previously, the NSWindow would dynamically change between different types of NSView based on the renderer being used. This is no longer necessary as we just have a single NSView type, GdkMacosView, which inherits from GdkMacosBaseView just to keep the tedius stuff separate from the machinery of GdkMacosView. We can merge those someday if we are okay with that. Changes to Views ================ GdkMacosCairoView, GdkMacosCairoSubView, GdkMacosGLView have all been removed and replaced with GdkMacosView. This new view has a single CALayer (GdkMacosLayer) attached to it which itself has sublayers. The contents of the CALayer is populated with an IOSurfaceRef which we allocated with the GdkMacosSurface. The surface is replaced when the NSWindow resizes. Changes to Layers ================= We now have a dedicated GdkMacosLayer which contains sublayers of GdkMacosTile. The tile has a maximum size of 128x128 pixels in device units. The GdkMacosTile is partitioned by splitting both the transparent region (window bounds minus opaque area) and then by splitting the opaque area. A tile has either translucent contents (and therefore is not opaque) or has opaque contents (and therefore is opaque). An opaque tile never contains transparent contents. As such, the opaque tiles contain a black background so that Core Animation will consider the tile's bounds as opaque. This can be verified with "Quartz Debug -> Show opaque regions". Changes to Cairo ================ GTK 4 cannot currently use cairo-quartz because of how CSS borders are rendered. It simply causes errors in the cairo_quartz_surface_t backend. Since we are restricted to using cairo_image_surface_t (which happens to be faster anyway) we can use the IOSurfaceBaseAddress() to obtain a mapping of the IOSurfaceRef in user-space. It always uses BGRA 32-bit with alpha channel even if we will discard the alpha channel as that is necessary to hit the fast paths in other parts of the platform. Note that while Cairo says CAIRO_FORMAT_ARGB32, it is really 32-bit BGRA on little-endian as we expect. OpenGL will render flipped (Quartz Native Co-ordinates) while Cairo renders with 0,O in the top-left. We could use cairo_translate() and cairo_scale() to reverse this, but it looks like some cairo things may not look quite as right if we do so. To reduce the chances of one-off bugs this continues to draw as Cairo would normally, but instead uses an CGAffineTransform in the tiles and some CGRect translation when swapping buffers to get the same effect. Changes to OpenGL ================= To simplify things, removal of all NSOpenGL* related components have been removed and we strictly use the Core GL (CGL*) API. This probably should have been done long ago anyay. Most examples found in the browsers to use IOSurfaceRef with OpenGL are using Legacy GL and there is still work underway to make this fit in with the rest of how the GSK GL renderer works. Since IOSurfaceRef bound to a texture/framebuffer will not have a default framebuffer ID of 0, we needed to add a default framebuffer id to the GdkGLContext. GskGLRenderer can use this to setup the command queue in such a way that our IOSurface destination has been glBindFramebuffer() as if it were the default drawable. This stuff is pretty slight-of-hand, so where things are and what needs flushing when and where has been a bit of an experiment to see what actually works to get synchronization across subsystems. Efficient Damages ================= After we draw with Cairo, we unlock the IOSurfaceRef and the contents are uploaded to the GPU. To make the contents visible to the app, we must clear the tiles contents with `layer.contents=nil;` and then re-apply the IOSurfaceRef. Since the buffer has likely not changed, we only do this if the tile overlaps the damage region. This gives the effect of having more tightly controlled damage regions even though updating the layer would damage be the whole window (as it is with OpenGL/Metal today with the exception of scissor-rect). This too can be verified usign "Quartz Debug -> Flash screen udpates". Frame Synchronized Resize ========================= In GTK 4, we have the ability to perform sizing changes from compute-size during the layout phase. Since the macOS backend already tracks window resizes manually, we can avoid doing the setFrame: immediately and instead do it within the frame clock's layout phase. Doing so gives us vastly better resize experience as we're more likely to get the size-change and updated-contents in the same frame on screen. It makes things feel "connected" in a way they weren't before. Some additional effort to tweak gravity during the process is also necessary but we were already doing that in the GTK 4 backend. Backporting =========== The design here has made an attempt to make it possible to backport by keeping GdkMacosBuffer, GdkMacosLayer, and GdkMacosTile fairly independent. There may be an opportunity to integrate this into GTK 3's quartz backend with a fair bit of work. Doing so could improve the situation for applications which are damage-rich such as The GIMP.
This commit is contained in:
parent
f9268e8137
commit
8b71cff71d
@ -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
|
@ -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
|
@ -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
|
385
gdk/macos/GdkMacosLayer.c
Normal file
385
gdk/macos/GdkMacosLayer.c
Normal file
@ -0,0 +1,385 @@
|
||||
/* 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 (CGRectEqualToRect (frame, self.frame))
|
||||
return;
|
||||
|
||||
self->_layoutInvalid = TRUE;
|
||||
|
||||
[super setFrame:frame];
|
||||
[self setNeedsLayout];
|
||||
}
|
||||
|
||||
-(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
44
gdk/macos/GdkMacosLayer.h
Normal 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
|
@ -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
|
@ -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
86
gdk/macos/GdkMacosView.c
Normal 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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
58
gdk/macos/gdkmacosbuffer-private.h
Normal file
58
gdk/macos/gdkmacosbuffer-private.h
Normal 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
271
gdk/macos/gdkmacosbuffer.c
Normal 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;
|
||||
}
|
@ -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;
|
||||
|
@ -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__ */
|
||||
|
@ -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,163 @@ 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 */
|
||||
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 +355,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 +486,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,8 +529,6 @@ 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);
|
||||
}
|
||||
|
||||
@ -410,22 +536,13 @@ 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 +553,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 +583,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 +636,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 +644,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
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
#include "gdksurfaceprivate.h"
|
||||
|
||||
#include "gdkmacosbuffer-private.h"
|
||||
#include "gdkmacosdisplay.h"
|
||||
#include "gdkmacossurface.h"
|
||||
|
||||
@ -45,8 +46,8 @@ struct _GdkMacosSurface
|
||||
GList frame;
|
||||
|
||||
GdkMacosWindow *window;
|
||||
GdkMacosBuffer *buffer;
|
||||
GPtrArray *monitors;
|
||||
cairo_region_t *input_region;
|
||||
cairo_region_t *opaque_region;
|
||||
char *title;
|
||||
|
||||
@ -65,10 +66,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 +81,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
|
||||
|
||||
|
@ -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
|
||||
@ -136,6 +136,8 @@ gdk_macos_surface_hide (GdkSurface *surface)
|
||||
|
||||
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));
|
||||
@ -147,6 +149,8 @@ gdk_macos_surface_hide (GdkSurface *surface)
|
||||
|
||||
_gdk_surface_clear_update_area (surface);
|
||||
|
||||
g_clear_object (&self->buffer);
|
||||
|
||||
if (was_mapped)
|
||||
gdk_surface_freeze_updates (GDK_SURFACE (self));
|
||||
}
|
||||
@ -763,6 +767,8 @@ _gdk_macos_surface_configure (GdkMacosSurface *self)
|
||||
surface->width = content_rect.size.width;
|
||||
surface->height = content_rect.size.height;
|
||||
|
||||
g_clear_object (&self->buffer);
|
||||
|
||||
_gdk_surface_update_size (surface);
|
||||
gdk_surface_request_layout (surface);
|
||||
gdk_surface_invalidate_rect (surface, NULL);
|
||||
@ -823,7 +829,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 +839,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
|
||||
@ -1139,3 +1100,60 @@ _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)
|
||||
{
|
||||
g_return_if_fail (GDK_IS_MACOS_SURFACE (self));
|
||||
g_return_if_fail (damage != NULL);
|
||||
|
||||
/* 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:self->buffer 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);
|
||||
}
|
||||
|
@ -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',
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user