Subject: bug#30699: 26.0.91; buffer contents flicker on macOS frames when frames are resized

From: Alan Third <[email-masked]>
Date: Mon, 19 Mar 2018 15:15:52 +0000

On Wed, Mar 14, 2018 at 03:08:58PM +0000, Alan Third wrote:
> On Tue, Mar 13, 2018 at 08:34:10AM -0700, Aaron Jensen wrote:
> > 
> > I haven't seen any glitches using this patch yet, fwiw.
> 
> I haven’t come across anything yet, but I’ve not been using this patch
> much.
> 
> Watch out for macOS re‐enabling screen updates after ~1 second. It
> does this automatically; I guess to stop buggy code from freezing the
> application completely.

It turns out that even with screen updates disabled some calls still
force the screen to update. I’m not sure which ones, but it does mean
we’re unlikely to completely lock up Emacs with this.

I’ve attached a new version of the patch.

Eli, I left the global variable as static since as far as I can tell
static global variables are still global to all threads, which is the
behaviour I believe we want. These functions should only be called
from the main thread anyway, Cocoa will kill the thread if GUI calls
are made from non‐main threads.

If it definitely shouldn’t be static, what should I do with it?

I moved the call to ns_enable_screen_updates in unwind_redisplay as
that’s where the double buffering code appears to unblock updates.
-- 
Alan Third
>From 823298202face05009808d2f00e2faa711c3882f Mon Sep 17 00:00:00 2001
From: Alan Third <[email-masked]>
Date: Sat, 10 Mar 2018 00:09:09 +0000
Subject: [PATCH v2] Fix frame resize flicker on macOS (bug#30699)

* src/nsterm.h (ns_enable_screen_updates): New function.
* src/nsterm.m (ns_enable_screen_updates):
(ns_disable_screen_updates): New functions.
(disable_screen_updates_count): Count of number of times we've called
NSDisableScreenUpdates.
(x_set_window_size): Disable screen updates when not in a live resize
loop.
* src/xdisp.c (redisplay_internal): Reenable screen updates when
redisplay doesn't complete due to a popup.
(unwind_redisplay): Reenable screen updates.
---
 src/nsterm.h |  3 +++
 src/nsterm.m | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 src/xdisp.c  | 16 +++++++++++++++-
 3 files changed, 64 insertions(+), 1 deletion(-)

diff --git a/src/nsterm.h b/src/nsterm.h
index 8b985930ec..df59a7dbd9 100644
--- a/src/nsterm.h
+++ b/src/nsterm.h
@@ -1160,6 +1160,9 @@ extern void ns_release_autorelease_pool (void *);
 extern const char *ns_get_defaults_value (const char *key);
 extern void ns_init_locale (void);
 
+#ifdef NS_IMPL_COCOA
+extern void ns_enable_screen_updates (void);
+#endif
 
 /* in nsmenu */
 extern void update_frame_tool_bar (struct frame *f);
diff --git a/src/nsterm.m b/src/nsterm.m
index 75e0b837c6..45367dd36f 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -288,6 +288,9 @@ - (NSColor *)colorUsingDefaultColorSpace
 static BOOL ns_fake_keydown = NO;
 #ifdef NS_IMPL_COCOA
 static BOOL ns_menu_bar_is_hidden = NO;
+
+/* The number of times NSDisableScreenUpdates has been called.  */
+static int disable_screen_updates_count = 0;
 #endif
 /*static int debug_lock = 0; */
 
@@ -727,6 +730,40 @@ Free a pool and temporary objects it refers to (callable from C)
 }

+#ifdef NS_IMPL_COCOA
+/* Disabling screen updates can be used to make several actions appear
+   "atomic" to the end user.  It seems some actions can still update
+   the display, though.
+
+   When we re-enable screen updates the number of calls to
+   NSEnableScreenUpdates should match the number to
+   NSDisableScreenUpdates.
+
+   We use this to prevent the user seeing a blank frame after it has
+   been resized.  x_set_window_size disables updates and when
+   redisplay compeltes unwind_redisplay enables them again
+   (bug#30699).  */
+
+static void
+ns_disable_screen_updates (void)
+{
+  NSDisableScreenUpdates ();
+  disable_screen_updates_count++;
+}
+
+void
+ns_enable_screen_updates (void)
+/* Re-enable screen updates.  Called from unwind_redisplay.  */
+{
+  while (disable_screen_updates_count > 0)
+    {
+      NSEnableScreenUpdates ();
+      disable_screen_updates_count--;
+    }
+}
+#endif
+
+
 static BOOL
 ns_menu_bar_should_be_hidden (void)
 /* True, if the menu bar should be hidden.  */
@@ -1877,6 +1914,15 @@ -(void)remove
 
   block_input ();
 
+#ifdef NS_IMPL_COCOA
+  /* To prevent showing the user a blank frame, stop updates being
+     flushed to the screen until after redisplay has completed.  This
+     breaks live resize (resizing with a mouse), so don't do it if
+     we're in a live resize loop.  */
+  if (![view inLiveResize])
+    ns_disable_screen_updates ();
+#endif
+
   if (pixelwise)
     {
       pixelwidth = FRAME_TEXT_TO_PIXEL_WIDTH (f, width);
diff --git a/src/xdisp.c b/src/xdisp.c
index a97d4db607..4778f532cd 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -13918,7 +13918,15 @@ redisplay_internal (void)
 
 #if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NS)
   if (popup_activated ())
-    return;
+    {
+#ifdef NS_IMPL_COCOA
+      /* On macOS we may have disabled screen updates due to window
+         resizing.  We should re-enable them so the popup can be
+         displayed.  */
+      ns_enable_screen_updates ();
+#endif
+      return;
+    }
 #endif
 
   /* I don't think this happens but let's be paranoid.  */
@@ -14722,6 +14730,12 @@ unwind_redisplay (void)
 {
   redisplaying_p = false;
   unblock_buffer_flips ();
+#ifdef NS_IMPL_COCOA
+  /* On macOS we may have disabled screen updates due to window
+     resizing.  When redisplay completes we want to re-enable
+     them.  */
+  ns_enable_screen_updates ();
+#endif
 }

-- 
2.16.1