Subject: Re: [PATCH v2] Fix more drawing bugs in NS port (bug#32932)

From: Alan Third <[email-masked]>
Date: Tue, 13 Nov 2018 22:13:36 +0000

On Fri, Nov 09, 2018 at 06:15:24AM -0800, Aaron Jensen wrote:
> 
> I’ve twice seen a full frame blank. The first time was when pasting
> something using cmd+v (it only happened the once) and the second time,
> I don’t recall what I was doing. The first time it repainted the
> entire frame with its two windows fine. The second time it repainted
> the cursor on the inactive window and a seemingly random rectangle on
> the active window that spanned several lines down below the cursor and
> half the window’s width to the right.

OK, so all new patch. It should apply to emacs-26 without any
additional patches (and might be OK for master too). Just the usual
’git am’ should apply all four, I believe.

If you can ignore the outer border randomly drawing in the wrong
colour, images being upside down and live‐resize displaying the frame
in black, then you’ll probably get along pretty well with this.

(There’s also a slightly odd redrawing issue with the title and tool
bars when dragging between monitors. No idea why that’s happening.)

Additionally, the fonts are drawn differently. I imagine that’s the
lack of subpixel anti‐aliasing that Yamamoto Mitsuharu talks about in
the Mac port release notes, in which case you won’t notice anything on
10.14.
-- 
Alan Third
>From b62bf4e329b106c08311456059f464ab44092a7f Mon Sep 17 00:00:00 2001
From: Alan Third <[email-masked]>
Date: Mon, 12 Nov 2018 11:28:27 +0000
Subject: [PATCH 1/4] Revert "Fix some NS drawing issues (bug#32932)"

This reverts commit 7e8eee60a9dbb0c59cf26f237b21efe7fd1043c9.
---
 src/nsterm.m | 80 ++++++++++++++++++++++++++++------------------------
 1 file changed, 43 insertions(+), 37 deletions(-)

diff --git a/src/nsterm.m b/src/nsterm.m
index 4b5d025ee3..8c355a89f8 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -277,6 +277,7 @@ - (NSColor *)colorUsingDefaultColorSpace
 
 /* display update */
 static int ns_window_num = 0;
+static BOOL gsaved = NO;
 static BOOL ns_fake_keydown = NO;
 #ifdef NS_IMPL_COCOA
 static BOOL ns_menu_bar_is_hidden = NO;
@@ -1179,6 +1180,7 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
             NSRectClipList (r, 2);
           else
             NSRectClip (*r);
+          gsaved = YES;
 
           return YES;
         }
@@ -1202,7 +1204,11 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
 {
   NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "ns_reset_clipping");
 
-  [[NSGraphicsContext currentContext] restoreGraphicsState];
+  if (gsaved)
+    {
+      [[NSGraphicsContext currentContext] restoreGraphicsState];
+      gsaved = NO;
+    }
 }

@@ -1228,6 +1234,19 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
   return ns_clip_to_rect (f, &clip_rect, 1);
 }
 
+
+static void
+ns_flush_display (struct frame *f)
+/* Force the frame to redisplay.  If areas have previously been marked
+   dirty by setNeedsDisplayInRect (in ns_clip_to_rect), then this will call
+   draw_rect: which will "expose" those areas.  */
+{
+  block_input ();
+  [FRAME_NS_VIEW (f) displayIfNeeded];
+  unblock_input ();
+}
+
+
 /* ==========================================================================
 
     Visible bell and beep.
@@ -2691,8 +2710,6 @@ so some key presses (TAB) are swallowed by the system. */
 static void
 ns_copy_bits (struct frame *f, NSRect src, NSRect dest)
 {
-  NSSize delta = NSMakeSize (dest.origin.x - src.origin.x,
-                             dest.origin.y - src.origin.y)
   NSTRACE ("ns_copy_bits");
 
   if (FRAME_NS_VIEW (f))
@@ -2701,21 +2718,10 @@ so some key presses (TAB) are swallowed by the system. */
 
       /* FIXME: scrollRect:by: is deprecated in macOS 10.14.  There is
          no obvious replacement so we may have to come up with our own.  */
-      [FRAME_NS_VIEW (f) scrollRect: src by: delta];
-
-#ifdef NS_IMPL_COCOA
-      /* As far as I can tell from the documentation, scrollRect:by:,
-         above, should copy the dirty rectangles from our source
-         rectangle to our destination, however it appears it clips the
-         operation to src.  As a result we need to use
-         translateRectsNeedingDisplayInRect:by: below, and we have to
-         union src and dest so it can pick up the dirty rectangles,
-         and place them, as it also clips to the rectangle.
-
-         FIXME: We need a GNUstep equivalent.  */
-      [FRAME_NS_VIEW (f) translateRectsNeedingDisplayInRect:NSUnionRect (src, dest)
-                                                         by:delta];
-#endif
+      [FRAME_NS_VIEW (f) scrollRect: src
+                                 by: NSMakeSize (dest.origin.x - src.origin.x,
+                                                 dest.origin.y - src.origin.y)];
+      [FRAME_NS_VIEW (f) setNeedsDisplay:YES];
     }
 }
 
@@ -3100,6 +3106,15 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors.
       else
         [FRAME_CURSOR_COLOR (f) set];
 
+#ifdef NS_IMPL_COCOA
+      /* TODO: This makes drawing of cursor plus that of phys_cursor_glyph
+         atomic.  Cleaner ways of doing this should be investigated.
+         One way would be to set a global variable DRAWING_CURSOR
+         when making the call to draw_phys..(), don't focus in that
+         case, then move the ns_reset_clipping() here after that call.  */
+      NSDisableScreenUpdates ();
+#endif
+
       switch (cursor_type)
         {
         case DEFAULT_CURSOR:
@@ -3133,6 +3148,10 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors.
       /* draw the character under the cursor */
       if (cursor_type != NO_CURSOR)
         draw_phys_cursor_glyph (w, glyph_row, DRAW_CURSOR);
+
+#ifdef NS_IMPL_COCOA
+      NSEnableScreenUpdates ();
+#endif
     }
 }
 
@@ -4958,7 +4977,7 @@ static Lisp_Object ns_string_to_lispmod (const char *s)
   ns_after_update_window_line,
   ns_update_window_begin,
   ns_update_window_end,
-  0, /* flush_display */
+  ns_flush_display, /* flush_display */
   x_clear_window_mouse_face,
   x_get_glyph_overhangs,
   x_fix_overlapping_area,
@@ -7027,6 +7046,7 @@ - (NSSize)windowWillResize: (NSWindow *)sender toSize: (NSSize)frameSize
         size_title = xmalloc (strlen (old_title) + 40);
 	esprintf (size_title, "%s  —  (%d x %d)", old_title, cols, rows);
         [window setTitle: [NSString stringWithUTF8String: size_title]];
+        [window display];
         xfree (size_title);
       }
   }
