推薦 MacOS Emacs 29 用戶使用的三個補丁

Emacs 29 release cycle 裏 MacOS GUI 得到了一定的優化,然而還有部分補丁因爲種種原因沒有合併(甚至還沒合併到 master 裏),在這裏分享一下。

第一個補丁修復了 Emacs 在關閉 frame 時會 raise 一個現有 frame 的問題,即使這個 frame 在另一個虛擬桌面裏。如果你會使用多 frame 並且 frame 不在同一個桌面上,那麼推薦使用該補丁。

0001-Do-not-raise-a-different-frame-when-closing-a-frame.patch
From dab0b8532e5723e273f594ee7548185a3ead5002 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Mart=C3=ADn?= <[email protected]>
Date: Wed, 15 Feb 2023 16:33:14 +0100
Subject: [PATCH 1/4] Do not raise a different frame when closing a frame

* src/frame.h: Declare an NS-only function to make a frame the key
window.
* src/nsfns.m (ns_make_frame_key_window): Implement it.
* src/frame.c (delete_frame): Call ns_make_frame_key_window instead of
Fraise_frame.  (Bug#61525)
---
 src/frame.c | 5 ++---
 src/frame.h | 5 +++++
 src/nsfns.m | 5 +++++
 3 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/frame.c b/src/frame.c
index fc6a3459482..9e4a16d6220 100644
--- a/src/frame.c
+++ b/src/frame.c
@@ -2145,9 +2145,8 @@ delete_frame (Lisp_Object frame, Lisp_Object force)
 	/* Under NS, there is no system mechanism for choosing a new
 	   window to get focus -- it is left to application code.
 	   So the portion of THIS application interfacing with NS
-	   needs to know about it.  We call Fraise_frame, but the
-	   purpose is really to transfer focus.  */
-	Fraise_frame (frame1);
+	   needs to make the frame we switch to the key window.  */
+	ns_make_frame_key_window (XFRAME (frame1));
 #endif
 
       do_switch_frame (frame1, 0, 1, Qnil);
diff --git a/src/frame.h b/src/frame.h
index 4a4c8c38447..b5cda1717ea 100644
--- a/src/frame.h
+++ b/src/frame.h
@@ -1366,6 +1366,11 @@ window_system_available (struct frame *f)
 extern void frame_size_history_plain (struct frame *, Lisp_Object);
 extern void frame_size_history_extra (struct frame *, Lisp_Object,
 				      int, int, int, int, int, int);
+#ifdef NS_IMPL_COCOA
+/* Implemented in nsfns.m.  */
+extern void ns_make_frame_key_window (struct frame *f);
+#endif
+
 extern Lisp_Object Vframe_list;
 
 /* Value is a pointer to the selected frame.  If the selected frame
diff --git a/src/nsfns.m b/src/nsfns.m
index 5ae2cc77bb2..36e38d80746 100644
--- a/src/nsfns.m
+++ b/src/nsfns.m
@@ -685,6 +685,11 @@ Turn the input menu (an NSMenu) into a lisp list for tracking on lisp side.
   SET_FRAME_GARBAGED (f);
 }
 
+void ns_make_frame_key_window (struct frame *f)
+{
+  [[FRAME_NS_VIEW (f) window] makeKeyWindow];
+}
+
 /* tabbar support */
 static void
 ns_set_tab_bar_lines (struct frame *f, Lisp_Object value, Lisp_Object oldval)
-- 
2.41.0

第二個補丁修復了已知的部分畫面撕裂問題。PS:如果打了補丁仍然有撕裂問題,可以嘗試將補丁中的 EMACSLAYER_DOUBLE_BUFFERED 改爲 0。

0004-Simplify-the-EmacsLayer-double-buffering-code-bug-63.patch
From 12d2ade9cb036e75535dd6737490a0fbebd14a31 Mon Sep 17 00:00:00 2001
From: Alan Third <[email protected]>
Date: Sun, 23 Jul 2023 12:00:30 +0100
Subject: [PATCH 4/4] Simplify the EmacsLayer double buffering code (bug#63187)

* src/nsterm.h (EmacsLayer): Remove cache and replace with two
IOSurface variables.
* src/nsterm.m (ns_scroll_run): Remove redundant code.
([EmacsView copyRect:to:]): Ensure the context is flushed before we
start messign directly with the pixel buffer.
([EmacsLayer initWithColorSpace:]):
([EmacsLayer dealloc]):
([EmacsLayer releaseSurfaces]):
([EmacsLayer checkDimensions]):
([EmacsLayer getContext]):
([EmacsLayer releaseContext]):
([EmacsLayer display]):
([EmacsLayer copyContentsTo:]): Remove cache and replace with two
IOSurface variables.
---
 src/nsterm.h |   3 +-
 src/nsterm.m | 147 +++++++++++++++++++--------------------------------
 2 files changed, 56 insertions(+), 94 deletions(-)

diff --git a/src/nsterm.h b/src/nsterm.h
index b6e5a813a6d..22dbf2d8f27 100644
--- a/src/nsterm.h
+++ b/src/nsterm.h
@@ -742,9 +742,8 @@ #define NSTRACE_UNSILENCE()
 #if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
 @interface EmacsLayer : CALayer
 {
-  NSMutableArray *cache;
   CGColorSpaceRef colorSpace;
-  IOSurfaceRef currentSurface;
+  IOSurfaceRef frontSurface, backSurface;
   CGContextRef context;
 }
 - (id) initWithColorSpace: (CGColorSpaceRef)cs;
diff --git a/src/nsterm.m b/src/nsterm.m
index 1c636ff41e1..f232afefe32 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -2704,11 +2704,10 @@ Hide the window (X11 semantics)
   {
     NSRect srcRect = NSMakeRect (x, from_y, width, height);
     NSPoint dest = NSMakePoint (x, to_y);
-    NSRect destRect = NSMakeRect (x, from_y, width, height);
     EmacsView *view = FRAME_NS_VIEW (f);
 
     [view copyRect:srcRect to:dest];
-#ifdef NS_IMPL_COCOA
+#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED < 101400
     [view setNeedsDisplayInRect:destRect];
 #endif
   }
@@ -8676,8 +8675,10 @@ - (void)copyRect:(NSRect)srcRect to:(NSPoint)dest
                                NSHeight (srcRect));
 
 #if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101400
-  double scale = [[self window] backingScaleFactor];
   CGContextRef context = [(EmacsLayer *)[self layer] getContext];
+  CGContextFlush (context);
+
+  double scale = [[self window] backingScaleFactor];
   int bpp = CGBitmapContextGetBitsPerPixel (context) / 8;
   void *pixels = CGBitmapContextGetData (context);
   int rowSize = CGBitmapContextGetBytesPerRow (context);
@@ -10433,21 +10434,16 @@ @implementation EmacsLayer
    of the output.  To avoid this problem we can check if the surface
    is "in use", and if it is then avoid using it.  Unfortunately to
    avoid writing to a surface that's in use, but still maintain the
-   ability to draw to the screen at any time, we need to keep a cache
-   of multiple surfaces that we can use at will.
+   ability to draw to the screen at any time, we need to mantain
+   multiple surfaces that we can use at will.
 
-   The EmacsLayer class maintains this cache of surfaces, and
-   handles the conversion to a CGGraphicsContext that AppKit can use
-   to draw on.
+   The EmacsLayer class maintains these surfaces, and handles the
+   conversion to a CGGraphicsContext that AppKit can use to draw on.
 
-   The cache is simple: if a free surface is found it is removed from
-   the cache and set as the "current" surface.  Emacs draws to the
-   surface and when the layer wants to update the screen we set it's
-   contents to the surface and then add it back on to the end of the
-   cache.  If no free surfaces are found in the cache then a new one
-   is created.  */
+   We simply have a "back" surface, which we can draw to, and a
+   "front" buffer that is currently being displayed on the screen.  */
 
-#define CACHE_MAX_SIZE 2
+#define EMACSLAYER_DOUBLE_BUFFERED 1
 
 - (id) initWithColorSpace: (CGColorSpaceRef)cs
 {
@@ -10455,14 +10451,9 @@ - (id) initWithColorSpace: (CGColorSpaceRef)cs
 
   self = [super init];
   if (self)
-    {
-      cache = [[NSMutableArray arrayWithCapacity:CACHE_MAX_SIZE] retain];
-      [self setColorSpace:cs];
-    }
+    [self setColorSpace:cs];
   else
-    {
-      return nil;
-    }
+    return nil;
 
   return self;
 }
@@ -10482,8 +10473,6 @@ - (void) setColorSpace: (CGColorSpaceRef)cs
 - (void) dealloc
 {
   [self releaseSurfaces];
-  [cache release];
-
   [super dealloc];
 }
 
@@ -10493,18 +10482,16 @@ - (void) releaseSurfaces
   [self setContents:nil];
   [self releaseContext];
 
-  if (currentSurface)
+  if (frontSurface)
     {
-      CFRelease (currentSurface);
-      currentSurface = nil;
+      CFRelease (frontSurface);
+      frontSurface = nil;
     }
 
-  if (cache)
+  if (backSurface)
     {
-      for (id object in cache)
-        CFRelease ((IOSurfaceRef)object);
-
-      [cache removeAllObjects];
+      CFRelease (backSurface);
+      backSurface = nil;
     }
 }
 
