cocoa: Implement OpenGL swap interval support with CVDisplayLink.
authorRyan C. Gordon <icculus@icculus.org>
Sun, 16 Dec 2018 01:03:17 -0500
changeset 1247773f3ca85ac0e
parent 12476 11f8bd1899c5
child 12478 b801d44da5dc
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.
src/video/cocoa/SDL_cocoaopengl.h
src/video/cocoa/SDL_cocoaopengl.m
     1.1 --- a/src/video/cocoa/SDL_cocoaopengl.h	Sat Dec 15 16:21:24 2018 +0100
     1.2 +++ b/src/video/cocoa/SDL_cocoaopengl.h	Sun Dec 16 01:03:17 2018 -0500
     1.3 @@ -36,6 +36,11 @@
     1.4  @interface SDLOpenGLContext : NSOpenGLContext {
     1.5      SDL_atomic_t dirty;
     1.6      SDL_Window *window;
     1.7 +    CVDisplayLinkRef displayLink;
     1.8 +    @public SDL_mutex *swapIntervalMutex;
     1.9 +    @public SDL_cond *swapIntervalCond;
    1.10 +    @public SDL_atomic_t swapIntervalSetting;
    1.11 +    @public SDL_atomic_t swapIntervalsPassed;
    1.12  }
    1.13  
    1.14  - (id)initWithFormat:(NSOpenGLPixelFormat *)format
    1.15 @@ -43,7 +48,7 @@
    1.16  - (void)scheduleUpdate;
    1.17  - (void)updateIfNeeded;
    1.18  - (void)setWindow:(SDL_Window *)window;
    1.19 -
    1.20 +- (void)dealloc;
    1.21  @end
    1.22  
    1.23  
     2.1 --- a/src/video/cocoa/SDL_cocoaopengl.m	Sat Dec 15 16:21:24 2018 +0100
     2.2 +++ b/src/video/cocoa/SDL_cocoaopengl.m	Sun Dec 16 01:03:17 2018 -0500
     2.3 @@ -36,6 +36,23 @@
     2.4  
     2.5  #define DEFAULT_OPENGL  "/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib"
     2.6  
     2.7 +static CVReturn
     2.8 +DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
     2.9 +{
    2.10 +    SDLOpenGLContext *nscontext = (SDLOpenGLContext *) displayLinkContext;
    2.11 +    
    2.12 +    /*printf("DISPLAY LINK! %u\n", (unsigned int) SDL_GetTicks()); */
    2.13 +    const int setting = SDL_AtomicGet(&nscontext->swapIntervalSetting);
    2.14 +    if (setting != 0) { /* nothing to do if vsync is disabled, don't even lock */
    2.15 +        SDL_LockMutex(nscontext->swapIntervalMutex);
    2.16 +        SDL_AtomicAdd(&nscontext->swapIntervalsPassed, 1);
    2.17 +        SDL_CondSignal(nscontext->swapIntervalCond);
    2.18 +        SDL_UnlockMutex(nscontext->swapIntervalMutex);
    2.19 +    }
    2.20 +
    2.21 +    return kCVReturnSuccess;
    2.22 +}
    2.23 +
    2.24  @implementation SDLOpenGLContext : NSOpenGLContext
    2.25  
    2.26  - (id)initWithFormat:(NSOpenGLPixelFormat *)format
    2.27 @@ -45,6 +62,20 @@
    2.28      if (self) {
    2.29          SDL_AtomicSet(&self->dirty, 0);
    2.30          self->window = NULL;
    2.31 +        SDL_AtomicSet(&self->swapIntervalSetting, 0);
    2.32 +        SDL_AtomicSet(&self->swapIntervalsPassed, 0);
    2.33 +        self->swapIntervalCond = SDL_CreateCond();
    2.34 +        self->swapIntervalMutex = SDL_CreateMutex();
    2.35 +        if (!self->swapIntervalCond || !self->swapIntervalMutex) {
    2.36 +            [self release];
    2.37 +            return nil;
    2.38 +        }
    2.39 +
    2.40 +        /* !!! FIXME: check return values. */
    2.41 +        CVDisplayLinkCreateWithActiveCGDisplays(&self->displayLink);
    2.42 +        CVDisplayLinkSetOutputCallback(self->displayLink, &DisplayLinkCallback, self);
    2.43 +        CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(self->displayLink, [self CGLContextObj], [format CGLPixelFormatObj]);
    2.44 +        CVDisplayLinkStart(displayLink);
    2.45      }
    2.46      return self;
    2.47  }
    2.48 @@ -114,6 +145,19 @@
    2.49      }
    2.50  }
    2.51  
    2.52 +- (void)dealloc
    2.53 +{
    2.54 +    if (self->displayLink) {
    2.55 +        CVDisplayLinkRelease(self->displayLink);
    2.56 +    }
    2.57 +    if (self->swapIntervalCond) {
    2.58 +        SDL_DestroyCond(self->swapIntervalCond);
    2.59 +    }
    2.60 +    if (self->swapIntervalMutex) {
    2.61 +        SDL_DestroyMutex(self->swapIntervalMutex);
    2.62 +    }
    2.63 +    [super dealloc];
    2.64 +}
    2.65  @end
    2.66  
    2.67  
    2.68 @@ -368,21 +412,17 @@
    2.69  Cocoa_GL_SetSwapInterval(_THIS, int interval)
    2.70  { @autoreleasepool
    2.71  {
    2.72 -    NSOpenGLContext *nscontext;
    2.73 -    GLint value;
    2.74 +    SDLOpenGLContext *nscontext = (SDLOpenGLContext *) SDL_GL_GetCurrentContext();
    2.75      int status;
    2.76  
    2.77 -    if (interval < 0) {  /* no extension for this on Mac OS X at the moment. */
    2.78 -        return SDL_SetError("Late swap tearing currently unsupported");
    2.79 -    }
    2.80 -
    2.81 -    nscontext = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
    2.82 -    if (nscontext != nil) {
    2.83 -        value = interval;
    2.84 -        [nscontext setValues:&value forParameter:NSOpenGLCPSwapInterval];
    2.85 +    if (nscontext == nil) {
    2.86 +        status = SDL_SetError("No current OpenGL context");
    2.87 +    } else {
    2.88 +        SDL_LockMutex(nscontext->swapIntervalMutex);
    2.89 +        SDL_AtomicSet(&nscontext->swapIntervalsPassed, 0);
    2.90 +        SDL_AtomicSet(&nscontext->swapIntervalSetting, interval);
    2.91 +        SDL_UnlockMutex(nscontext->swapIntervalMutex);
    2.92          status = 0;
    2.93 -    } else {
    2.94 -        status = SDL_SetError("No current OpenGL context");
    2.95      }
    2.96  
    2.97      return status;
    2.98 @@ -392,17 +432,8 @@
    2.99  Cocoa_GL_GetSwapInterval(_THIS)
   2.100  { @autoreleasepool
   2.101  {
   2.102 -    NSOpenGLContext *nscontext;
   2.103 -    GLint value;
   2.104 -    int status = 0;
   2.105 -
   2.106 -    nscontext = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
   2.107 -    if (nscontext != nil) {
   2.108 -        [nscontext getValues:&value forParameter:NSOpenGLCPSwapInterval];
   2.109 -        status = (int)value;
   2.110 -    }
   2.111 -
   2.112 -    return status;
   2.113 +    SDLOpenGLContext *nscontext = (SDLOpenGLContext *) SDL_GL_GetCurrentContext();
   2.114 +    return nscontext ? SDL_AtomicGet(&nscontext->swapIntervalSetting) : 0;
   2.115  }}
   2.116  
   2.117  int
   2.118 @@ -411,6 +442,25 @@
   2.119  {
   2.120      SDLOpenGLContext* nscontext = (SDLOpenGLContext*)SDL_GL_GetCurrentContext();
   2.121      SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
   2.122 +    const int setting = SDL_AtomicGet(&nscontext->swapIntervalSetting);
   2.123 +
   2.124 +    if (setting == 0) {
   2.125 +        /* nothing to do if vsync is disabled, don't even lock */
   2.126 +    } else if (setting < 0) {  /* late swap tearing */
   2.127 +        SDL_LockMutex(nscontext->swapIntervalMutex);
   2.128 +        while (SDL_AtomicGet(&nscontext->swapIntervalsPassed) == 0) {
   2.129 +            SDL_CondWait(nscontext->swapIntervalCond, nscontext->swapIntervalMutex);
   2.130 +        }
   2.131 +        SDL_AtomicSet(&nscontext->swapIntervalsPassed, 0);
   2.132 +        SDL_UnlockMutex(nscontext->swapIntervalMutex);
   2.133 +    } else {
   2.134 +        SDL_LockMutex(nscontext->swapIntervalMutex);
   2.135 +        do {  /* always wait here so we know we just hit a swap interval. */
   2.136 +            SDL_CondWait(nscontext->swapIntervalCond, nscontext->swapIntervalMutex);
   2.137 +        } while ((SDL_AtomicGet(&nscontext->swapIntervalsPassed) % setting) != 0);
   2.138 +        SDL_AtomicSet(&nscontext->swapIntervalsPassed, 0);
   2.139 +        SDL_UnlockMutex(nscontext->swapIntervalMutex);
   2.140 +    }
   2.141  
   2.142      /* on 10.14 ("Mojave") and later, this deadlocks if two contexts in two
   2.143         threads try to swap at the same time, so put a mutex around it. */