@@ -8075,8 +8095,8 @@ - (instancetype)toggleToolbar: (id)sender
 
 - (void)drawRect: (NSRect)rect
 {
-  const NSRect *rectList;
-  NSInteger numRects;
+  int x = NSMinX (rect), y = NSMinY (rect);
+  int width = NSWidth (rect), height = NSHeight (rect);
 
   NSTRACE ("[EmacsView drawRect:" NSTRACE_FMT_RECT "]",
            NSTRACE_ARG_RECT(rect));
@@ -8084,23 +8104,9 @@ - (void)drawRect: (NSRect)rect
   if (!emacsframe || !emacsframe->output_data.ns)
     return;
 
+  ns_clear_frame_area (emacsframe, x, y, width, height);
   block_input ();
-
-  /* Get only the precise dirty rectangles to avoid redrawing
-     potentially large areas of the frame that haven't changed.
-
-     I'm not sure this actually provides much of a performance benefit
-     as it's hard to benchmark, but it certainly doesn't seem to
-     hurt.  */
-  [self getRectsBeingDrawn:&rectList count:&numRects];
-  for (int i = 0 ; i < numRects ; i++)
-    {
-      NSRect r = rectList[i];
-      expose_frame (emacsframe,
-                    NSMinX (r), NSMinY (r),
-                    NSWidth (r), NSHeight (r));
-    }
-
+  expose_frame (emacsframe, x, y, width, height);
   unblock_input ();
 
   /*
-- 
2.19.1

>From 1b3d42a83b79a27c29be475dabc1d6be5b91383b Mon Sep 17 00:00:00 2001
From: Alan Third <[email-masked]>
Date: Mon, 12 Nov 2018 11:28:48 +0000
Subject: [PATCH 2/4] Revert "Ensure NS frame is redrawn correctly  after
 scroll"

This reverts commit a6ab8db3a3dc5ec107ef023c6659620584309c97.
---
 src/nsterm.m | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/nsterm.m b/src/nsterm.m
index 8c355a89f8..d92d6c3244 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -2721,7 +2721,6 @@ so some key presses (TAB) are swallowed by the system. */
       [FRAME_NS_VIEW (f) scrollRect: src
                                  by: NSMakeSize (dest.origin.x - src.origin.x,
                                                  dest.origin.y - src.origin.y)];
-      [FRAME_NS_VIEW (f) setNeedsDisplay:YES];
     }
 }
 
-- 
2.19.1

>From e63c4e1bc305be13ff9b308019cb9d78cda697fc Mon Sep 17 00:00:00 2001
From: Alan Third <[email-masked]>
Date: Mon, 12 Nov 2018 11:29:07 +0000
Subject: [PATCH 3/4] Revert "Make all NS drawing be done from drawRect"

This reverts commit 7946445962372c4255180af45cb7c857f1b0b5fa.
---
 src/nsterm.m | 767 ++++++++++++++++++++++++++-------------------------
 1 file changed, 391 insertions(+), 376 deletions(-)

diff --git a/src/nsterm.m b/src/nsterm.m
index d92d6c3244..abb80c9938 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -276,7 +276,12 @@ - (NSColor *)colorUsingDefaultColorSpace
 long context_menu_value = 0;
 
 /* display update */
+static struct frame *ns_updating_frame;
+static NSView *focus_view = NULL;
 static int ns_window_num = 0;
+#ifdef NS_IMPL_GNUSTEP
+static NSRect uRect;            // TODO: This is dead, remove it?
+#endif
 static BOOL gsaved = NO;
 static BOOL ns_fake_keydown = NO;
 #ifdef NS_IMPL_COCOA
@@ -1034,13 +1039,12 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
    external (RIF) call; whole frame, called before update_window_begin
    -------------------------------------------------------------------------- */
 {
-#ifdef NS_IMPL_COCOA
   EmacsView *view = FRAME_NS_VIEW (f);
-
   NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_update_begin");
 
   ns_update_auto_hide_menu_bar ();
 
+#ifdef NS_IMPL_COCOA
   if ([view isFullscreen] && [view fsIsNative])
   {
     // Fix reappearing tool bar in fullscreen for Mac OS X 10.7
@@ -1049,29 +1053,36 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
     if (! tbar_visible != ! [toolbar isVisible])
       [toolbar setVisible: tbar_visible];
   }
+#endif
+
+  ns_updating_frame = f;
+  [view lockFocus];
 
   /* drawRect may have been called for say the minibuffer, and then clip path
      is for the minibuffer.  But the display engine may draw more because
      we have set the frame as garbaged.  So reset clip path to the whole
      view.  */
-  /* FIXME: I don't think we need to do this.  */
-  if ([NSView focusView] == FRAME_NS_VIEW (f))
-    {
-      NSBezierPath *bp;
-      NSRect r = [view frame];
-      NSRect cr = [[view window] frame];
-      /* If a large frame size is set, r may be larger than the window frame
-         before constrained.  In that case don't change the clip path, as we
-         will clear in to the tool bar and title bar.  */
-      if (r.size.height
-          + FRAME_NS_TITLEBAR_HEIGHT (f)
-          + FRAME_TOOLBAR_HEIGHT (f) <= cr.size.height)
-        {
-          bp = [[NSBezierPath bezierPathWithRect: r] retain];
-          [bp setClip];
-          [bp release];
-        }
-    }
+#ifdef NS_IMPL_COCOA
+  {
+    NSBezierPath *bp;
+    NSRect r = [view frame];
+    NSRect cr = [[view window] frame];
+    /* If a large frame size is set, r may be larger than the window frame
+       before constrained.  In that case don't change the clip path, as we
+       will clear in to the tool bar and title bar.  */
+    if (r.size.height
+        + FRAME_NS_TITLEBAR_HEIGHT (f)
+        + FRAME_TOOLBAR_HEIGHT (f) <= cr.size.height)
+      {
+        bp = [[NSBezierPath bezierPathWithRect: r] retain];
+        [bp setClip];
+        [bp release];
+      }
+  }
+#endif
+
+#ifdef NS_IMPL_GNUSTEP
+  uRect = NSMakeRect (0, 0, 0, 0);
 #endif
 }
 
@@ -1153,66 +1164,99 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
    external (RIF) call; for whole frame, called after update_window_end
    -------------------------------------------------------------------------- */
 {
+  EmacsView *view = FRAME_NS_VIEW (f);
+
   NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_update_end");
 
 /*   if (f == MOUSE_HL_INFO (f)->mouse_face_mouse_frame) */
   MOUSE_HL_INFO (f)->mouse_face_defer = 0;
-}
 
+  block_input ();
 
-static BOOL
-ns_clip_to_rect (struct frame *f, NSRect *r, int n)
+  [view unlockFocus];
+  [[view window] flushWindow];
+
+  unblock_input ();
+  ns_updating_frame = NULL;
+}
+
+static void
+ns_focus (struct frame *f, NSRect *r, int n)
 /* --------------------------------------------------------------------------
-   Clip the drawing area to rectangle r in frame f.  If drawing is not
-   currently possible mark r as dirty and return NO, otherwise return
-   YES.
+   Internal: Focus on given frame.  During small local updates this is used to
+     draw, however during large updates, ns_update_begin and ns_update_end are
+     called to wrap the whole thing, in which case these calls are stubbed out.
+     Except, on GNUstep, we accumulate the rectangle being drawn into, because
+     the back end won't do this automatically, and will just end up flushing
+     the entire window.
    -------------------------------------------------------------------------- */
 {
-  NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "ns_clip_to_rect");
-  if (r)
+  NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "ns_focus");
+  if (r != NULL)
     {
       NSTRACE_RECT ("r", *r);
+    }
 