@@ -10515,8 +10502,7 @@ - (BOOL) checkDimensions
 {
   int width = NSWidth ([self bounds]) * [self contentsScale];
   int height = NSHeight ([self bounds]) * [self contentsScale];
-  IOSurfaceRef s = currentSurface ? currentSurface
-    : (IOSurfaceRef)[cache firstObject];
+  IOSurfaceRef s = backSurface ? backSurface : frontSurface;
 
   return !s || (IOSurfaceGetWidth (s) == width
                 && IOSurfaceGetHeight (s) == height);
@@ -10529,41 +10515,21 @@ - (CGContextRef) getContext
   CGFloat scale = [self contentsScale];
 
   NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "[EmacsLayer getContext]");
-  NSTRACE_MSG ("IOSurface count: %lu", [cache count] + (currentSurface ? 1 : 0));
 
   if (![self checkDimensions])
     [self releaseSurfaces];
 
   if (!context)
     {
-      IOSurfaceRef surface = NULL;
       int width = NSWidth ([self bounds]) * scale;
       int height = NSHeight ([self bounds]) * scale;
 
-      for (id object in cache)
-        {
-          if (!IOSurfaceIsInUse ((IOSurfaceRef)object))
-            {
-              surface = (IOSurfaceRef)object;
-              [cache removeObject:object];
-              break;
-            }
-        }
-
-      if (!surface && [cache count] >= CACHE_MAX_SIZE)
-        {
-          /* Just grab the first one off the cache.  This may result
-             in tearing effects.  The alternative is to wait for one
-             of the surfaces to become free.  */
-          surface = (IOSurfaceRef)[cache firstObject];
-          [cache removeObject:(id)surface];
-        }
-      else if (!surface)
+      if (!backSurface)
         {
           int bytesPerRow = IOSurfaceAlignProperty (kIOSurfaceBytesPerRow,
                                                     width * 4);
 
-          surface = IOSurfaceCreate
+          backSurface = IOSurfaceCreate
             ((CFDictionaryRef)@{(id)kIOSurfaceWidth:[NSNumber numberWithInt:width],
                 (id)kIOSurfaceHeight:[NSNumber numberWithInt:height],
                 (id)kIOSurfaceBytesPerRow:[NSNumber numberWithInt:bytesPerRow],
@@ -10571,25 +10537,23 @@ - (CGContextRef) getContext
                 (id)kIOSurfacePixelFormat:[NSNumber numberWithUnsignedInt:'BGRA']});
         }
 
-      if (!surface)
+      if (!backSurface)
         {
           NSLog (@"Failed to create IOSurface for frame %@", [self delegate]);
           return nil;
         }
 
-      IOReturn lockStatus = IOSurfaceLock (surface, 0, nil);
+      IOReturn lockStatus = IOSurfaceLock (backSurface, 0, nil);
       if (lockStatus != kIOReturnSuccess)
         NSLog (@"Failed to lock surface: %x", lockStatus);
 
-      [self copyContentsTo:surface];
+      [self copyContentsTo:backSurface];
 
-      currentSurface = surface;
-
-      context = CGBitmapContextCreate (IOSurfaceGetBaseAddress (currentSurface),
-                                       IOSurfaceGetWidth (currentSurface),
-                                       IOSurfaceGetHeight (currentSurface),
+      context = CGBitmapContextCreate (IOSurfaceGetBaseAddress (backSurface),
+                                       IOSurfaceGetWidth (backSurface),
+                                       IOSurfaceGetHeight (backSurface),
                                        8,
-                                       IOSurfaceGetBytesPerRow (currentSurface),
+                                       IOSurfaceGetBytesPerRow (backSurface),
                                        colorSpace,
                                        (kCGImageAlphaPremultipliedFirst
                                         | kCGBitmapByteOrder32Host));
@@ -10597,13 +10561,13 @@ - (CGContextRef) getContext
       if (!context)
         {
           NSLog (@"Failed to create context for frame %@", [self delegate]);
-          IOSurfaceUnlock (currentSurface, 0, nil);
-          CFRelease (currentSurface);
-          currentSurface = nil;
+          IOSurfaceUnlock (backSurface, 0, nil);
+          CFRelease (backSurface);
+          backSurface = nil;
           return nil;
         }
 
-      CGContextTranslateCTM(context, 0, IOSurfaceGetHeight (currentSurface));
+      CGContextTranslateCTM(context, 0, IOSurfaceGetHeight (backSurface));
       CGContextScaleCTM(context, scale, -scale);
     }
 
@@ -10620,10 +10584,11 @@ - (void) releaseContext
   if (!context)
     return;
 
+  CGContextFlush (context);
   CGContextRelease (context);
   context = NULL;
 
-  IOReturn lockStatus = IOSurfaceUnlock (currentSurface, 0, nil);
+  IOReturn lockStatus = IOSurfaceUnlock (backSurface, 0, nil);
   if (lockStatus != kIOReturnSuccess)
     NSLog (@"Failed to unlock surface: %x", lockStatus);
 }
