Subject: bug#32932: 27.0.50; render bugs on macOS Mojave

From: Alan Third <[email-masked]>
Date: Mon, 10 Feb 2020 07:44:04 +0000

On Sat, Feb 08, 2020 at 08:45:25AM -0800, Aaron Jensen wrote:
> 
> Yeah, unfortunately. Are you on Catalina? I can try the build on my
> work machine as well. It's a 2019 MBP 16", so pretty close to my 2017
> at home, but different. A hardware issue seems unlikely...

No, I’m still on Mojave.

Anyway, one last thing to try. I was reading up on how MacVim handled
this same issue, and while their drawing techniques are quite
different, they did find performance problems with CGLayers so
switched to using CGImages. I’ve given that a go and I don’t think
this patch is quite as fast here as the CGLayer one, but it may be
faster for you.

-- 
Alan Third
>From 2b514b8dfac196a7e19ccb4718f3b40afd09d8e0 Mon Sep 17 00:00:00 2001
From: Alan Third <[email-masked]>
Date: Sat, 1 Feb 2020 21:17:29 +0000
Subject: [PATCH] Use CGImage instead of NSBitmapImageRep (bug#32932)

---
 src/nsterm.h |   4 +-
 src/nsterm.m | 115 +++++++++++++++++++++++++++++----------------------
 2 files changed, 68 insertions(+), 51 deletions(-)

diff --git a/src/nsterm.h b/src/nsterm.h
index 980ca534cf..7c6197f128 100644
--- a/src/nsterm.h
+++ b/src/nsterm.h
@@ -418,7 +418,7 @@ #define NSTRACE_UNSILENCE()
    NSWindow *nonfs_window;
    BOOL fs_is_native;
 #ifdef NS_IMPL_COCOA
-   NSBitmapImageRep *drawingBuffer;
+   CGContextRef drawingBuffer;
 #endif
 @public
    struct frame *emacsframe;
@@ -464,7 +464,7 @@ #define NSTRACE_UNSILENCE()
 - (void)focusOnDrawingBuffer;
 #endif
 - (void)copyRect:(NSRect)srcRect to:(NSRect)dstRect;
-- (void)createDrawingBufferWithRect:(NSRect)rect;
+- (void)createDrawingBuffer;
 
 /* Non-notification versions of NSView methods. Used for direct calls.  */
 - (void)windowWillEnterFullScreen;
diff --git a/src/nsterm.m b/src/nsterm.m
index 9d427b9b38..8761f65da9 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -1141,7 +1141,6 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
 
 #ifdef NS_IMPL_COCOA
   [NSGraphicsContext setCurrentContext:nil];
-  [view display];
 #else
   block_input ();
 
@@ -2853,7 +2852,9 @@ so some key presses (TAB) are swallowed by the system.  */
   ns_unfocus (f);
 
   /* as of 2006/11 or so this is now needed */
-  ns_redraw_scroll_bars (f);
+  /* FIXME: I don't see any reason for this and removing it makes no
+     difference here.  Do we need it for GNUstep?  */
+  //ns_redraw_scroll_bars (f);
   unblock_input ();
 }
 
@@ -3169,18 +3170,6 @@ so some key presses (TAB) are swallowed by the system.  */
 
       NSTRACE_RECT ("fromRect", fromRect);
 