-      if ([NSView focusView] == FRAME_NS_VIEW (f))
+  if (f != ns_updating_frame)
+    {
+      NSView *view = FRAME_NS_VIEW (f);
+      if (view != focus_view)
         {
-          [[NSGraphicsContext currentContext] saveGraphicsState];
-          if (n == 2)
-            NSRectClipList (r, 2);
-          else
-            NSRectClip (*r);
-          gsaved = YES;
+          if (focus_view != NULL)
+            {
+              [focus_view unlockFocus];
+              [[focus_view window] flushWindow];
+/*debug_lock--; */
+            }
 
-          return YES;
-        }
-      else
-        {
-          NSView *view = FRAME_NS_VIEW (f);
-          int i;
-          for (i = 0 ; i < n ; i++)
-            [view setNeedsDisplayInRect:r[i]];
+          if (view)
+            [view lockFocus];
+          focus_view = view;
+/*if (view) debug_lock++; */
         }
     }
 
-  return NO;
+  /* clipping */
+  if (r)
+    {
+      [[NSGraphicsContext currentContext] saveGraphicsState];
+      if (n == 2)
+        NSRectClipList (r, 2);
+      else
+        NSRectClip (*r);
+      gsaved = YES;
+    }
 }

 static void
-ns_reset_clipping (struct frame *f)
-/* Internal: Restore the previous graphics state, unsetting any
-   clipping areas.  */
+ns_unfocus (struct frame *f)
+/* --------------------------------------------------------------------------
+     Internal: Remove focus on given frame
+   -------------------------------------------------------------------------- */
 {
-  NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "ns_reset_clipping");
+  NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "ns_unfocus");
 
   if (gsaved)
     {
       [[NSGraphicsContext currentContext] restoreGraphicsState];
       gsaved = NO;
     }
+
+  if (f != ns_updating_frame)
+    {
+      if (focus_view != NULL)
+        {
+          [focus_view unlockFocus];
+          [[focus_view window] flushWindow];
+          focus_view = NULL;
+/*debug_lock--; */
+        }
+    }
 }

-static BOOL
+static void
 ns_clip_to_row (struct window *w, struct glyph_row *row,
 		enum glyph_row_area area, BOOL gc)
 /* --------------------------------------------------------------------------
@@ -1231,19 +1275,7 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
   clip_rect.size.width = window_width;
   clip_rect.size.height = row->visible_height;
 
-  return ns_clip_to_rect (f, &clip_rect, 1);
-}
-
-
-static void
-ns_flush_display (struct frame *f)
-/* Force the frame to redisplay.  If areas have previously been marked
-   dirty by setNeedsDisplayInRect (in ns_clip_to_rect), then this will call
-   draw_rect: which will "expose" those areas.  */
-{
-  block_input ();
-  [FRAME_NS_VIEW (f) displayIfNeeded];
-  unblock_input ();
+  ns_focus (f, &clip_rect, 1);
 }

@@ -2667,16 +2699,14 @@ so some key presses (TAB) are swallowed by the system. */
   r = [view bounds];
 
   block_input ();
-  if (ns_clip_to_rect (f, &r, 1))
-    {
-      [ns_lookup_indexed_color (NS_FACE_BACKGROUND
-                                (FACE_FROM_ID (f, DEFAULT_FACE_ID)), f) set];
-      NSRectFill (r);
-      ns_reset_clipping (f);
-
-      /* as of 2006/11 or so this is now needed */
-      ns_redraw_scroll_bars (f);
-    }
+  ns_focus (f, &r, 1);
+  [ns_lookup_indexed_color (NS_FACE_BACKGROUND
+			    (FACE_FROM_ID (f, DEFAULT_FACE_ID)), f) set];
+  NSRectFill (r);
+  ns_unfocus (f);
+
+  /* as of 2006/11 or so this is now needed */
+  ns_redraw_scroll_bars (f);
   unblock_input ();
 }
 
@@ -2697,14 +2727,13 @@ so some key presses (TAB) are swallowed by the system. */
   NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_clear_frame_area");
 
   r = NSIntersectionRect (r, [view frame]);
-  if (ns_clip_to_rect (f, &r, 1))
-    {
-      [ns_lookup_indexed_color (NS_FACE_BACKGROUND (face), f) set];
+  ns_focus (f, &r, 1);
+  [ns_lookup_indexed_color (NS_FACE_BACKGROUND (face), f) set];
 
-      NSRectFill (r);
+  NSRectFill (r);
 
-      ns_reset_clipping (f);
-    }
+  ns_unfocus (f);
+  return;
 }
 
 static void
@@ -2716,11 +2745,11 @@ so some key presses (TAB) are swallowed by the system. */
     {
       hide_bell();              // Ensure the bell image isn't scrolled.
 
-      /* FIXME: scrollRect:by: is deprecated in macOS 10.14.  There is
-         no obvious replacement so we may have to come up with our own.  */
+      ns_focus (f, &dest, 1);
       [FRAME_NS_VIEW (f) scrollRect: src
                                  by: NSMakeSize (dest.origin.x - src.origin.x,
                                                  dest.origin.y - src.origin.y)];
+      ns_unfocus (f);
     }
 }
 
@@ -2931,86 +2960,85 @@ so some key presses (TAB) are swallowed by the system. */
     }
 
   /* Must clip because of partially visible lines.  */
