]> git.eshelyaron.com Git - emacs.git/commitdiff
Improve performance of NS port's display on macOS
authorAlan Third <alan@idiocy.org>
Fri, 21 May 2021 12:33:56 +0000 (13:33 +0100)
committerAlan Third <alan@idiocy.org>
Tue, 1 Jun 2021 18:51:21 +0000 (19:51 +0100)
* src/nsterm.h: Update EmacsSurface definition.
* src/nsterm.m ([EmacsView focusOnDrawingBuffer]): Don't change the
CGContext's settings directly.
([EmacsView unfocusDrawingBuffer]): Don't release the context here.
(CACHE_MAX_SIZE): Add maximum cache size.
([EmacsView updateLayer]): Send a request for getContext, which will
copy the buffer and create the context if it doesn't already exist, to
the NS run loop.
([EmacsSurface initWithSize:ColorSpace:Scale:]): Add the scale factor
and if there's already a CGContext available, reuse it.
([EmacsSurface dealloc]): No longer need to release lastSurface
separately.
([EmacsSurface getContext]): Don't create more surfaces than we have
spaces for in the cache.
([EmacsSurface releaseContext]): If there's no context don't try to
release it and put currentSurface back on the cache instead of
lastSurface.
([EmacsSurface copyContentsTo:]): Don't try to copy if the source and
destination are actually the same surface.

src/nsterm.h
src/nsterm.m

index 017c2394ef146295e2c07b0afc9d023ceac83fcd..0596f3f3c102194e340b5dc6e02ab0b539245a59 100644 (file)
@@ -724,8 +724,9 @@ typedef id instancetype;
   IOSurfaceRef currentSurface;
   IOSurfaceRef lastSurface;
   CGContextRef context;
+  CGFloat scale;
 }
-- (id) initWithSize: (NSSize)s ColorSpace: (CGColorSpaceRef)cs;
+- (id) initWithSize: (NSSize)s ColorSpace: (CGColorSpaceRef)cs Scale: (CGFloat)scale;
 - (void) dealloc;
 - (NSSize) getSize;
 - (CGContextRef) getContext;
index bb20886ab1d1b762e990c53cab78c9bb9b1c2941..f6168243a497d640c926d9af2b52659138105d60 100644 (file)
@@ -8353,19 +8353,17 @@ not_in_argv (NSString *arg)
 
       surface = [[EmacsSurface alloc] initWithSize:s
                                         ColorSpace:[[[self window] colorSpace]
-                                                     CGColorSpace]];
+                                                     CGColorSpace]
+                                             Scale:scale];
 
       /* Since we're using NSViewLayerContentsRedrawOnSetNeedsDisplay
          the layer's scale factor is not set automatically, so do it
          now.  */
-      [[self layer] setContentsScale:[[self window] backingScaleFactor]];
+      [[self layer] setContentsScale:scale];
     }
 
   CGContextRef context = [surface getContext];
 
-  CGContextTranslateCTM(context, 0, [surface getSize].height);
-  CGContextScaleCTM(context, scale, -scale);
-
   [NSGraphicsContext
     setCurrentContext:[NSGraphicsContext
                         graphicsContextWithCGContext:context
@@ -8378,7 +8376,6 @@ not_in_argv (NSString *arg)
   NSTRACE ("[EmacsView unfocusDrawingBuffer]");
 
   [NSGraphicsContext setCurrentContext:nil];
-  [surface releaseContext];
   [self setNeedsDisplay:YES];
 }
 
@@ -8516,7 +8513,11 @@ not_in_argv (NSString *arg)
      There's a private method, -[CALayer setContentsChanged], that we
      could use to force it, but we shouldn't often get the same
      surface twice in a row.  */
+  [surface releaseContext];
   [[self layer] setContents:(id)[surface getSurface]];
+  [surface performSelectorOnMainThread:@selector (getContext)
+                            withObject:nil
+                         waitUntilDone:NO];
 }
 #endif
 
@@ -9717,17 +9718,20 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
    probably be some sort of pruning job that removes excess
    surfaces.  */
 
+#define CACHE_MAX_SIZE 2
 
 - (id) initWithSize: (NSSize)s
          ColorSpace: (CGColorSpaceRef)cs
+              Scale: (CGFloat)scl
 {
   NSTRACE ("[EmacsSurface initWithSize:ColorSpace:]");
 
   [super init];
 
-  cache = [[NSMutableArray arrayWithCapacity:3] retain];
+  cache = [[NSMutableArray arrayWithCapacity:CACHE_MAX_SIZE] retain];
   size = s;
   colorSpace = cs;
+  scale = scl;
 
   return self;
 }
@@ -9740,8 +9744,6 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
 
   if (currentSurface)
     CFRelease (currentSurface);
-  if (lastSurface)
-    CFRelease (lastSurface);
 
   for (id object in cache)
     CFRelease ((IOSurfaceRef)object);