-      /* Because we're drawing into an offscreen buffer which isn't
-         flipped, the images come out upside down.  To work around it
-         we need to do some fancy transforms.  */
-      {
-        NSAffineTransform *transform = [NSAffineTransform transform];
-        [transform translateXBy:0 yBy:NSMaxY(imageRect)];
-        [transform scaleXBy:1 yBy:-1];
-        [transform concat];
-
-        imageRect.origin.y = 0;
-      }
-
       [img drawInRect: imageRect
              fromRect: fromRect
             operation: NSCompositingOperationSourceOver
@@ -3938,11 +3927,6 @@ Function modeled after x_draw_glyph_string_box ().
 
       NSAffineTransform *doTransform = [NSAffineTransform transform];
 
-      /* We have to flip the image around the X axis as the offscreen
-         bitmap we're drawing to is flipped.  */
-      [doTransform scaleXBy:1 yBy:-1];
-      [doTransform translateXBy:0 yBy:-[img size].height];
-
       /* ImageMagick images don't have transforms.  */
       if (img->transform)
         [doTransform appendTransform:img->transform];
@@ -4838,7 +4822,7 @@ in certain situations (rapid incoming events).
   if (NILP (window->vertical_scroll_bar))
     {
       if (width > 0 && height > 0)
-	ns_clear_frame_area (f, left, top, width, height);
+        ns_clear_frame_area (f, left, top, width, height);
 
       bar = [[EmacsScroller alloc] initFrame: r window: win];
       wset_vertical_scroll_bar (window, make_mint_ptr (bar));
@@ -7104,7 +7088,7 @@ - (void) updateFrameSize: (BOOL) delay
          from non-native fullscreen, in other circumstances it appears
          to be a noop.  (bug#28872) */
       wr = NSMakeRect (0, 0, neww, newh);
-      [self createDrawingBufferWithRect:wr];
+      [self createDrawingBuffer];
       [view setFrame: wr];
 
       // To do: consider using [NSNotificationCenter postNotificationName:].
@@ -7444,7 +7428,8 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f
   maximizing_resize = NO;
 #endif
 
-  [self createDrawingBufferWithRect:r];
+  [self setCanDrawConcurrently:YES];
+  [self createDrawingBuffer];
 
   win = [[EmacsWindow alloc]
             initWithContentRect: r
@@ -8229,20 +8214,28 @@ - (instancetype)toggleToolbar: (id)sender
 }

-- (void)createDrawingBufferWithRect:(NSRect)rect
-  /* Create and store a new NSBitmapImageRep for Emacs to draw
-     into.
+- (void)createDrawingBuffer
+  /* Create and store a new CGLayer for Emacs to draw into.
 
-     Drawing to an offscreen bitmap doesn't work in GNUstep as there's
-     a bug in graphicsContextWithBitmapImageRep
-     (https://savannah.gnu.org/bugs/?38405).  So under GNUstep we
-     retain the old method of drawing direct to the EmacsView.  */
+     We can't do this in GNUstep as there's no CGLayer or equivalent.
+     So under GNUstep we retain the old method of drawing direct to
+     the EmacsView.  */
 {
 #ifdef NS_IMPL_COCOA
+  NSGraphicsContext *screen;
+  CGFloat scale = [[self window] backingScaleFactor];
+  NSRect frame = [self frame];
+
   if (drawingBuffer != nil)
-    [drawingBuffer release];
+    CGContextRelease (drawingBuffer);
+
+  drawingBuffer = CGBitmapContextCreate (nil, NSWidth (frame) * scale,
+                                           NSHeight (frame) * scale,
+                                           8, 0, self.window.colorSpace.CGColorSpace,
+                                         kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
 
-  drawingBuffer = [[self bitmapImageRepForCachingDisplayInRect:rect] retain];
+  CGContextTranslateCTM(drawingBuffer, 0, NSHeight (frame) * scale);
+  CGContextScaleCTM(drawingBuffer, scale, -scale);
 #endif
 }
 
@@ -8250,11 +8243,11 @@ - (void)createDrawingBufferWithRect:(NSRect)rect
 #ifdef NS_IMPL_COCOA
 - (void)focusOnDrawingBuffer
 {
-  /* Creating the graphics context each time is very slow, but it
-     doesn't seem possible to cache and reuse it.  */
-  [NSGraphicsContext
-    setCurrentContext:
-      [NSGraphicsContext graphicsContextWithBitmapImageRep:drawingBuffer]];
+  NSGraphicsContext *buf =
+    [NSGraphicsContext
+        graphicsContextWithCGContext:drawingBuffer flipped:YES];
+
+  [NSGraphicsContext setCurrentContext:buf];
 }

@@ -8269,7 +8262,7 @@ - (void)windowDidChangeBackingProperties:(NSNotification *)notification
    if (old != new)
      {
        NSRect frame = [self frame];
-       [self createDrawingBufferWithRect:frame];
+       [self createDrawingBuffer];
        ns_clear_frame (emacsframe);
        expose_frame (emacsframe, 0, 0, NSWidth (frame), NSHeight (frame));
      }
@@ -8284,13 +8277,27 @@ - (void)copyRect:(NSRect)srcRect to:(NSRect)dstRect
   NSTRACE_RECT ("Destination", dstRect);
 
 #ifdef NS_IMPL_COCOA
-  [drawingBuffer drawInRect:dstRect
-                   fromRect:srcRect
-                  operation:NSCompositingOperationCopy
-                   fraction:1.0
-             respectFlipped:NO
-                      hints:nil];
+  CGImageRef copy;
+  NSAffineTransform *setOrigin = [NSAffineTransform transform];
+  NSRect frame = [self frame];
+  CGFloat xoffset = NSMinX (dstRect) - NSMinX (srcRect);
+  CGFloat yoffset = NSMinY (dstRect) - NSMinY (srcRect);
+
+  [[NSGraphicsContext currentContext] saveGraphicsState];
+
+  NSRectClip (dstRect);
 
+  [setOrigin scaleXBy:1 yBy:-1];
+  [setOrigin translateXBy:xoffset
+                      yBy:-NSHeight (frame) - yoffset];
+  [setOrigin concat];
+
+  copy = CGBitmapContextCreateImage (drawingBuffer);
+  CGContextDrawImage (drawingBuffer, frame, copy);
+
+  CGImageRelease (copy);
+
+  [[NSGraphicsContext currentContext] restoreGraphicsState];
   [self setNeedsDisplayInRect:dstRect];
 #else
   hide_bell();              // Ensure the bell image isn't scrolled.
@@ -8304,6 +8311,20 @@ - (void)copyRect:(NSRect)srcRect to:(NSRect)dstRect
 }

+- (BOOL)wantsUpdateLayer
+{
+    return YES;
+}
+
+
+- (void)updateLayer
+{
+  CGImageRef contentsImage = CGBitmapContextCreateImage(drawingBuffer);
+  self.layer.contents = (id)contentsImage;
+  CGImageRelease(contentsImage);
+}
+
+
 - (void)drawRect: (NSRect)rect
 {
   NSTRACE ("[EmacsView drawRect:" NSTRACE_FMT_RECT "]",
@@ -8313,12 +8334,8 @@ - (void)drawRect: (NSRect)rect
     return;
 
 #ifdef NS_IMPL_COCOA
-  [drawingBuffer drawInRect:rect
-                   fromRect:rect
-                  operation:NSCompositingOperationSourceOver
-                   fraction:1
-             respectFlipped:NO
-                      hints:nil];
+  // CGContextRef ctx = [[NSGraphicsContext currentContext] CGContext];
+  // CGContextDrawLayerInRect (ctx, NSRectToCGRect ([self frame]), drawingBuffer);
 #else
   int x = NSMinX (rect), y = NSMinY (rect);
   int width = NSWidth (rect), height = NSHeight (rect);
-- 
2.24.0