-  if (ns_clip_to_row (w, row, ANY_AREA, YES))
+  ns_clip_to_row (w, row, ANY_AREA, YES);
+
+  if (!p->overlay_p)
     {
-      if (!p->overlay_p)
-        {
-          int bx = p->bx, by = p->by, nx = p->nx, ny = p->ny;
+      int bx = p->bx, by = p->by, nx = p->nx, ny = p->ny;
 
-          if (bx >= 0 && nx > 0)
-            {
-              NSRect r = NSMakeRect (bx, by, nx, ny);
-              NSRectClip (r);
-              [ns_lookup_indexed_color (face->background, f) set];
-              NSRectFill (r);
-            }
+      if (bx >= 0 && nx > 0)
+        {
+          NSRect r = NSMakeRect (bx, by, nx, ny);
+          NSRectClip (r);
+          [ns_lookup_indexed_color (face->background, f) set];
+          NSRectFill (r);
         }
+    }
 
-      if (p->which)
-        {
-          NSRect r = NSMakeRect (p->x, p->y, p->wd, p->h);
-          EmacsImage *img = bimgs[p->which - 1];
+  if (p->which)
+    {
+      NSRect r = NSMakeRect (p->x, p->y, p->wd, p->h);
+      EmacsImage *img = bimgs[p->which - 1];
 
-          if (!img)
-            {
-              // Note: For "periodic" images, allocate one EmacsImage for
-              // the base image, and use it for all dh:s.
-              unsigned short *bits = p->bits;
-              int full_height = p->h + p->dh;
-              int i;
-              unsigned char *cbits = xmalloc (full_height);
-
-              for (i = 0; i < full_height; i++)
-                cbits[i] = bits[i];
-              img = [[EmacsImage alloc] initFromXBM: cbits width: 8
-                                             height: full_height
-                                                 fg: 0 bg: 0];
-              bimgs[p->which - 1] = img;
-              xfree (cbits);
-            }
+      if (!img)
+        {
+          // Note: For "periodic" images, allocate one EmacsImage for
+          // the base image, and use it for all dh:s.
+          unsigned short *bits = p->bits;
+          int full_height = p->h + p->dh;
+          int i;
+          unsigned char *cbits = xmalloc (full_height);
+
+          for (i = 0; i < full_height; i++)
+            cbits[i] = bits[i];
+          img = [[EmacsImage alloc] initFromXBM: cbits width: 8
+                                         height: full_height
+                                             fg: 0 bg: 0];
+          bimgs[p->which - 1] = img;
+          xfree (cbits);
+        }
 
-          NSTRACE_RECT ("r", r);
+      NSTRACE_RECT ("r", r);
 
-          NSRectClip (r);
-          /* Since we composite the bitmap instead of just blitting it, we need
-             to erase the whole background.  */
-          [ns_lookup_indexed_color(face->background, f) set];
-          NSRectFill (r);
+      NSRectClip (r);
+      /* Since we composite the bitmap instead of just blitting it, we need
+         to erase the whole background. */
+      [ns_lookup_indexed_color(face->background, f) set];
+      NSRectFill (r);
 
-          {
-            NSColor *bm_color;
-            if (!p->cursor_p)
-              bm_color = ns_lookup_indexed_color(face->foreground, f);
-            else if (p->overlay_p)
-              bm_color = ns_lookup_indexed_color(face->background, f);
-            else
-              bm_color = f->output_data.ns->cursor_color;
-            [img setXBMColor: bm_color];
-          }
+      {
+        NSColor *bm_color;
+        if (!p->cursor_p)
+          bm_color = ns_lookup_indexed_color(face->foreground, f);
+        else if (p->overlay_p)
+          bm_color = ns_lookup_indexed_color(face->background, f);
+        else
+          bm_color = f->output_data.ns->cursor_color;
+        [img setXBMColor: bm_color];
+      }
 
 #ifdef NS_IMPL_COCOA
-          // Note: For periodic images, the full image height is "h + hd".
-          // By using the height h, a suitable part of the image is used.
-          NSRect fromRect = NSMakeRect(0, 0, p->wd, p->h);
-
-          NSTRACE_RECT ("fromRect", fromRect);
-
-          [img drawInRect: r
-                 fromRect: fromRect
-                operation: NSCompositingOperationSourceOver
-                 fraction: 1.0
-               respectFlipped: YES
-                    hints: nil];
+      // Note: For periodic images, the full image height is "h + hd".
+      // By using the height h, a suitable part of the image is used.
+      NSRect fromRect = NSMakeRect(0, 0, p->wd, p->h);
+
+      NSTRACE_RECT ("fromRect", fromRect);
+
+      [img drawInRect: r
+              fromRect: fromRect
+             operation: NSCompositingOperationSourceOver
+              fraction: 1.0
+           respectFlipped: YES
+                hints: nil];
 #else
-          {
-            NSPoint pt = r.origin;
-            pt.y += p->h;
-            [img compositeToPoint: pt operation: NSCompositingOperationSourceOver];
-          }
+      {
+        NSPoint pt = r.origin;
+        pt.y += p->h;
+        [img compositeToPoint: pt operation: NSCompositingOperationSourceOver];
+      }
 #endif
-        }
-      ns_reset_clipping (f);
     }
+  ns_unfocus (f);
 }

@@ -3092,66 +3120,67 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors.
   r.size.height = h;
   r.size.width = w->phys_cursor_width;
 
-  /* Prevent the cursor from being drawn outside the text area.  */
-  if (ns_clip_to_row (w, glyph_row, TEXT_AREA, NO))
+  /* Prevent the cursor from being drawn outside the text area. */
+  ns_clip_to_row (w, glyph_row, TEXT_AREA, NO); /* do ns_focus(f, &r, 1); if remove */
+
+
+  face = FACE_FROM_ID_OR_NULL (f, phys_cursor_glyph->face_id);
+  if (face && NS_FACE_BACKGROUND (face)
+      == ns_index_color (FRAME_CURSOR_COLOR (f), f))
     {
-      face = FACE_FROM_ID_OR_NULL (f, phys_cursor_glyph->face_id);
-      if (face && NS_FACE_BACKGROUND (face)
-          == ns_index_color (FRAME_CURSOR_COLOR (f), f))
-        {
-          [ns_lookup_indexed_color (NS_FACE_FOREGROUND (face), f) set];
-          hollow_color = FRAME_CURSOR_COLOR (f);
-        }
-      else
-        [FRAME_CURSOR_COLOR (f) set];
+      [ns_lookup_indexed_color (NS_FACE_FOREGROUND (face), f) set];
+      hollow_color = FRAME_CURSOR_COLOR (f);
+    }
+  else
+    [FRAME_CURSOR_COLOR (f) set];
 
 #ifdef NS_IMPL_COCOA
-      /* TODO: This makes drawing of cursor plus that of phys_cursor_glyph
-         atomic.  Cleaner ways of doing this should be investigated.
-         One way would be to set a global variable DRAWING_CURSOR
-         when making the call to draw_phys..(), don't focus in that
-         case, then move the ns_reset_clipping() here after that call.  */
-      NSDisableScreenUpdates ();
+  /* TODO: This makes drawing of cursor plus that of phys_cursor_glyph
+           atomic.  Cleaner ways of doing this should be investigated.
+           One way would be to set a global variable DRAWING_CURSOR
+  	   when making the call to draw_phys..(), don't focus in that
+  	   case, then move the ns_unfocus() here after that call. */
+  NSDisableScreenUpdates ();
 #endif
 
-      switch (cursor_type)
-        {
-        case DEFAULT_CURSOR:
-        case NO_CURSOR:
-          break;
-        case FILLED_BOX_CURSOR:
-          NSRectFill (r);
-          break;
-        case HOLLOW_BOX_CURSOR:
-          NSRectFill (r);
-          [hollow_color set];
-          NSRectFill (NSInsetRect (r, 1, 1));
-          [FRAME_CURSOR_COLOR (f) set];
-          break;
-        case HBAR_CURSOR:
-          NSRectFill (r);
-          break;
-        case BAR_CURSOR:
-          s = r;
-          /* If the character under cursor is R2L, draw the bar cursor
-             on the right of its glyph, rather than on the left.  */
-          cursor_glyph = get_phys_cursor_glyph (w);
-          if ((cursor_glyph->resolved_level & 1) != 0)
-            s.origin.x += cursor_glyph->pixel_width - s.size.width;
-
-          NSRectFill (s);
-          break;
-        }
-      ns_reset_clipping (f);
+  switch (cursor_type)
+    {
+    case DEFAULT_CURSOR:
+    case NO_CURSOR:
+      break;
+    case FILLED_BOX_CURSOR:
+      NSRectFill (r);
+      break;
+    case HOLLOW_BOX_CURSOR:
+      NSRectFill (r);
+      [hollow_color set];
+      NSRectFill (NSInsetRect (r, 1, 1));
+      [FRAME_CURSOR_COLOR (f) set];
+      break;
+    case HBAR_CURSOR:
+      NSRectFill (r);
+      break;
+    case BAR_CURSOR:
+      s = r;
+      /* If the character under cursor is R2L, draw the bar cursor
+         on the right of its glyph, rather than on the left.  */
+      cursor_glyph = get_phys_cursor_glyph (w);
+      if ((cursor_glyph->resolved_level & 1) != 0)
+        s.origin.x += cursor_glyph->pixel_width - s.size.width;
+
+      NSRectFill (s);
+      break;
+    }
+  ns_unfocus (f);
 