@@ -10633,63 +10598,61 @@ - (void) display
 {
   NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "[EmacsLayer display]");
 
-  if (context)
+  if (context && context != [[NSGraphicsContext currentContext] CGContext])
     {
       [self releaseContext];
 
-#if CACHE_MAX_SIZE == 1
+#ifdef EMACSLAYER_DOUBLE_BUFFERED
+      IOSurfaceRef tmp = frontSurface;
+      frontSurface = backSurface;
+      backSurface = tmp;
+
+      [self setContents:(id)frontSurface];
+#else
       /* This forces the layer to see the surface as updated.  */
       [self setContents:nil];
-#endif
-
-      [self setContents:(id)currentSurface];
 
-      /* Put currentSurface back on the end of the cache.  */
-      [cache addObject:(id)currentSurface];
-      currentSurface = NULL;
+      /* Since we're not doible buffering, just use the back
+         surface. */
+      [self setContents:(id)backSurface];
 
-      /* Schedule a run of getContext so that if Emacs is idle it will
-         perform the buffer copy, etc.  */
-      [self performSelectorOnMainThread:@selector (getContext)
-                             withObject:nil
-                          waitUntilDone:NO];
+#endif
     }
 }
 
 
-/* Copy the contents of lastSurface to DESTINATION.  This is required
+/* Copy the contents of frontSurface to DESTINATION.  This is required
    every time we want to use an IOSurface as its contents are probably
    blanks (if it's new), or stale.  */
 - (void) copyContentsTo: (IOSurfaceRef) destination
 {
   IOReturn lockStatus;
-  IOSurfaceRef source = (IOSurfaceRef)[self contents];
-  void *sourceData, *destinationData;
+  void *frontSurfaceData, *destinationData;
   int numBytes = IOSurfaceGetAllocSize (destination);
 
   NSTRACE_WHEN (NSTRACE_GROUP_FOCUS, "[EmacsLayer copyContentsTo:]");
 
-  if (!source || source == destination)
+  if (!frontSurface || frontSurface == destination)
     return;
 
-  lockStatus = IOSurfaceLock (source, kIOSurfaceLockReadOnly, nil);
+  lockStatus = IOSurfaceLock (frontSurface, kIOSurfaceLockReadOnly, nil);
   if (lockStatus != kIOReturnSuccess)
     NSLog (@"Failed to lock source surface: %x", lockStatus);
 
-  sourceData = IOSurfaceGetBaseAddress (source);
+  frontSurfaceData = IOSurfaceGetBaseAddress (frontSurface);
   destinationData = IOSurfaceGetBaseAddress (destination);
 
   /* Since every IOSurface should have the exact same settings, a
      memcpy seems like the fastest way to copy the data from one to
      the other.  */
-  memcpy (destinationData, sourceData, numBytes);
+  memcpy (destinationData, frontSurfaceData, numBytes);
 
-  lockStatus = IOSurfaceUnlock (source, kIOSurfaceLockReadOnly, nil);
+  lockStatus = IOSurfaceUnlock (frontSurface, kIOSurfaceLockReadOnly, nil);
   if (lockStatus != kIOReturnSuccess)
     NSLog (@"Failed to unlock source surface: %x", lockStatus);
 }
 
