[gtk/wip/chergert/quartz4u: 85/116] macos: add opaque CALayer when drawing



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]