-      /* draw the character under the cursor */
-      if (cursor_type != NO_CURSOR)
-        draw_phys_cursor_glyph (w, glyph_row, DRAW_CURSOR);
+  /* draw the character under the cursor */
+  if (cursor_type != NO_CURSOR)
+    draw_phys_cursor_glyph (w, glyph_row, DRAW_CURSOR);
 
 #ifdef NS_IMPL_COCOA
-      NSEnableScreenUpdates ();
+  NSEnableScreenUpdates ();
 #endif
-    }
+
 }

@@ -3169,14 +3198,12 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors.
 
   face = FACE_FROM_ID_OR_NULL (f, VERTICAL_BORDER_FACE_ID);
 
-  if (ns_clip_to_rect (f, &r, 1))
-    {
-      if (face)
-        [ns_lookup_indexed_color(face->foreground, f) set];
+  ns_focus (f, &r, 1);
+  if (face)
+    [ns_lookup_indexed_color(face->foreground, f) set];
 
-      NSRectFill(r);
-      ns_reset_clipping (f);
-    }
+  NSRectFill(r);
+  ns_unfocus (f);
 }

@@ -3203,40 +3230,39 @@ Note that CURSOR_WIDTH is meaningful only for (h)bar cursors.
 
   NSTRACE ("ns_draw_window_divider");
 
-  if (ns_clip_to_rect (f, &divider, 1))
-    {
-      if ((y1 - y0 > x1 - x0) && (x1 - x0 >= 3))
-        /* A vertical divider, at least three pixels wide: Draw first and
-           last pixels differently.  */
-        {
-          [ns_lookup_indexed_color(color_first, f) set];
-          NSRectFill(NSMakeRect (x0, y0, 1, y1 - y0));
-          [ns_lookup_indexed_color(color, f) set];
-          NSRectFill(NSMakeRect (x0 + 1, y0, x1 - x0 - 2, y1 - y0));
-          [ns_lookup_indexed_color(color_last, f) set];
-          NSRectFill(NSMakeRect (x1 - 1, y0, 1, y1 - y0));
-        }
-      else if ((x1 - x0 > y1 - y0) && (y1 - y0 >= 3))
-        /* A horizontal divider, at least three pixels high: Draw first and
-           last pixels differently.  */
-        {
-          [ns_lookup_indexed_color(color_first, f) set];
-          NSRectFill(NSMakeRect (x0, y0, x1 - x0, 1));
-          [ns_lookup_indexed_color(color, f) set];
-          NSRectFill(NSMakeRect (x0, y0 + 1, x1 - x0, y1 - y0 - 2));
-          [ns_lookup_indexed_color(color_last, f) set];
-          NSRectFill(NSMakeRect (x0, y1 - 1, x1 - x0, 1));
-        }
-      else
-        {
-          /* In any other case do not draw the first and last pixels
-             differently.  */
-          [ns_lookup_indexed_color(color, f) set];
-          NSRectFill(divider);
-        }
+  ns_focus (f, &divider, 1);
 
-      ns_reset_clipping (f);
+  if ((y1 - y0 > x1 - x0) && (x1 - x0 >= 3))
+    /* A vertical divider, at least three pixels wide: Draw first and
+       last pixels differently.  */
+    {
+      [ns_lookup_indexed_color(color_first, f) set];
+      NSRectFill(NSMakeRect (x0, y0, 1, y1 - y0));
+      [ns_lookup_indexed_color(color, f) set];
+      NSRectFill(NSMakeRect (x0 + 1, y0, x1 - x0 - 2, y1 - y0));
+      [ns_lookup_indexed_color(color_last, f) set];
+      NSRectFill(NSMakeRect (x1 - 1, y0, 1, y1 - y0));
+    }
+  else if ((x1 - x0 > y1 - y0) && (y1 - y0 >= 3))
+    /* A horizontal divider, at least three pixels high: Draw first and
+       last pixels differently.  */
+    {
+      [ns_lookup_indexed_color(color_first, f) set];
+      NSRectFill(NSMakeRect (x0, y0, x1 - x0, 1));
+      [ns_lookup_indexed_color(color, f) set];
+      NSRectFill(NSMakeRect (x0, y0 + 1, x1 - x0, y1 - y0 - 2));
+      [ns_lookup_indexed_color(color_last, f) set];
+      NSRectFill(NSMakeRect (x0, y1 - 1, x1 - x0, 1));
+    }
+  else
+    {
+      /* In any other case do not draw the first and last pixels
+         differently.  */
+      [ns_lookup_indexed_color(color, f) set];
+      NSRectFill(divider);
     }
+
+  ns_unfocus (f);
 }
 
 static void
@@ -3820,84 +3846,83 @@ Function modeled after x_draw_glyph_string_box ().
       n = ns_get_glyph_string_clip_rect (s, r);
       *r = NSMakeRect (s->x, s->y, s->background_width, s->height);
 