-#undef CACHE_MAX_SIZE
+#undef EMACSLAYER_DOUBLE_BUFFERED
 
 @end /* EmacsLayer */
 
-- 
2.41.0

第三個補丁是之前討論過的 GC 優化。已經合併到了 master 分支但是 29 沒有,但是誰說不能 cherry-pick 進來呢 :-p

0002-bug-61489-Increase-BLOCK_ALIGN-from-1024-to-32768.patch
From 9c734ade7a47e3b0fe04cb9f37a5fd11addab45b Mon Sep 17 00:00:00 2001
From: Konstantin Kharlamov <[email protected]>
Date: Thu, 16 Feb 2023 18:07:55 +0300
Subject: [PATCH 2/4] bug#61489: Increase BLOCK_ALIGN from 1024 to 32768
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Originally discovered by Tyler Dodge in his article "Significant Garbage
Collection Improvement For Emacs".

While testing this change on Archlinux system with Intel i5-7200U CPU,
average time of garbage collection gets reduced by ≈25%. Other users
report improvements up to 50%. While monitoring PSS of emacs with and
without customizations loaded before and after the patch, no
statistically significant differences were discovered. So overall, this
change is a win.
* src/alloc.c (BLOCK_ALIGN): increase from 1024 to 32768.
---
 src/alloc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/alloc.c b/src/alloc.c
