cocoa: Implement OpenGL swap interval support with CVDisplayLink.

Not only does this fix macOS 10.14 ("Mojave")'s broken NSOpenGLCPSwapInterval
support, it also lets us implement "adaptive vsync" on macOS!

CVDisplayLink is supported back to macOS 10.4 ("Tiger"), so we just use it
universally without version checks and dump NSOpenGLCPSwapInterval, Mojave or
not.
This commit is contained in:
Ryan C. Gordon 2018-12-16 01:03:17 -05:00
parent 1ed6021960
commit 13869f194c
2 changed files with 79 additions and 24 deletions

View File

@ -36,6 +36,11 @@ struct SDL_GLDriverData
@interface SDLOpenGLContext : NSOpenGLContext { @interface SDLOpenGLContext : NSOpenGLContext {
SDL_atomic_t dirty; SDL_atomic_t dirty;
SDL_Window *window; SDL_Window *window;
CVDisplayLinkRef displayLink;
@public SDL_mutex *swapIntervalMutex;
@public SDL_cond *swapIntervalCond;
@public SDL_atomic_t swapIntervalSetting;
@public SDL_atomic_t swapIntervalsPassed;
} }
- (id)initWithFormat:(NSOpenGLPixelFormat *)format - (id)initWithFormat:(NSOpenGLPixelFormat *)format
@ -43,7 +48,7 @@ struct SDL_GLDriverData
- (void)scheduleUpdate; - (void)scheduleUpdate;
- (void)updateIfNeeded; - (void)updateIfNeeded;
- (void)setWindow:(SDL_Window *)window; - (void)setWindow:(SDL_Window *)window;
- (void)dealloc;
@end @end

View File

