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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
diff --git a/src/video/cocoa/SDL_cocoaopengl.h b/src/video/cocoa/SDL_cocoaopengl.h
index 81ca5ed..9f508dc 100644
--- a/src/video/cocoa/SDL_cocoaopengl.h
+++ b/src/video/cocoa/SDL_cocoaopengl.h
@@ -36,6 +36,11 @@ struct SDL_GLDriverData
@interface SDLOpenGLContext : NSOpenGLContext {
SDL_atomic_t dirty;
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
@@ -43,7 +48,7 @@ struct SDL_GLDriverData
- (void)scheduleUpdate;
- (void)updateIfNeeded;
- (void)setWindow:(SDL_Window *)window;
-
+- (void)dealloc;
@end
diff --git a/src/video/cocoa/SDL_cocoaopengl.m b/src/video/cocoa/SDL_cocoaopengl.m
index 9539c17..8d31664 100644
--- a/src/video/cocoa/SDL_cocoaopengl.m
+++ b/src/video/cocoa/SDL_cocoaopengl.m
@@ -36,6 +36,23 @@
#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
- (id)initWithFormat:(NSOpenGLPixelFormat *)format
@@ -45,6 +62,20 @@
if (self) {
SDL_AtomicSet(&self->dirty, 0);
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;
}
@@ -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
@@ -368,21 +412,17 @@ int
Cocoa_GL_SetSwapInterval(_THIS, int interval)
{ @autoreleasepool
{
- NSOpenGLContext *nscontext;
- GLint value;
+ SDLOpenGLContext *nscontext = (SDLOpenGLContext *) SDL_GL_GetCurrentContext();
int status;
- if (interval < 0) { /* no extension for this on Mac OS X at the moment. */
- 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 {
+ if (nscontext == nil) {
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;
@@ -392,17 +432,8 @@ int
Cocoa_GL_GetSwapInterval(_THIS)
{ @autoreleasepool
{
- NSOpenGLContext *nscontext;
- GLint value;
- int status = 0;
-
- nscontext = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
- if (nscontext != nil) {
- [nscontext getValues:&value forParameter:NSOpenGLCPSwapInterval];
- status = (int)value;
- }
-
- return status;
+ SDLOpenGLContext *nscontext = (SDLOpenGLContext *) SDL_GL_GetCurrentContext();
+ return nscontext ? SDL_AtomicGet(&nscontext->swapIntervalSetting) : 0;
}}
int
@@ -411,6 +442,25 @@ Cocoa_GL_SwapWindow(_THIS, SDL_Window * window)
{
SDLOpenGLContext* nscontext = (SDLOpenGLContext*)SDL_GL_GetCurrentContext();
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
threads try to swap at the same time, so put a mutex around it. */