From 246e107d73e633c06478eaf021776acedef9dafc Mon Sep 17 00:00:00 2001 From: Alan Third Date: Fri, 21 May 2021 13:33:56 +0100 Subject: [PATCH] Improve performance of NS port's display on macOS * 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 | 3 +- src/nsterm.m | 121 +++++++++++++++++++++++++++++---------------------- 2 files changed, 72 insertions(+), 52 deletions(-) diff --git a/src/nsterm.h b/src/nsterm.h index 017c2394ef1..0596f3f3c10 100644 --- a/src/nsterm.h +++ b/src/nsterm.h @@ -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; diff --git a/src/nsterm.m b/src/nsterm.m index bb20886ab1d..f6168243a49 100644 --- a/src/nsterm.m +++ b/src/nsterm.m @@ -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 */ -- 2.39.2