[gtk/wip/chergert/quartz4u: 85/116] macos: add opaque CALayer when drawing
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/chergert/quartz4u: 85/116] macos: add opaque CALayer when drawing
- Date: Wed, 24 Jun 2020 23:45:51 +0000 (UTC)
commit 363a4ccd2dd099bee572658c024af8cfb3894bab
Author: Christian Hergert <chergert redhat com>
Date: Wed Jun 3 13:33:10 2020 -0700
macos: add opaque CALayer when drawing
This helps us to avoid transparent blending by the display server
with CSD windows meaning faster composition times.
gdk/macos/GdkMacosBaseView.c | 11 +++
gdk/macos/GdkMacosBaseView.h | 1 +
gdk/macos/GdkMacosCairoSubview.c | 171 +++++++++++++++++++++++++++++++++++++++
gdk/macos/GdkMacosCairoSubview.h | 35 ++++++++
gdk/macos/GdkMacosCairoView.c | 123 ++++++++++++++--------------
gdk/macos/GdkMacosCairoView.h | 2 +-
gdk/macos/gdkmacossurface.c | 22 +++--
gdk/macos/meson.build | 1 +
8 files changed, 289 insertions(+), 77 deletions(-)
---
diff --git a/gdk/macos/GdkMacosBaseView.c b/gdk/macos/GdkMacosBaseView.c
index d94140579e..c6750dfdb9 100644
--- a/gdk/macos/GdkMacosBaseView.c
+++ b/gdk/macos/GdkMacosBaseView.c
@@ -70,6 +70,17 @@
return self;
}
+-(void)setNeedsDisplay:(BOOL)needsDisplay
+{
+ for (id child in [self subviews])
+ [child setNeedsDisplay:needsDisplay];
+}
+
+-(void)setOpaqueRegion:(cairo_region_t *)region
+{
+ /* Do nothing */
+}
+
-(BOOL)acceptsFirstMouse
{
return YES;
diff --git a/gdk/macos/GdkMacosBaseView.h b/gdk/macos/GdkMacosBaseView.h
index 44fc182f04..7fcfc7e43b 100644
--- a/gdk/macos/GdkMacosBaseView.h
+++ b/gdk/macos/GdkMacosBaseView.h
@@ -41,5 +41,6 @@
-(GdkMacosDisplay *)gdkDisplay;
-(void)setNeedsInvalidateShadow: (BOOL)invalidate;
-(NSTrackingArea *)trackingArea;
+-(void)setOpaqueRegion:(cairo_region_t *)region;
@end
diff --git a/gdk/macos/GdkMacosCairoSubview.c b/gdk/macos/GdkMacosCairoSubview.c
new file mode 100644
index 0000000000..425b52ac78
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoSubview.c
@@ -0,0 +1,171 @@
+/* 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>
+
+#include "gdkinternals.h"
+
+#import "GdkMacosCairoSubview.h"
+#import "GdkMacosCairoView.h"
+
+#include "gdkmacossurface-private.h"
+
+@implementation GdkMacosCairoSubview
+
+-(BOOL)isOpaque
+{
+ return _isOpaque;
+}
+
+-(BOOL)isFlipped
+{
+ return YES;
+}
+
+-(GdkSurface *)gdkSurface
+{
+ return GDK_SURFACE ([(GdkMacosBaseView *)[self superview] gdkSurface]);
+}
+
+-(void)drawRect:(NSRect)rect
+{
+ CGContextRef cgContext;
+ GdkSurface *gdk_surface;
+ cairo_surface_t *dest;
+ const NSRect *rects = NULL;
+ NSView *root_view;
+ NSInteger n_rects = 0;
+ NSRect abs_bounds;
+ cairo_t *cr;
+ CGSize scale;
+ int scale_factor;
+
+ if (self->cairoSurface == NULL)
+ return;
+
+ /* Acquire everything we need to do translations, drawing, etc */
+ gdk_surface = [self gdkSurface];
+ scale_factor = gdk_surface_get_scale_factor (gdk_surface);
+ root_view = [[self window] contentView];
+ cgContext = [[NSGraphicsContext currentContext] CGContext];
+ abs_bounds = [self convertRect:[self bounds] toView:root_view];
+
+ CGContextSaveGState (cgContext);
+
+ /* Translate scaling to remove HiDPI scaling from CGContext as
+ * cairo will be doing that for us already.
+ */
+ scale = CGSizeMake (1.0, 1.0);
+ scale = CGContextConvertSizeToDeviceSpace (cgContext, scale);
+ CGContextScaleCTM (cgContext, 1.0 / scale.width, 1.0 / scale.height);
+
+ /* Create the cairo surface to draw to the CGContext and translate
+ * coordinates so we can pretend we are in the same coordinate system
+ * as the GDK surface.
+ */
+ dest = cairo_quartz_surface_create_for_cg_context (cgContext,
+ gdk_surface->width * scale_factor,
+ gdk_surface->height * scale_factor);
+ cairo_surface_set_device_scale (dest, scale_factor, scale_factor);
+
+ /* Create cairo context and translate things into the origin of
+ * the topmost contentView so that we just draw at 0,0 with a
+ * clip region to paint the surface.
+ */
+ cr = cairo_create (dest);
+ cairo_translate (cr, -abs_bounds.origin.x, -abs_bounds.origin.y);
+
+ /* Clip the cairo context based on the rectangles to be drawn
+ * within the bounding box :rect.
+ */
+ [self getRectsBeingDrawn:&rects count:&n_rects];
+ for (NSInteger i = 0; i < n_rects; i++)
+ {
+ NSRect area = [self convertRect:rects[i] toView:root_view];
+ cairo_rectangle (cr,
+ area.origin.x, area.origin.y,
+ area.size.width, area.size.height);
+ }
+ cairo_clip (cr);
+
+ /* Now paint the surface (without blending) as we do not need
+ * any compositing here. The transparent regions (like shadows)
+ * are already on non-opaque layers.
+ */
+ cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+ cairo_set_source_surface (cr, self->cairoSurface, 0, 0);
+ cairo_paint (cr);
+
+ /* Cleanup state, flush the surface to the backing layer, and
+ * restore GState for future use.
+ */
+ cairo_destroy (cr);
+ cairo_surface_flush (dest);
+ cairo_surface_destroy (dest);
+ CGContextRestoreGState (cgContext);
+}
+
+-(void)setCairoSurface:(cairo_surface_t *)surface
+ withDamage:(cairo_region_t *)region
+{
+ if (surface != self->cairoSurface)
+ {
+ g_clear_pointer (&self->cairoSurface, cairo_surface_destroy);
+ if (surface != NULL)
+ self->cairoSurface = cairo_surface_reference (surface);
+ }
+
+ if (region != NULL)
+ {
+ NSView *root_view = [[self window] contentView];
+ NSRect abs_bounds = [self convertRect:[self bounds] toView:root_view];
+ 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);
+
+ if (NSIntersectsRect (abs_bounds, nsrect))
+ {
+ nsrect.origin.x -= abs_bounds.origin.x;
+ nsrect.origin.y -= abs_bounds.origin.y;
+ [self setNeedsDisplayInRect:nsrect];
+ }
+ }
+ }
+
+ for (id view in [self subviews])
+ [(GdkMacosCairoSubview *)view setCairoSurface:surface
+ withDamage:region];
+}
+
+-(void)setOpaque:(BOOL)opaque
+{
+ self->_isOpaque = opaque;
+}
+
+@end
diff --git a/gdk/macos/GdkMacosCairoSubview.h b/gdk/macos/GdkMacosCairoSubview.h
new file mode 100644
index 0000000000..9255347566
--- /dev/null
+++ b/gdk/macos/GdkMacosCairoSubview.h
@@ -0,0 +1,35 @@
+/* GdkMacosCairoSubview.h
+ *
+ * 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 <AppKit/AppKit.h>
+
+#define GDK_IS_MACOS_CAIRO_SUBVIEW(obj) ((obj) && [obj isKindOfClass:[GdkMacosCairoSubview class]])
+
+@interface GdkMacosCairoSubview : NSView
+{
+ BOOL _isOpaque;
+ cairo_surface_t *cairoSurface;
+}
+
+-(void)setOpaque:(BOOL)opaque;
+-(void)setCairoSurface:(cairo_surface_t *)cairoSurface
+ withDamage:(cairo_region_t *)region;
+
+@end
diff --git a/gdk/macos/GdkMacosCairoView.c b/gdk/macos/GdkMacosCairoView.c
index 8f049e71f5..06f1db44a8 100644
--- a/gdk/macos/GdkMacosCairoView.c
+++ b/gdk/macos/GdkMacosCairoView.c
@@ -27,17 +27,12 @@
#include "gdkinternals.h"
#import "GdkMacosCairoView.h"
+#import "GdkMacosCairoSubview.h"
#include "gdkmacossurface-private.h"
@implementation GdkMacosCairoView
--(void)dealloc
-{
- g_clear_pointer (&self->surface, cairo_surface_destroy);
- [super dealloc];
-}
-
-(BOOL)isOpaque
{
if ([self window])
@@ -53,76 +48,78 @@
-(void)setCairoSurface:(cairo_surface_t *)cairoSurface
withDamage:(cairo_region_t *)cairoRegion
{
- guint n_rects = cairo_region_num_rectangles (cairoRegion);
-
- if (self->surface == NULL)
- {
- [self setNeedsDisplay:YES];
- }
- else
- {
- for (guint i = 0; i < n_rects; i++)
- {
- cairo_rectangle_int_t rect;
-
- cairo_region_get_rectangle (cairoRegion, i, &rect);
- [self setNeedsDisplayInRect:NSMakeRect (rect.x, rect.y, rect.width, rect.height)];
- }
- }
-
- if (self->surface != cairoSurface)
- {
- g_clear_pointer (&self->surface, cairo_surface_destroy);
- self->surface = cairo_surface_reference (cairoSurface);
- }
+ for (id view in [self subviews])
+ [(GdkMacosCairoSubview *)view setCairoSurface:cairoSurface
+ withDamage:cairoRegion];
}
--(void)drawRect:(NSRect)rect
+-(void)removeOpaqueChildren
{
- GdkMacosSurface *gdkSurface;
- CGContextRef cg_context;
- cairo_surface_t *dest;
- const NSRect *rects = NULL;
- NSInteger n_rects = 0;
- cairo_t *cr;
- int scale_factor;
-
- if (self->surface == NULL)
- return;
+ [[self->transparent subviews]
+ makeObjectsPerformSelector:@selector(removeFromSuperview)];
+}
- gdkSurface = [self gdkSurface];
- cg_context = _gdk_macos_surface_acquire_context (gdkSurface, TRUE, TRUE);
- scale_factor = gdk_surface_get_scale_factor (GDK_SURFACE (gdkSurface));
+-(void)setOpaqueRegion:(cairo_region_t *)region
+{
+ NSRect abs_bounds;
+ guint n_rects;
- dest = cairo_quartz_surface_create_for_cg_context (cg_context,
- self.bounds.size.width * scale_factor,
- self.bounds.size.height * scale_factor);
- cairo_surface_set_device_scale (dest, scale_factor, scale_factor);
+ [self removeOpaqueChildren];
- cr = cairo_create (dest);
+ if (region == NULL)
+ return;
- cairo_set_source_surface (cr, self->surface, 0, 0);
+ abs_bounds = [self convertRect:[self bounds] toView:nil];
+ n_rects = cairo_region_num_rectangles (region);
+ 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];
+ }
+}
- [self getRectsBeingDrawn:&rects count:&n_rects];
- for (NSInteger i = 0; i < n_rects; i++)
+-(NSView *)initWithFrame:(NSRect)frame
+{
+ if ((self = [super initWithFrame:frame]))
{
- const NSRect *r = &rects[i];
- cairo_rectangle (cr,
- r->origin.x,
- r->origin.y,
- r->size.width,
- r->size.height);
+ /* 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];
}
- cairo_clip (cr);
- cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
- cairo_paint (cr);
+ return self;
+}
+
+-(void)setFrame:(NSRect)rect
+{
+ [super setFrame:rect];
+ [self->transparent setFrame:NSMakeRect (0, 0, rect.size.width, rect.size.height)];
+}
- cairo_destroy (cr);
- cairo_surface_flush (dest);
- cairo_surface_destroy (dest);
+-(BOOL)acceptsFirstMouse
+{
+ return YES;
+}
- _gdk_macos_surface_release_context (gdkSurface, cg_context);
+-(BOOL)mouseDownCanMoveWindow
+{
+ return NO;
}
@end
diff --git a/gdk/macos/GdkMacosCairoView.h b/gdk/macos/GdkMacosCairoView.h
index c4190ff059..913c24e399 100644
--- a/gdk/macos/GdkMacosCairoView.h
+++ b/gdk/macos/GdkMacosCairoView.h
@@ -27,7 +27,7 @@
@interface GdkMacosCairoView : GdkMacosBaseView
{
- cairo_surface_t *surface;
+ NSView *transparent;
}
-(void)setCairoSurface:(cairo_surface_t *)cairoSurface
diff --git a/gdk/macos/gdkmacossurface.c b/gdk/macos/gdkmacossurface.c
index d42ca3aa40..221390ae2b 100644
--- a/gdk/macos/gdkmacossurface.c
+++ b/gdk/macos/gdkmacossurface.c
@@ -94,19 +94,15 @@ static void
gdk_macos_surface_set_opaque_region (GdkSurface *surface,
cairo_region_t *region)
{
- /* TODO:
- *
- * For CSD, we have a NSWindow with isOpaque:NO. There is some performance
- * penalty for this because the compositor will have to composite every pixel
- * intead of simply taking the source pixel.
- *
- * To improve this when using Cairo, we could have a number of child NSView
- * which are opaque, and simply draw from the underlying Cairo surface for
- * each view. Then only the corners and shadow would have non-opaque regions.
- *
- * However, as we intend to move to a OpenGL (or Metal) renderer, the effort
- * here may not be worth the time.
- */
+ NSView *nsview;
+
+ g_assert (GDK_IS_MACOS_SURFACE (surface));
+
+ if (!(nsview = _gdk_macos_surface_get_view (GDK_MACOS_SURFACE (surface))) ||
+ !GDK_IS_MACOS_CAIRO_VIEW (nsview))
+ return;
+
+ [(GdkMacosCairoView *)nsview setOpaqueRegion:region];
}
static void
diff --git a/gdk/macos/meson.build b/gdk/macos/meson.build
index cecbdbe97f..e534e1f581 100644
--- a/gdk/macos/meson.build
+++ b/gdk/macos/meson.build
@@ -20,6 +20,7 @@ gdk_macos_sources = files([
'GdkMacosBaseView.c',
'GdkMacosCairoView.c',
+ 'GdkMacosCairoSubview.c',
# 'GdkMacosGLView.c',
])
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]