Skip to content

Commit ab71bd8

Browse files
committed
Fix resizing issues with the flicker fix and hide the tabline
Fix the misc resizing issues with the previous CoreText renderer commit, in particular cases where zoom button was clicked, Vim initiated resizing (e.g. ":set lines+=10"), font size changes (Cmd-+/-), fullscreen toggles, etc. - The core issue is that the order of operation for those are not consistent. Sometimes, MacVim changes window size first before letting Vim knows, but other times it lets Vim handle it before resizing (e.g. zoom). - The new CoreText renderer's buffer needs to know when the size change in order to resize the buffer, and it wasn't doing it in the right spot. Fix it so that it's delayed until updateLayer: is called. By that time both MacVim and Vim should have already come to an agreement on the new size. - Also, when using the new 10.14 buffer renderer, don't use [NSAnimationContext beginGrouping] to block the system from resizing the window, because it also suffers from the order of operation issue and sometimes endGrouping could get called before beginGrouping, causing the UI to appear frozen. Instead, just have updateLayer make a new image and copy over the old one to avoid the black flickering when resizing (which was what the begin/endGrouping was trying to solve to begin with), and the UI now works smoother as well (e.g. double clicking the border now works smoothly). The previous change also set the window background color to whatever default background color is which is fine but it affects the tabline separator as well and makes it look jarring. The tabline separator is mostly a relic of the older macOS versions, so disable it on new-ish macOS verisons. Also, update docs in the known issues section to make it clear there's currently an issue in performance under Mojave. That will be removed when the performance is fixed in the future.
1 parent 0db547d commit ab71bd8

File tree

5 files changed

+148
-26
lines changed

5 files changed

+148
-26
lines changed

runtime/doc/gui_mac.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,9 @@ to use in normal mode and type ":set imd" followed by ":set noimd".
715715
This list is by no means exhaustive, it only enumerates some of the more
716716
prominent bugs/missing features.
717717

718+
- Under macOS Mojave (10.14), the default renderer (Core Text renderer) has
719+
some performance issues and scrolling is not as smooth as previous macOS
720+
versions (10.13 or below).
718721
- Localized menus are not supported. Choosing anything but "English" in the
719722
"International" pane of "System Prefences" may break the menus (and
720723
toolbar).

src/MacVim/MMCoreTextView.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,32 @@
4141
CGPoint *positions;
4242
NSMutableArray *fontCache;
4343

44+
// Issue draws onto an CGImage that caches the drawn results instead of
45+
// directly in drawRect:. This is the default behavior in cases where simply
46+
// drawing incrementally in drawRect: doesn't work. Those cases are:
47+
// 1. Non-native fullscreen
48+
// 2. 10.14+ (views are always layer-backed which means the view buffer will
49+
// be cleared and we can't incrementally draw in drawRect:)
50+
//
51+
// This can be configured by setting MMBufferedDrawingKey in user defaults.
52+
BOOL cgBufferDrawEnabled;
53+
BOOL cgBufferDrawNeedsUpdateContext;
54+
CGContextRef cgContext;
55+
56+
// *Deprecated*
57+
// Draw onto a CGLayer instead of lazily updating the view's buffer in
58+
// drawRect: which is error-prone and relying on undocumented behaviors
59+
// (that the OS will preserve the old buffer).
60+
//
61+
// This is deprecated. Use cgBufferDrawEnabled instead which is more
62+
// efficient.
63+
//
64+
// This can be configured by setting MMUseCGLayerAlwaysKey in user defaults.
4465
BOOL cgLayerEnabled;
4566
CGLayerRef cgLayer;
4667
CGContextRef cgLayerContext;
4768
NSLock *cgLayerLock;
4869

49-
CGContextRef cgContext;
50-
5170
// These are used in MMCoreTextView+ToolTip.m
5271
id trackingRectOwner_; // (not retained)
5372
void *trackingRectUserData_;

src/MacVim/MMCoreTextView.m

Lines changed: 111 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ - (id)initWithFrame:(NSRect)frame
132132
{
133133
if (!(self = [super initWithFrame:frame]))
134134
return nil;
135+
136+
cgBufferDrawEnabled = [[NSUserDefaults standardUserDefaults]
137+
boolForKey:MMBufferedDrawingKey];
138+
cgBufferDrawNeedsUpdateContext = NO;
135139

136140
cgLayerEnabled = [[NSUserDefaults standardUserDefaults]
137141
boolForKey:MMUseCGLayerAlwaysKey];
@@ -447,18 +451,31 @@ - (BOOL)_wantsKeyDownForEvent:(id)event
447451
}
448452