-      if (ns_clip_to_rect (s->f, r, n))
+      ns_focus (s->f, r, n);
+
+      if (s->hl == DRAW_MOUSE_FACE)
+       {
+         face = FACE_FROM_ID_OR_NULL (s->f,
+				      MOUSE_HL_INFO (s->f)->mouse_face_face_id);
+         if (!face)
+           face = FACE_FROM_ID (s->f, MOUSE_FACE_ID);
+       }
+      else
+       face = FACE_FROM_ID (s->f, s->first_glyph->face_id);
+
+      bgCol = ns_lookup_indexed_color (NS_FACE_BACKGROUND (face), s->f);
+      fgCol = ns_lookup_indexed_color (NS_FACE_FOREGROUND (face), s->f);
+
+      for (i = 0; i < n; ++i)
         {
-          if (s->hl == DRAW_MOUSE_FACE)
+          if (!s->row->full_width_p)
             {
-              face = FACE_FROM_ID_OR_NULL (s->f,
-                                           MOUSE_HL_INFO (s->f)->mouse_face_face_id);
-              if (!face)
-                face = FACE_FROM_ID (s->f, MOUSE_FACE_ID);
-            }
-          else
-            face = FACE_FROM_ID (s->f, s->first_glyph->face_id);
+	      int overrun, leftoverrun;
 
-          bgCol = ns_lookup_indexed_color (NS_FACE_BACKGROUND (face), s->f);
-          fgCol = ns_lookup_indexed_color (NS_FACE_FOREGROUND (face), s->f);
+              /* truncate to avoid overwriting fringe and/or scrollbar */
+	      overrun = max (0, (s->x + s->background_width)
+			     - (WINDOW_BOX_RIGHT_EDGE_X (s->w)
+				- WINDOW_RIGHT_FRINGE_WIDTH (s->w)));
+              r[i].size.width -= overrun;
 
-          for (i = 0; i < n; ++i)
-            {
-              if (!s->row->full_width_p)
-                {
-                  int overrun, leftoverrun;
-
-                  /* truncate to avoid overwriting fringe and/or scrollbar */
-                  overrun = max (0, (s->x + s->background_width)
-                                 - (WINDOW_BOX_RIGHT_EDGE_X (s->w)
-                                    - WINDOW_RIGHT_FRINGE_WIDTH (s->w)));
-                  r[i].size.width -= overrun;
-
-                  /* truncate to avoid overwriting to left of the window box */
-                  leftoverrun = (WINDOW_BOX_LEFT_EDGE_X (s->w)
-                                 + WINDOW_LEFT_FRINGE_WIDTH (s->w)) - s->x;
-
-                    if (leftoverrun > 0)
-                      {
-                        r[i].origin.x += leftoverrun;
-                        r[i].size.width -= leftoverrun;
-                      }
-
-                    /* XXX: Try to work between problem where a stretch glyph on
-                       a partially-visible bottom row will clear part of the
-                       modeline, and another where list-buffers headers and similar
-                       rows erroneously have visible_height set to 0.  Not sure
-                       where this is coming from as other terms seem not to show.  */
-                    r[i].size.height = min (s->height, s->row->visible_height);
-                }
+	      /* truncate to avoid overwriting to left of the window box */
+	      leftoverrun = (WINDOW_BOX_LEFT_EDGE_X (s->w)
+			     + WINDOW_LEFT_FRINGE_WIDTH (s->w)) - s->x;
 
-              [bgCol set];
+	      if (leftoverrun > 0)
+		{
+		  r[i].origin.x += leftoverrun;
+		  r[i].size.width -= leftoverrun;
+		}
 
-              /* NOTE: under NS this is NOT used to draw cursors, but we must avoid
-                 overwriting cursor (usually when cursor on a tab).  */
-              if (s->hl == DRAW_CURSOR)
-                {
-                  CGFloat x, width;
+              /* XXX: Try to work between problem where a stretch glyph on
+                 a partially-visible bottom row will clear part of the
+                 modeline, and another where list-buffers headers and similar
+                 rows erroneously have visible_height set to 0.  Not sure
+                 where this is coming from as other terms seem not to show. */
+              r[i].size.height = min (s->height, s->row->visible_height);
+            }
 
-                  x = r[i].origin.x;
-                  width = s->w->phys_cursor_width;
-                  r[i].size.width -= width;
-                  r[i].origin.x += width;
+          [bgCol set];
 
-                  NSRectFill (r[i]);
+          /* NOTE: under NS this is NOT used to draw cursors, but we must avoid
+             overwriting cursor (usually when cursor on a tab) */
+          if (s->hl == DRAW_CURSOR)
+            {
+              CGFloat x, width;
 
-                  /* Draw overlining, etc. on the cursor.  */
-                  if (s->w->phys_cursor_type == FILLED_BOX_CURSOR)
-                    ns_draw_text_decoration (s, face, bgCol, width, x);
-                  else
-                    ns_draw_text_decoration (s, face, fgCol, width, x);
-                }
-              else
-                {
-                  NSRectFill (r[i]);
-                }
+              x = r[i].origin.x;
+              width = s->w->phys_cursor_width;
+              r[i].size.width -= width;
+              r[i].origin.x += width;
+
+              NSRectFill (r[i]);
 
-              /* Draw overlining, etc. on the stretch glyph (or the part
-                 of the stretch glyph after the cursor).  */
-              ns_draw_text_decoration (s, face, fgCol, r[i].size.width,
-                                       r[i].origin.x);
+              /* Draw overlining, etc. on the cursor. */
+              if (s->w->phys_cursor_type == FILLED_BOX_CURSOR)
+                ns_draw_text_decoration (s, face, bgCol, width, x);
+              else
+                ns_draw_text_decoration (s, face, fgCol, width, x);
             }
-          ns_reset_clipping (s->f);
+          else
+            {
+              NSRectFill (r[i]);
+            }
+
+          /* Draw overlining, etc. on the stretch glyph (or the part
+             of the stretch glyph after the cursor). */
+          ns_draw_text_decoration (s, face, fgCol, r[i].size.width,
+                                   r[i].origin.x);
         }
+      ns_unfocus (s->f);
       s->background_filled_p = 1;
     }
 }
@@ -4047,11 +4072,9 @@ overwriting cursor (usually when cursor on a tab).  */
             if (next->first_glyph->type != STRETCH_GLYPH)
               {
                 n = ns_get_glyph_string_clip_rect (s->next, r);
-                if (ns_clip_to_rect (s->f, r, n))
-                  {
-                    ns_maybe_dumpglyphs_background (s->next, 1);
-                    ns_reset_clipping (s->f);
-                  }
+                ns_focus (s->f, r, n);
+                ns_maybe_dumpglyphs_background (s->next, 1);
+                ns_unfocus (s->f);
               }
             else
               {
@@ -4066,12 +4089,10 @@ overwriting cursor (usually when cursor on a tab).  */
 	    || s->first_glyph->type == COMPOSITE_GLYPH))
     {
       n = ns_get_glyph_string_clip_rect (s, r);
-      if (ns_clip_to_rect (s->f, r, n))
-        {
-          ns_maybe_dumpglyphs_background (s, 1);
-          ns_dumpglyphs_box_or_relief (s);
-          ns_reset_clipping (s->f);
-        }
+      ns_focus (s->f, r, n);
+      ns_maybe_dumpglyphs_background (s, 1);
+      ns_dumpglyphs_box_or_relief (s);
+      ns_unfocus (s->f);
       box_drawn_p = 1;
     }
 
@@ -4080,11 +4101,9 @@ overwriting cursor (usually when cursor on a tab).  */
 
     case IMAGE_GLYPH:
       n = ns_get_glyph_string_clip_rect (s, r);
-      if (ns_clip_to_rect (s->f, r, n))
-        {
-          ns_dumpglyphs_image (s, r[0]);
-          ns_reset_clipping (s->f);
-        }
+      ns_focus (s->f, r, n);
+      ns_dumpglyphs_image (s, r[0]);
+      ns_unfocus (s->f);
       break;
 
     case STRETCH_GLYPH:
@@ -4094,68 +4113,66 @@ overwriting cursor (usually when cursor on a tab).  */
     case CHAR_GLYPH:
     case COMPOSITE_GLYPH:
       n = ns_get_glyph_string_clip_rect (s, r);