index 7ff2cd3b100..d6ef4c2388e 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -1052,7 +1052,7 @@ lisp_free (void *block)
    BLOCK_BYTES and guarantees they are aligned on a BLOCK_ALIGN boundary.  */
 
 /* Byte alignment of storage blocks.  */
-#define BLOCK_ALIGN (1 << 10)
+#define BLOCK_ALIGN (1 << 15)
 verify (POWER_OF_2 (BLOCK_ALIGN));
 
 /* Use aligned_alloc if it or a simple substitute is available.
-- 
2.41.0

(BTW,希望論壇能支持一下上傳 patch 類型的文件……)

5 个赞

用 GitHub Gist

补丁如何安装?

https://about.gitlab.com/blog/2021/03/15/patch-files-for-code-review/ 可以参考这个

GC 那个真能有那么大的优化吗

这个帖子本身是针对Emacs29 三个补丁的,建议你单独开贴或者在频道里面问问大家。

都自己编译了,不如直接编译 master 啦。

用穩定版就是爲了穩定嘛,不會提心吊膽某次 git pull 之後 Emacs 掛掉

从来都是直接用emacs-plus。坐等作者打好patch 哈哈哈

试了下 Emacs 29.1,直接发现了 auth-source 的一个 bug。pixel scroll 的手感也不太对 (可以用 像素滚动的过渡动画 - #2,来自 hsingko )。Mituharu Mac 版本还停留在 28.3,但整得我不想升级了。等 29.2 修正 bug 后再说吧

不是29.1刚发布吗?开始29.2开发了?

打错