449453
- (void)setFrameSize:(NSSize)newSize {
450-
if (NSEqualSizes(newSize, self.bounds.size))
451-
return;
452-
if (!drawPending) {
453-
[NSAnimationContext beginGrouping];
454-
drawPending = YES;
454+
if (!NSEqualSizes(newSize, self.bounds.size)) {
455+
if (!drawPending && !cgBufferDrawEnabled) {
456+
// When resizing a window, it will invalidate the buffer and cause
457+
// MacVim to draw black until we get the draw commands from Vim and
458+
// we draw them out in drawRect. Use beginGrouping to stop the
459+
// window resize from happening until we get the draw calls.
460+
//
461+
// The updateLayer/cgBufferDrawEnabled path handles this differently
462+
// and don't need this.
463+
[NSAnimationContext beginGrouping];
464+
drawPending = YES;
465+
}
466+
if (cgBufferDrawEnabled) {
467+
cgBufferDrawNeedsUpdateContext = YES;
468+
}
455469
}
470+
456471
[super setFrameSize:newSize];
457-
[self updateCGContext];
458472
}
459473

460474
- (void)viewDidChangeBackingProperties {
461-
[self updateCGContext];
475+
if (cgBufferDrawEnabled) {
476+
cgBufferDrawNeedsUpdateContext = YES;
477+
}
478+
[super viewDidChangeBackingProperties];
462479
}
463480

464481
- (void)keyDown:(NSEvent *)event
@@ -617,20 +634,81 @@ - (BOOL)isFlipped
617634
}
618635