-      if (ns_clip_to_rect (s->f, r, n))
-        {
-          if (s->for_overlaps || (s->cmp_from > 0
-                                  && ! s->first_glyph->u.cmp.automatic))
-            s->background_filled_p = 1;
-          else
-            ns_maybe_dumpglyphs_background
-              (s, s->first_glyph->type == COMPOSITE_GLYPH);
+      ns_focus (s->f, r, n);
 
-          if (s->hl == DRAW_CURSOR && s->w->phys_cursor_type == FILLED_BOX_CURSOR)
-            {
-              unsigned long tmp = NS_FACE_BACKGROUND (s->face);
-              NS_FACE_BACKGROUND (s->face) = NS_FACE_FOREGROUND (s->face);
-              NS_FACE_FOREGROUND (s->face) = tmp;
-            }
+      if (s->for_overlaps || (s->cmp_from > 0
+			      && ! s->first_glyph->u.cmp.automatic))
+        s->background_filled_p = 1;
+      else
+        ns_maybe_dumpglyphs_background
+          (s, s->first_glyph->type == COMPOSITE_GLYPH);
 
-          {
-            BOOL isComposite = s->first_glyph->type == COMPOSITE_GLYPH;
+      if (s->hl == DRAW_CURSOR && s->w->phys_cursor_type == FILLED_BOX_CURSOR)
+        {
+          unsigned long tmp = NS_FACE_BACKGROUND (s->face);
+          NS_FACE_BACKGROUND (s->face) = NS_FACE_FOREGROUND (s->face);
+          NS_FACE_FOREGROUND (s->face) = tmp;
+        }
 
-            if (isComposite)
-              ns_draw_composite_glyph_string_foreground (s);
-            else
-              ns_draw_glyph_string_foreground (s);
-          }
+      {
+        BOOL isComposite = s->first_glyph->type == COMPOSITE_GLYPH;
 
-          {
-            NSColor *col = (NS_FACE_FOREGROUND (s->face) != 0
-                            ? ns_lookup_indexed_color (NS_FACE_FOREGROUND (s->face),
-                                                       s->f)
-                            : FRAME_FOREGROUND_COLOR (s->f));
-            [col set];
-
-            /* Draw underline, overline, strike-through.  */
-            ns_draw_text_decoration (s, s->face, col, s->width, s->x);
-          }
+        if (isComposite)
+          ns_draw_composite_glyph_string_foreground (s);
+        else
+          ns_draw_glyph_string_foreground (s);
+      }
 
-          if (s->hl == DRAW_CURSOR && s->w->phys_cursor_type == FILLED_BOX_CURSOR)
-            {
-              unsigned long tmp = NS_FACE_BACKGROUND (s->face);
-              NS_FACE_BACKGROUND (s->face) = NS_FACE_FOREGROUND (s->face);
-              NS_FACE_FOREGROUND (s->face) = tmp;
-            }
+      {
+        NSColor *col = (NS_FACE_FOREGROUND (s->face) != 0
+                        ? ns_lookup_indexed_color (NS_FACE_FOREGROUND (s->face),
+                                                   s->f)
+                        : FRAME_FOREGROUND_COLOR (s->f));
+        [col set];
+
+        /* Draw underline, overline, strike-through. */
+        ns_draw_text_decoration (s, s->face, col, s->width, s->x);
+      }
 
-          ns_reset_clipping (s->f);
+      if (s->hl == DRAW_CURSOR && s->w->phys_cursor_type == FILLED_BOX_CURSOR)
+        {
+          unsigned long tmp = NS_FACE_BACKGROUND (s->face);
+          NS_FACE_BACKGROUND (s->face) = NS_FACE_FOREGROUND (s->face);
+          NS_FACE_FOREGROUND (s->face) = tmp;
         }
+
+      ns_unfocus (s->f);
       break;
 
     case GLYPHLESS_GLYPH:
       n = ns_get_glyph_string_clip_rect (s, r);
-      if (ns_clip_to_rect (s->f, r, n))
-        {
-          if (s->for_overlaps || (s->cmp_from > 0
-                                  && ! s->first_glyph->u.cmp.automatic))
-            s->background_filled_p = 1;
-          else
-            ns_maybe_dumpglyphs_background
-              (s, s->first_glyph->type == COMPOSITE_GLYPH);
-          /* ... */
-          /* Not yet implemented.  */
-          /* ... */
-          ns_reset_clipping (s->f);
-        }
+      ns_focus (s->f, r, n);
+
+      if (s->for_overlaps || (s->cmp_from > 0
+			      && ! s->first_glyph->u.cmp.automatic))
+        s->background_filled_p = 1;
+      else
+        ns_maybe_dumpglyphs_background
+          (s, s->first_glyph->type == COMPOSITE_GLYPH);
+      /* ... */
+      /* Not yet implemented.  */
+      /* ... */
+      ns_unfocus (s->f);
       break;
 
     default:
@@ -4166,11 +4183,9 @@ overwriting cursor (usually when cursor on a tab).  */
   if (!s->for_overlaps && !box_drawn_p && s->face->box != FACE_NO_BOX)
     {
       n = ns_get_glyph_string_clip_rect (s, r);
-      if (ns_clip_to_rect (s->f, r, n))
-        {
-          ns_dumpglyphs_box_or_relief (s);
-          ns_reset_clipping (s->f);
-        }
+      ns_focus (s->f, r, n);
+      ns_dumpglyphs_box_or_relief (s);
+      ns_unfocus (s->f);
     }
 
   s->num_clips = 0;
@@ -4976,7 +4991,7 @@ static Lisp_Object ns_string_to_lispmod (const char *s)
   ns_after_update_window_line,
   ns_update_window_begin,
   ns_update_window_end,
-  ns_flush_display, /* flush_display */
+  0, /* flush_display */
   x_clear_window_mouse_face,
   x_get_glyph_overhangs,
   x_fix_overlapping_area,
-- 
2.19.1

>From 812d2b6dbb3eb254736fd6161895d319b5d74b07 Mon Sep 17 00:00:00 2001
From: Alan Third <[email-masked]>
Date: Tue, 13 Nov 2018 21:58:45 +0000
Subject: [PATCH 4/4] That was almost too easy

---
 src/nsterm.h |   3 ++
 src/nsterm.m | 135 ++++++++++++++++++++++++++++++---------------------
 2 files changed, 83 insertions(+), 55 deletions(-)

diff --git a/src/nsterm.h b/src/nsterm.h
index 588b9fc644..034da58d9c 100644
--- a/src/nsterm.h
+++ b/src/nsterm.h
@@ -423,6 +423,7 @@ typedef id instancetype;
    EmacsToolbar *toolbar;
    NSRect ns_userRect;
    BOOL wait_for_tool_bar;
+   NSBitmapImageRep *drawingBuffer;
    }
 
 /* AppKit-side interface */
@@ -456,6 +457,8 @@ typedef id instancetype;
 #endif
 - (int)fullscreenState;
 
+- (void)focusOnDrawingBuffer;
+
 /* Non-notification versions of NSView methods. Used for direct calls. */
 - (void)windowWillEnterFullScreen;
 - (void)windowDidEnterFullScreen;
diff --git a/src/nsterm.m b/src/nsterm.m
index abb80c9938..107c8d98b4 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -277,7 +277,6 @@ - (NSColor *)colorUsingDefaultColorSpace
 
 /* display update */
 static struct frame *ns_updating_frame;
-static NSView *focus_view = NULL;
 static int ns_window_num = 0;
 #ifdef NS_IMPL_GNUSTEP
 static NSRect uRect;            // TODO: This is dead, remove it?