@ -36,6 +36,23 @@
#define DEFAULT_OPENGL "/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib" #define DEFAULT_OPENGL "/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib"
static CVReturn
DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
SDLOpenGLContext *nscontext = (SDLOpenGLContext *) displayLinkContext;
/*printf("DISPLAY LINK! %u\n", (unsigned int) SDL_GetTicks()); */
const int setting = SDL_AtomicGet(&nscontext->swapIntervalSetting);
if (setting != 0) { /* nothing to do if vsync is disabled, don't even lock */
SDL_LockMutex(nscontext->swapIntervalMutex);
SDL_AtomicAdd(&nscontext->swapIntervalsPassed, 1);
SDL_CondSignal(nscontext->swapIntervalCond);
SDL_UnlockMutex(nscontext->swapIntervalMutex);
}
return kCVReturnSuccess;
}
@implementation SDLOpenGLContext : NSOpenGLContext @implementation SDLOpenGLContext : NSOpenGLContext
- (id)initWithFormat:(NSOpenGLPixelFormat *)format - (id)initWithFormat:(NSOpenGLPixelFormat *)format
@ -45,6 +62,20 @@
if (self) { if (self) {
SDL_AtomicSet(&self->dirty, 0); SDL_AtomicSet(&self->dirty, 0);
self->window = NULL; self->window = NULL;
SDL_AtomicSet(&self->swapIntervalSetting, 0);
SDL_AtomicSet(&self->swapIntervalsPassed, 0);
self->swapIntervalCond = SDL_CreateCond();
self->swapIntervalMutex = SDL_CreateMutex();
if (!self->swapIntervalCond || !self->swapIntervalMutex) {
[self release];
return nil;
}
/* !!! FIXME: check return values. */
CVDisplayLinkCreateWithActiveCGDisplays(&self->displayLink);
CVDisplayLinkSetOutputCallback(self->displayLink, &DisplayLinkCallback, self);
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(self->displayLink, [self CGLContextObj], [format CGLPixelFormatObj]);
CVDisplayLinkStart(displayLink);
} }
return self; return self;
} }
@ -114,6 +145,19 @@
} }
} }
- (void)dealloc
{
if (self->displayLink) {
CVDisplayLinkRelease(self->displayLink);
}
if (self->swapIntervalCond) {
SDL_DestroyCond(self->swapIntervalCond);
}
if (self->swapIntervalMutex) {
SDL_DestroyMutex(self->swapIntervalMutex);
}
[super dealloc];
}
@end @end
@ -368,21 +412,17 @@ int
Cocoa_GL_SetSwapInterval(_THIS, int interval) Cocoa_GL_SetSwapInterval(_THIS, int interval)
{ @autoreleasepool { @autoreleasepool
{ {
NSOpenGLContext *nscontext; SDLOpenGLContext *nscontext = (SDLOpenGLContext *) SDL_GL_GetCurrentContext();
GLint value;
int status; int status;
if (interval < 0) { /* no extension for this on Mac OS X at the moment. */ if (nscontext == nil) {
return SDL_SetError("Late swap tearing currently unsupported");
}
nscontext = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
if (nscontext != nil) {
value = interval;
[nscontext setValues:&value forParameter:NSOpenGLCPSwapInterval];
status = 0;
} else {
status = SDL_SetError("No current OpenGL context"); status = SDL_SetError("No current OpenGL context");
} else {
SDL_LockMutex(nscontext->swapIntervalMutex);
SDL_AtomicSet(&nscontext->swapIntervalsPassed, 0);
SDL_AtomicSet(&nscontext->swapIntervalSetting, interval);
SDL_UnlockMutex(nscontext->swapIntervalMutex);
status = 0;
} }
return status; return status;
@ -392,17 +432,8 @@ int
Cocoa_GL_GetSwapInterval(_THIS) Cocoa_GL_GetSwapInterval(_THIS)
{ @autoreleasepool { @autoreleasepool
{ {
NSOpenGLContext *nscontext; SDLOpenGLContext *nscontext = (SDLOpenGLContext *) SDL_GL_GetCurrentContext();
GLint value; return nscontext ? SDL_AtomicGet(&nscontext->swapIntervalSetting) : 0;
int status = 0;
nscontext = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
if (nscontext != nil) {
[nscontext getValues:&value forParameter:NSOpenGLCPSwapInterval];
status = (int)value;
}
return status;
}} }}
int int
@ -411,6 +442,25 @@ Cocoa_GL_SwapWindow(_THIS, SDL_Window * window)
{ {
SDLOpenGLContext* nscontext = (SDLOpenGLContext*)SDL_GL_GetCurrentContext(); SDLOpenGLContext* nscontext = (SDLOpenGLContext*)SDL_GL_GetCurrentContext();
SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata; SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
const int setting = SDL_AtomicGet(&nscontext->swapIntervalSetting);
if (setting == 0) {
/* nothing to do if vsync is disabled, don't even lock */
} else if (setting < 0) { /* late swap tearing */
SDL_LockMutex(nscontext->swapIntervalMutex);
while (SDL_AtomicGet(&nscontext->swapIntervalsPassed) == 0) {
SDL_CondWait(nscontext->swapIntervalCond, nscontext->swapIntervalMutex);
}
SDL_AtomicSet(&nscontext->swapIntervalsPassed, 0);
SDL_UnlockMutex(nscontext->swapIntervalMutex);
} else {
SDL_LockMutex(nscontext->swapIntervalMutex);
do { /* always wait here so we know we just hit a swap interval. */
SDL_CondWait(nscontext->swapIntervalCond, nscontext->swapIntervalMutex);
} while ((SDL_AtomicGet(&nscontext->swapIntervalsPassed) % setting) != 0);
SDL_AtomicSet(&nscontext->swapIntervalsPassed, 0);
SDL_UnlockMutex(nscontext->swapIntervalMutex);
}
/* on 10.14 ("Mojave") and later, this deadlocks if two contexts in two /* on 10.14 ("Mojave") and later, this deadlocks if two contexts in two
threads try to swap at the same time, so put a mutex around it. */ threads try to swap at the same time, so put a mutex around it. */