1
0
mirror of https://github.com/TomHarte/CLK.git synced 2025-08-08 14:25:05 +00:00

Merge pull request #773 from TomHarte/MacCrashAgain

Ensures proper NSScreen comparison...
This commit is contained in:
Thomas Harte
2020-03-20 23:19:53 -04:00
committed by GitHub
2 changed files with 40 additions and 13 deletions

View File

@@ -67,7 +67,7 @@
</Testables> </Testables>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
buildConfiguration = "Release" buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES" enableASanStackUseAfterReturn = "YES"

View File

@@ -22,6 +22,9 @@
NSTrackingArea *_mouseTrackingArea; NSTrackingArea *_mouseTrackingArea;
NSTimer *_mouseHideTimer; NSTimer *_mouseHideTimer;
BOOL _mouseIsCaptured; BOOL _mouseIsCaptured;
volatile int32_t _isDrawingFlag;
BOOL _isInvalid;
} }
- (void)prepareOpenGL { - (void)prepareOpenGL {
@@ -39,33 +42,39 @@
} }
- (void)setupDisplayLink { - (void)setupDisplayLink {
// Kill the existing link if there is one, then wait until its final shout is definitely done. // Kill the existing link if there is one.
if(_displayLink) { if(_displayLink) {
[self invalidate]; [self stopDisplayLink];
CVDisplayLinkRelease(_displayLink); CVDisplayLinkRelease(_displayLink);
} }
// Create a display link capable of being used with all active displays // Create a display link for the display the window is currently on.
NSNumber *const screenNumber = self.window.screen.deviceDescription[@"NSScreenNumber"]; NSNumber *const screenNumber = self.window.screen.deviceDescription[@"NSScreenNumber"];
CVDisplayLinkCreateWithCGDisplay(screenNumber.unsignedIntValue, &_displayLink); CVDisplayLinkCreateWithCGDisplay(screenNumber.unsignedIntValue, &_displayLink);
// Set the renderer output callback function // Set the renderer output callback function.
CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void * __nullable)(self)); CVDisplayLinkSetOutputCallback(_displayLink, DisplayLinkCallback, (__bridge void * __nullable)(self));
// Set the display link for the current renderer // Set the display link for the current renderer.
CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; CGLContextObj cglContext = [[self openGLContext] CGLContextObj];
CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj];
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat); CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_displayLink, cglContext, cglPixelFormat);
// Activate the display link // Activate the display link.
CVDisplayLinkStart(_displayLink); CVDisplayLinkStart(_displayLink);
} }
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) {
CSOpenGLView *const view = (__bridge CSOpenGLView *)displayLinkContext; CSOpenGLView *const view = (__bridge CSOpenGLView *)displayLinkContext;
[view checkDisplayLink]; // Schedule an opportunity to check that the display link is still linked to the correct display.
dispatch_async(dispatch_get_main_queue(), ^{
[view checkDisplayLink];
});
// Ensure _isDrawingFlag has value 1 when drawing, 0 otherwise.
OSAtomicIncrement32(&view->_isDrawingFlag);
[view.displayLinkDelegate openGLViewDisplayLinkDidFire:view now:now outputTime:outputTime]; [view.displayLinkDelegate openGLViewDisplayLinkDidFire:view now:now outputTime:outputTime];
/* /*
Do not touch the display link from after this call; there's a bit of a race condition with setupDisplayLink. Do not touch the display link from after this call; there's a bit of a race condition with setupDisplayLink.
@@ -77,15 +86,22 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
access the display link itself as part of -drawAtTime:frequency:. access the display link itself as part of -drawAtTime:frequency:.
*/ */
OSAtomicDecrement32(&view->_isDrawingFlag);
return kCVReturnSuccess; return kCVReturnSuccess;
} }
- (void)checkDisplayLink { - (void)checkDisplayLink {
// Don't do anything if this view has already been invalidated.
if(_isInvalid) {
return;
}
// Test now whether the screen this view is on has changed since last time it was checked. // Test now whether the screen this view is on has changed since last time it was checked.
// There's likely a callback available for this, on NSWindow if nowhere else, or an NSNotification, // There's likely a callback available for this, on NSWindow if nowhere else, or an NSNotification,
// but since this method is going to be called repeatedly anyway, and the test is cheap, polling // but since this method is going to be called repeatedly anyway, and the test is cheap, polling
// feels fine. // feels fine.
if(self.window.screen != _currentScreen) { if(![self.window.screen isEqual:_currentScreen]) {
_currentScreen = self.window.screen; _currentScreen = self.window.screen;
// Issue a reshape, in case a switch to/from a Retina display has // Issue a reshape, in case a switch to/from a Retina display has
@@ -113,13 +129,24 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
} }
- (void)invalidate { - (void)invalidate {
_isInvalid = YES;
[self stopDisplayLink];
}
- (void)stopDisplayLink {
const double duration = CVDisplayLinkGetActualOutputVideoRefreshPeriod(_displayLink); const double duration = CVDisplayLinkGetActualOutputVideoRefreshPeriod(_displayLink);
CVDisplayLinkStop(_displayLink); CVDisplayLinkStop(_displayLink);
// This is a workaround; I could find no way to ensure that a callback from the display // This is a workaround; CVDisplayLinkStop does not wait for any existing call to the
// link is not currently ongoing. In short: call stop, wait for an entire refresh period, // display-link callback to stop. Furthermore there's a race condition between a callback
// then assume (/hope) the coast is clear. // and any ability by me to set state.
//
// So: wait for a whole display link tick to avoid the second race condition. Then spin
// on an atomic flag.
usleep((useconds_t)ceil(duration * 1000000.0)); usleep((useconds_t)ceil(duration * 1000000.0));
// Spin until _isDrawingFlag is 0 (and leave it as 0).
while(!OSAtomicCompareAndSwap32(0, 0, &_isDrawingFlag));
} }
- (void)dealloc { - (void)dealloc {