@@ -1056,7 +1055,7 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
 #endif
 
   ns_updating_frame = f;
-  [view lockFocus];
+  [view focusOnDrawingBuffer];
 
   /* drawRect may have been called for say the minibuffer, and then clip path
      is for the minibuffer.  But the display engine may draw more because
@@ -1168,15 +1167,12 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
 
   NSTRACE_WHEN (NSTRACE_GROUP_UPDATES, "ns_update_end");
 
+  [[NSGraphicsContext currentContext] flushGraphics];
+  [view setNeedsDisplay:YES];
+
 /*   if (f == MOUSE_HL_INFO (f)->mouse_face_mouse_frame) */
   MOUSE_HL_INFO (f)->mouse_face_defer = 0;
 
-  block_input ();
-
-  [view unlockFocus];
-  [[view window] flushWindow];
-
-  unblock_input ();
   ns_updating_frame = NULL;
 }
 
@@ -1200,20 +1196,8 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
   if (f != ns_updating_frame)
     {
       NSView *view = FRAME_NS_VIEW (f);
-      if (view != focus_view)
-        {
-          if (focus_view != NULL)
-            {
-              [focus_view unlockFocus];
-              [[focus_view window] flushWindow];
-/*debug_lock--; */
-            }
-
-          if (view)
-            [view lockFocus];
-          focus_view = view;
-/*if (view) debug_lock++; */
-        }
+      if (view)
+        [view focusOnDrawingBuffer];
     }
 
   /* clipping */
@@ -1242,17 +1226,6 @@ static NSRect constrain_frame_rect(NSRect frameRect, bool isFullscreen)
       [[NSGraphicsContext currentContext] restoreGraphicsState];
       gsaved = NO;
     }
-
-  if (f != ns_updating_frame)
-    {
-      if (focus_view != NULL)
-        {
-          [focus_view unlockFocus];
-          [[focus_view window] flushWindow];
-          focus_view = NULL;
-/*debug_lock--; */
-        }
-    }
 }

@@ -2736,22 +2709,6 @@ so some key presses (TAB) are swallowed by the system. */
   return;
 }
 
-static void
-ns_copy_bits (struct frame *f, NSRect src, NSRect dest)
-{
-  NSTRACE ("ns_copy_bits");
-
-  if (FRAME_NS_VIEW (f))
-    {
-      hide_bell();              // Ensure the bell image isn't scrolled.
-
-      ns_focus (f, &dest, 1);
-      [FRAME_NS_VIEW (f) scrollRect: src
-                                 by: NSMakeSize (dest.origin.x - src.origin.x,
-                                                 dest.origin.y - src.origin.y)];
-      ns_unfocus (f);
-    }
-}
 
 static void
 ns_scroll_run (struct window *w, struct run *run)
@@ -2805,7 +2762,7 @@ so some key presses (TAB) are swallowed by the system. */
     NSRect srcRect = NSMakeRect (x, from_y, width, height);
     NSRect dstRect = NSMakeRect (x, to_y, width, height);
 
-    ns_copy_bits (f, srcRect , dstRect);
+    [FRAME_NS_VIEW (f) copyDrawingBufferRect:srcRect To:dstRect];
   }
 
   unblock_input ();
@@ -2864,7 +2821,7 @@ so some key presses (TAB) are swallowed by the system. */
 
   NSTRACE ("ns_shift_glyphs_for_insert");
 
-  ns_copy_bits (f, srcRect, dstRect);
+  [FRAME_NS_VIEW (f) copyDrawingBufferRect:srcRect To:dstRect];
 }

@@ -6978,6 +6935,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);
+      [view createDrawingBufferWithRect:wr];
       [view setFrame: wr];
 
       // to do: consider using [NSNotificationCenter postNotificationName:].
@@ -7248,6 +7206,74 @@ - (BOOL)isOpaque
 }

+- (void)windowDidChangeBackingProperties:(NSNotification *)notification
+  /* Update the drawing buffer when the backing scale factor changes.  */
+{
+   CGFloat old = [[[notification userInfo]
+                    objectForKey:@"NSBackingPropertyOldScaleFactorKey"]
+                   doubleValue];
+   CGFloat new = [[self window] backingScaleFactor];
+
+   if (old != new)
+     {
+       NSRect frame = [self frame];
+       [self createDrawingBufferWithRect:frame];
+       ns_clear_frame (emacsframe);
+       expose_frame (emacsframe, 0, 0, NSWidth (frame), NSHeight (frame));
+     }
+}
+
+
+- (void)createDrawingBufferWithRect:(NSRect)rect
+  /* Create and store a new NSBitmapImageRep for Emacs to draw
+     into.  */
+{
+  NSRect backingRect;
+
+  if (drawingBuffer != nil)
+    [drawingBuffer release];
+
+  backingRect = [self convertRectToBacking:rect];
+
+  drawingBuffer = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:nil
+                                                          pixelsWide:NSWidth (backingRect)
+                                                          pixelsHigh:NSHeight (backingRect)
+                                                       bitsPerSample:8
+                                                     samplesPerPixel:4
+                                                            hasAlpha:YES
+                                                            isPlanar:NO
+                                                      colorSpaceName:NSCalibratedRGBColorSpace
+                                                        bitmapFormat:0
+                                                         bytesPerRow:(4 * NSWidth (backingRect))
+                                                        bitsPerPixel:32];
+
+  [drawingBuffer setSize:rect.size];
+}
+
+
+- (void)focusOnDrawingBuffer
+{
+  [NSGraphicsContext setCurrentContext:
+                       [NSGraphicsContext graphicsContextWithBitmapImageRep:drawingBuffer]];
+}
+
+
+- (void)copyDrawingBufferRect:(NSRect)srcRect To:(NSRect)dstRect
+{
+  NSTRACE ("[EmacsView copyDrawingBufferRect:To:]");
+  NSTRACE_RECT ("SOURCE", srcRect);
+  NSTRACE_RECT ("Destination", dstRect);
+
+  [self focusOnDrawingBuffer];
+  [drawingBuffer drawInRect:dstRect
+                   fromRect:srcRect
+                  operation:NSCompositingOperationCopy
+                   fraction:1.0
+             respectFlipped:YES
+                      hints:nil];
+}
+
+
 - (void)createToolbar: (struct frame *)f
 {
   EmacsView *view = (EmacsView *)FRAME_NS_VIEW (f);
@@ -7317,6 +7343,8 @@ - (instancetype) initFrameFromEmacs: (struct frame *)f
   maximizing_resize = NO;
 #endif
 
+  [self createDrawingBufferWithRect:r];
+
   win = [[EmacsWindow alloc]
             initWithContentRect: r
                       styleMask: (FRAME_UNDECORATED (f)
@@ -8118,10 +8146,7 @@ - (void)drawRect: (NSRect)rect
   if (!emacsframe || !emacsframe->output_data.ns)
     return;
 
-  ns_clear_frame_area (emacsframe, x, y, width, height);
-  block_input ();
-  expose_frame (emacsframe, x, y, width, height);
-  unblock_input ();
+  [drawingBuffer draw];
 
   /*
     drawRect: may be called (at least in Mac OS X 10.5) for invisible
-- 
2.19.1