@@ -9764,50 +9766,66 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
    calls cannot be nested.  */
 - (CGContextRef) getContext
 {
-  IOSurfaceRef surface = NULL;
-
-  NSTRACE ("[EmacsSurface getContextWithSize:]");
-  NSTRACE_MSG ("IOSurface count: %lu", [cache count] + (lastSurface ? 1 : 0));
+  NSTRACE ("[EmacsSurface getContext]");
 
-  for (id object in cache)
+  if (!context)
     {
-      if (!IOSurfaceIsInUse ((IOSurfaceRef)object))
-      {
-        surface = (IOSurfaceRef)object;
-        [cache removeObject:object];
-        break;
-      }
-    }
+      IOSurfaceRef surface = NULL;
 
-  if (!surface)
-    {
-      int bytesPerRow = IOSurfaceAlignProperty (kIOSurfaceBytesPerRow,
-                                                size.width * 4);
+      NSTRACE_MSG ("IOSurface count: %lu", [cache count] + (lastSurface ? 1 : 0));
 
-      surface = IOSurfaceCreate
-        ((CFDictionaryRef)@{(id)kIOSurfaceWidth:[NSNumber numberWithInt:size.width],
-            (id)kIOSurfaceHeight:[NSNumber numberWithInt:size.height],
-            (id)kIOSurfaceBytesPerRow:[NSNumber numberWithInt:bytesPerRow],
-            (id)kIOSurfaceBytesPerElement:[NSNumber numberWithInt:4],
-            (id)kIOSurfacePixelFormat:[NSNumber numberWithUnsignedInt:'BGRA']});
-    }
+      for (id object in cache)
+        {
+          if (!IOSurfaceIsInUse ((IOSurfaceRef)object))
+            {
+              surface = (IOSurfaceRef)object;
+              [cache removeObject:object];
+              break;
+            }
+        }
 
-  IOReturn lockStatus = IOSurfaceLock (surface, 0, nil);
-  if (lockStatus != kIOReturnSuccess)
-    NSLog (@"Failed to lock surface: %x", lockStatus);
+      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)
+        {
+          int bytesPerRow = IOSurfaceAlignProperty (kIOSurfaceBytesPerRow,
+                                                    size.width * 4);
+
+          surface = IOSurfaceCreate
+            ((CFDictionaryRef)@{(id)kIOSurfaceWidth:[NSNumber numberWithInt:size.width],
+                (id)kIOSurfaceHeight:[NSNumber numberWithInt:size.height],
+                (id)kIOSurfaceBytesPerRow:[NSNumber numberWithInt:bytesPerRow],
+                (id)kIOSurfaceBytesPerElement:[NSNumber numberWithInt:4],
+                (id)kIOSurfacePixelFormat:[NSNumber numberWithUnsignedInt:'BGRA']});
+        }
+
+      IOReturn lockStatus = IOSurfaceLock (surface, 0, nil);
+      if (lockStatus != kIOReturnSuccess)
+        NSLog (@"Failed to lock surface: %x", lockStatus);
 
-  [self copyContentsTo:surface];
+      [self copyContentsTo:surface];
 
-  currentSurface = surface;
+      currentSurface = surface;
+
+      context = CGBitmapContextCreate (IOSurfaceGetBaseAddress (currentSurface),
+                                       IOSurfaceGetWidth (currentSurface),
+                                       IOSurfaceGetHeight (currentSurface),
+                                       8,
+                                       IOSurfaceGetBytesPerRow (currentSurface),
+                                       colorSpace,
+                                       (kCGImageAlphaPremultipliedFirst
+                                        | kCGBitmapByteOrder32Host));
+
+      CGContextTranslateCTM(context, 0, size.height);
+      CGContextScaleCTM(context, scale, -scale);
+    }
 
-  context = CGBitmapContextCreate (IOSurfaceGetBaseAddress (currentSurface),
-                                   IOSurfaceGetWidth (currentSurface),
-                                   IOSurfaceGetHeight (currentSurface),
-                                   8,
-                                   IOSurfaceGetBytesPerRow (currentSurface),
-                                   colorSpace,
-                                   (kCGImageAlphaPremultipliedFirst
-                                    | kCGBitmapByteOrder32Host));
   return context;
 }
 
@@ -9818,6 +9836,9 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
 {
   NSTRACE ("[EmacsSurface releaseContextAndGetSurface]");
 
+  if (!context)
+    return;
+
   CGContextRelease (context);
   context = NULL;
 
@@ -9825,11 +9846,8 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
   if (lockStatus != kIOReturnSuccess)
     NSLog (@"Failed to unlock surface: %x", lockStatus);
 
-  /* Put lastSurface back on the end of the cache.  It may not have
-     been displayed on the screen yet, but we probably want the new
-     data and not some stale data anyway.  */
-  if (lastSurface)
-    [cache addObject:(id)lastSurface];
+  /* Put currentSurface back on the end of the cache.  */
+  [cache addObject:(id)currentSurface];
   lastSurface = currentSurface;
   currentSurface = NULL;
 }
@@ -9854,7 +9872,7 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
 
   NSTRACE ("[EmacsSurface copyContentsTo:]");
 
-  if (! lastSurface)
+  if (!lastSurface || lastSurface == destination)
     return;
 
   lockStatus = IOSurfaceLock (lastSurface, kIOSurfaceLockReadOnly, nil);
@@ -9874,6 +9892,7 @@ nswindow_orderedIndex_sort (id w1, id w2, void *c)
     NSLog (@"Failed to unlock source surface: %x", lockStatus);
 }
 
+#undef CACHE_MAX_SIZE
 
 @end /* EmacsSurface */