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.
This commit is contained in:
Alan Third 2021-05-21 13:33:56 +01:00
parent a32e65b357
commit 246e107d73
2 changed files with 76 additions and 56 deletions

View 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;

View file

@ -8353,19 +8353,17 @@ - (void)focusOnDrawingBuffer
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 @@ - (void)unfocusDrawingBuffer
NSTRACE ("[EmacsView unfocusDrawingBuffer]");
[NSGraphicsContext setCurrentContext:nil];
[surface releaseContext];
[self setNeedsDisplay:YES];
}
@ -8516,7 +8513,11 @@ - (void)updateLayer
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 @@ @implementation EmacsSurface
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 @@ - (void) dealloc
if (currentSurface)
CFRelease (currentSurface);
if (lastSurface)
CFRelease (lastSurface);
for (id object in cache)
CFRelease ((IOSurfaceRef)object);
@ -9764,50 +9766,66 @@ - (NSSize) getSize
calls cannot be nested. */
- (CGContextRef) getContext
{
IOSurfaceRef surface = NULL;
NSTRACE ("[EmacsSurface getContext]");
NSTRACE ("[EmacsSurface getContextWithSize:]");
NSTRACE_MSG ("IOSurface count: %lu", [cache count] + (lastSurface ? 1 : 0));
for (id object in cache)
if (!context)
{
if (!IOSurfaceIsInUse ((IOSurfaceRef)object))
{
surface = (IOSurfaceRef)object;
[cache removeObject:object];
break;
}
IOSurfaceRef surface = NULL;
NSTRACE_MSG ("IOSurface count: %lu", [cache count] + (lastSurface ? 1 : 0));
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)
{
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];
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);
}
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];
currentSurface = surface;
context = CGBitmapContextCreate (IOSurfaceGetBaseAddress (currentSurface),
IOSurfaceGetWidth (currentSurface),
IOSurfaceGetHeight (currentSurface),
8,
IOSurfaceGetBytesPerRow (currentSurface),
colorSpace,
(kCGImageAlphaPremultipliedFirst
| kCGBitmapByteOrder32Host));
return context;
}
@ -9818,6 +9836,9 @@ - (void) releaseContext
{
NSTRACE ("[EmacsSurface releaseContextAndGetSurface]");
if (!context)
return;
CGContextRelease (context);
context = NULL;
@ -9825,11 +9846,8 @@ - (void) releaseContext
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 @@ - (void) copyContentsTo: (IOSurfaceRef) destination
NSTRACE ("[EmacsSurface copyContentsTo:]");
if (! lastSurface)
if (!lastSurface || lastSurface == destination)
return;
lockStatus = IOSurfaceLock (lastSurface, kIOSurfaceLockReadOnly, nil);
@ -9874,6 +9892,7 @@ - (void) copyContentsTo: (IOSurfaceRef) destination
NSLog (@"Failed to unlock source surface: %x", lockStatus);
}
#undef CACHE_MAX_SIZE
@end /* EmacsSurface */