619636
- (void)updateCGContext {
620-
if (cgContext || [[NSUserDefaults standardUserDefaults]
621-
boolForKey:MMBufferedDrawingKey]) {
637+
if (cgContext) {
622638
CGContextRelease(cgContext);
623-
NSRect backingRect = [self convertRectToBacking:self.bounds];
624-
cgContext = CGBitmapContextCreate(NULL, NSWidth(backingRect), NSHeight(backingRect), 8, 0, self.window.colorSpace.CGColorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
625-
CGContextScaleCTM(cgContext, self.window.backingScaleFactor, self.window.backingScaleFactor);
639+
cgContext = nil;
626640
}
641+
642+
NSRect backingRect = [self convertRectToBacking:self.bounds];
643+
cgContext = CGBitmapContextCreate(NULL, NSWidth(backingRect), NSHeight(backingRect), 8, 0, self.window.colorSpace.CGColorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
644+
CGContextScaleCTM(cgContext, self.window.backingScaleFactor, self.window.backingScaleFactor);
645+
646+
cgBufferDrawNeedsUpdateContext = NO;
627647
}
628648

629649
- (BOOL)wantsUpdateLayer {
630-
return cgContext != nil;
650+
return cgBufferDrawEnabled;
631651
}
632652

633653
- (void)updateLayer {
654+
if (!cgContext) {
655+
[self updateCGContext];
656+
} else if (cgBufferDrawNeedsUpdateContext) {
657+
if ([drawData count] != 0) {
658+
[self updateCGContext];
659+
} else {
660+
// In this case, we don't have a single draw command, meaning that
661+
// Vim hasn't caught up yet and hasn't issued draw commands. We
662+
// don't want to use [NSAnimationContext beginGrouping] as it's
663+
// fragile (we may miss the endGrouping call due to order of
664+
// operation), and also it makes the animation jerky.
665+
// Instead, copy the image to the new context and align it to the
666+
// top left and make sure it doesn't stretch. This makes the
667+
// resizing smooth while Vim tries to catch up in issuing draws.
668+
CGImageRef oldImage = CGBitmapContextCreateImage(cgContext);
669+
670+
[self updateCGContext]; // This will make a new cgContext
671+
672+
CGContextSaveGState(cgContext);
673+
CGContextSetBlendMode(cgContext, kCGBlendModeCopy);
674+
675+
// Filling the background so the edge won't be black.
676+
NSRect newRect = [self bounds];
677+
float r = [defaultBackgroundColor redComponent];
678+
float g = [defaultBackgroundColor greenComponent];
679+
float b = [defaultBackgroundColor blueComponent];
680+
float a = [defaultBackgroundColor alphaComponent];
681+
CGContextSetRGBFillColor(cgContext, r, g, b, a);
682+
CGContextFillRect(cgContext, *(CGRect*)&newRect);
683+
CGContextSetBlendMode(cgContext, kCGBlendModeNormal);
684+
685+
// Copy the old image over to the new image, and make sure to
686+
// respect scaling and remember that CGImage's Y origin is
687+
// bottom-left.
688+
CGFloat scale = self.window.backingScaleFactor;
689+
size_t oldWidth = CGImageGetWidth(oldImage) / scale;
690+
size_t oldHeight = CGImageGetHeight(oldImage) / scale;
691+
CGFloat newHeight = newRect.size.height;
692+
NSRect imageRect = NSMakeRect(0, newHeight - oldHeight, (CGFloat)oldWidth, (CGFloat)oldHeight);
693+
694+
CGContextDrawImage(cgContext, imageRect, oldImage);
695+
CGImageRelease(oldImage);
696+
CGContextRestoreGState(cgContext);
697+
}
698+
}
699+
700+
// Now issue the batched draw commands
701+
if ([drawData count] != 0) {
702+
[NSGraphicsContext saveGraphicsState];
703+
NSGraphicsContext.currentContext = [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:self.flipped];
704+
id data;
705+
NSEnumerator *e = [drawData objectEnumerator];
706+
while ((data = [e nextObject]))
707+
[self batchDrawData:data];
708+
[drawData removeAllObjects];
709+
[NSGraphicsContext restoreGraphicsState];
710+
}
711+
634712
CGImageRef contentsImage = CGBitmapContextCreateImage(cgContext);
635713
self.layer.contents = (id)contentsImage;
636714
CGImageRelease(contentsImage);
@@ -683,11 +761,24 @@ - (void)drawRect:(NSRect)rect
683761

684762
- (void)performBatchDrawWithData:(NSData *)data
685763
{
686-
if (cgContext) {
687-
[NSGraphicsContext saveGraphicsState];
688-
NSGraphicsContext.currentContext = [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:self.flipped];
689-
[self batchDrawData:data];
690-
[NSGraphicsContext restoreGraphicsState];
764+
if (cgBufferDrawEnabled) {
765+
// We batch up all the commands and actually perform the draw at
766+
// updateLayer. The reason is that right now MacVim has a lot of
767+
// different paths that could change the view size (zoom, user resizing
768+
// from either dragging border or another program, Cmd-+/- to change
769+
// font size, fullscreen, etc). Those different paths don't currently
770+
// have a consistent order of operation of (Vim or MacVim go first), so
771+
// sometimes Vim gets updated and issue a batch draw first, but
772+
// sometimes MacVim gets notified first (e.g. when window is resized).
773+
// If frame size has changed we need to call updateCGContext but we
774+
// can't do it here because of the order of operation issue. That's why
775+
// we wait till updateLayer to do it where everything has already been
776+
// done and settled.
777+
//
778+
// Note: Should probably refactor the different ways window size could
779+
// be changed and unify them instead of the status quo of spaghetti.
780+
[drawData addObject:data];
781+
[self setNeedsDisplay:YES];
691782
} else if (cgLayerEnabled && drawData.count == 0 && [self getCGContext]) {
692783
[cgLayerLock lock];
693784
[self batchDrawData:data];
@@ -748,13 +839,13 @@ - (CGContextRef)getCGContext
748839

749840
- (void)setNeedsDisplayCGLayerInRect:(CGRect)rect
750841
{
751-
if (cgLayerEnabled || cgContext)
842+
if (cgLayerEnabled)
752843
[self setNeedsDisplayInRect:rect];
753844
}
754845

755846
- (void)setNeedsDisplayCGLayer:(BOOL)flag
756847
{
757-
if (cgLayerEnabled || cgContext)
848+
if (cgLayerEnabled)
758849
[self setNeedsDisplay:flag];
759850
}
760851

src/MacVim/MMWindowController.m

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,11 +1544,17 @@ - (void)updateTablineSeparator
15441544
BOOL windowTextured = ([decoratedWindow styleMask] &
15451545
NSWindowStyleMaskTexturedBackground) != 0;
15461546
BOOL hideSeparator = NO;
1547-
1548-
if (fullScreenEnabled || tabBarVisible)
1547+
1548+
if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_10) {
1549+
// The tabline separator is mostly an old feature and not necessary
1550+
// modern macOS versions.
15491551
hideSeparator = YES;
1550-
else
1551-
hideSeparator = toolbarHidden && !windowTextured;
1552+
} else {
1553+
if (fullScreenEnabled || tabBarVisible)
1554+
hideSeparator = YES;
1555+
else
1556+
hideSeparator = toolbarHidden && !windowTextured;
1557+
}
15521558

15531559
[self hideTablineSeparator:hideSeparator];
15541560
}

src/MacVim/MacVim.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@
4545
#ifndef NSAppKitVersionNumber10_12
4646
# define NSAppKitVersionNumber10_12 1504
4747
#endif
48+
#ifndef NSAppKitVersionNumber10_13
49+
# define NSAppKitVersionNumber10_13 1561
50+
#endif
4851

4952
#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_12
5053
// Deprecated constants in 10.12 SDK

0 commit comments

Comments
 (0)