src/video/cocoa/SDL_cocoaopengl.m
author Alex Szpakowski <slime73@gmail.com>
Wed, 17 Apr 2019 20:14:40 -0300
changeset 12706 d7782848eb50
parent 12702 0ff5bbe35bf5
child 12840 1b6f67e84802
permissions -rw-r--r--
macOS: Fix compilation when using the 10.9 SDK or older.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2019 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 #include "../../SDL_internal.h"
    22 
    23 /* NSOpenGL implementation of SDL OpenGL support */
    24 
    25 #if SDL_VIDEO_OPENGL_CGL
    26 #include "SDL_cocoavideo.h"
    27 #include "SDL_cocoaopengl.h"
    28 #include "SDL_cocoaopengles.h"
    29 
    30 #include <OpenGL/CGLTypes.h>
    31 #include <OpenGL/OpenGL.h>
    32 #include <OpenGL/CGLRenderers.h>
    33 
    34 #include "SDL_loadso.h"
    35 #include "SDL_opengl.h"
    36 
    37 #define DEFAULT_OPENGL  "/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib"
    38 
    39 static CVReturn
    40 DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
    41 {
    42     SDLOpenGLContext *nscontext = (SDLOpenGLContext *) displayLinkContext;
    43 
    44     /*printf("DISPLAY LINK! %u\n", (unsigned int) SDL_GetTicks()); */
    45     const int setting = SDL_AtomicGet(&nscontext->swapIntervalSetting);
    46     if (setting != 0) { /* nothing to do if vsync is disabled, don't even lock */
    47         SDL_LockMutex(nscontext->swapIntervalMutex);
    48         SDL_AtomicAdd(&nscontext->swapIntervalsPassed, 1);
    49         SDL_CondSignal(nscontext->swapIntervalCond);
    50         SDL_UnlockMutex(nscontext->swapIntervalMutex);
    51     }
    52 
    53     return kCVReturnSuccess;
    54 }
    55 
    56 @implementation SDLOpenGLContext : NSOpenGLContext
    57 
    58 - (id)initWithFormat:(NSOpenGLPixelFormat *)format
    59         shareContext:(NSOpenGLContext *)share
    60 {
    61     self = [super initWithFormat:format shareContext:share];
    62     if (self) {
    63         SDL_AtomicSet(&self->dirty, 0);
    64         self->window = NULL;
    65         SDL_AtomicSet(&self->swapIntervalSetting, 0);
    66         SDL_AtomicSet(&self->swapIntervalsPassed, 0);
    67         self->swapIntervalCond = SDL_CreateCond();
    68         self->swapIntervalMutex = SDL_CreateMutex();
    69         if (!self->swapIntervalCond || !self->swapIntervalMutex) {
    70             [self release];
    71             return nil;
    72         }
    73 
    74         /* !!! FIXME: check return values. */
    75         CVDisplayLinkCreateWithActiveCGDisplays(&self->displayLink);
    76         CVDisplayLinkSetOutputCallback(self->displayLink, &DisplayLinkCallback, self);
    77         CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(self->displayLink, [self CGLContextObj], [format CGLPixelFormatObj]);
    78         CVDisplayLinkStart(displayLink);
    79     }
    80     return self;
    81 }
    82 
    83 - (void)scheduleUpdate
    84 {
    85     SDL_AtomicAdd(&self->dirty, 1);
    86 }
    87 
    88 /* This should only be called on the thread on which a user is using the context. */
    89 - (void)updateIfNeeded
    90 {
    91     int value = SDL_AtomicSet(&self->dirty, 0);
    92     if (value > 0) {
    93         /* We call the real underlying update here, since -[SDLOpenGLContext update] just calls us. */
    94         [super update];
    95     }
    96 }
    97 
    98 /* This should only be called on the thread on which a user is using the context. */
    99 - (void)update
   100 {
   101     /* This ensures that regular 'update' calls clear the atomic dirty flag. */
   102     [self scheduleUpdate];
   103     [self updateIfNeeded];
   104 }
   105 
   106 /* Updates the drawable for the contexts and manages related state. */
   107 - (void)setWindow:(SDL_Window *)newWindow
   108 {
   109     if (self->window) {
   110         SDL_WindowData *oldwindowdata = (SDL_WindowData *)self->window->driverdata;
   111 
   112         /* Make sure to remove us from the old window's context list, or we'll get scheduled updates from it too. */
   113         NSMutableArray *contexts = oldwindowdata->nscontexts;
   114         @synchronized (contexts) {
   115             [contexts removeObject:self];
   116         }
   117     }
   118 
   119     self->window = newWindow;
   120 
   121     if (newWindow) {
   122         SDL_WindowData *windowdata = (SDL_WindowData *)newWindow->driverdata;
   123 
   124         /* Now sign up for scheduled updates for the new window. */
   125         NSMutableArray *contexts = windowdata->nscontexts;
   126         @synchronized (contexts) {
   127             [contexts addObject:self];
   128         }
   129 
   130         if ([self view] != [windowdata->nswindow contentView]) {
   131             [self setView:[windowdata->nswindow contentView]];
   132             if (self == [NSOpenGLContext currentContext]) {
   133                 [self update];
   134             } else {
   135                 [self scheduleUpdate];
   136             }
   137         }
   138     } else {
   139         [self clearDrawable];
   140         if (self == [NSOpenGLContext currentContext]) {
   141             [self update];
   142         } else {
   143             [self scheduleUpdate];
   144         }
   145     }
   146 }
   147 
   148 - (void)dealloc
   149 {
   150     if (self->displayLink) {
   151         CVDisplayLinkRelease(self->displayLink);
   152     }
   153     if (self->swapIntervalCond) {
   154         SDL_DestroyCond(self->swapIntervalCond);
   155     }
   156     if (self->swapIntervalMutex) {
   157         SDL_DestroyMutex(self->swapIntervalMutex);
   158     }
   159     [super dealloc];
   160 }
   161 @end
   162 
   163 
   164 int
   165 Cocoa_GL_LoadLibrary(_THIS, const char *path)
   166 {
   167     /* Load the OpenGL library */
   168     if (path == NULL) {
   169         path = SDL_getenv("SDL_OPENGL_LIBRARY");
   170     }
   171     if (path == NULL) {
   172         path = DEFAULT_OPENGL;
   173     }
   174     _this->gl_config.dll_handle = SDL_LoadObject(path);
   175     if (!_this->gl_config.dll_handle) {
   176         return -1;
   177     }
   178     SDL_strlcpy(_this->gl_config.driver_path, path,
   179                 SDL_arraysize(_this->gl_config.driver_path));
   180     return 0;
   181 }
   182 
   183 void *
   184 Cocoa_GL_GetProcAddress(_THIS, const char *proc)
   185 {
   186     return SDL_LoadFunction(_this->gl_config.dll_handle, proc);
   187 }
   188 
   189 void
   190 Cocoa_GL_UnloadLibrary(_THIS)
   191 {
   192     SDL_UnloadObject(_this->gl_config.dll_handle);
   193     _this->gl_config.dll_handle = NULL;
   194 }
   195 
   196 SDL_GLContext
   197 Cocoa_GL_CreateContext(_THIS, SDL_Window * window)
   198 { @autoreleasepool
   199 {
   200     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
   201     SDL_DisplayData *displaydata = (SDL_DisplayData *)display->driverdata;
   202     SDL_bool lion_or_later = floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6;
   203     NSOpenGLPixelFormatAttribute attr[32];
   204     NSOpenGLPixelFormat *fmt;
   205     SDLOpenGLContext *context;
   206     NSOpenGLContext *share_context = nil;
   207     int i = 0;
   208     const char *glversion;
   209     int glversion_major;
   210     int glversion_minor;
   211     int interval;
   212 
   213     if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) {
   214 #if SDL_VIDEO_OPENGL_EGL
   215         /* Switch to EGL based functions */
   216         Cocoa_GL_UnloadLibrary(_this);
   217         _this->GL_LoadLibrary = Cocoa_GLES_LoadLibrary;
   218         _this->GL_GetProcAddress = Cocoa_GLES_GetProcAddress;
   219         _this->GL_UnloadLibrary = Cocoa_GLES_UnloadLibrary;
   220         _this->GL_CreateContext = Cocoa_GLES_CreateContext;
   221         _this->GL_MakeCurrent = Cocoa_GLES_MakeCurrent;
   222         _this->GL_SetSwapInterval = Cocoa_GLES_SetSwapInterval;
   223         _this->GL_GetSwapInterval = Cocoa_GLES_GetSwapInterval;
   224         _this->GL_SwapWindow = Cocoa_GLES_SwapWindow;
   225         _this->GL_DeleteContext = Cocoa_GLES_DeleteContext;
   226         
   227         if (Cocoa_GLES_LoadLibrary(_this, NULL) != 0) {
   228             return NULL;
   229         }
   230         return Cocoa_GLES_CreateContext(_this, window);
   231 #else
   232         SDL_SetError("SDL not configured with EGL support");
   233         return NULL;
   234 #endif
   235     }
   236     if ((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_CORE) && !lion_or_later) {
   237         SDL_SetError ("OpenGL Core Profile is not supported on this platform version");
   238         return NULL;
   239     }
   240 
   241     attr[i++] = NSOpenGLPFAAllowOfflineRenderers;
   242 
   243     /* specify a profile if we're on Lion (10.7) or later. */
   244     if (lion_or_later) {
   245         NSOpenGLPixelFormatAttribute profile = NSOpenGLProfileVersionLegacy;
   246         if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_CORE) {
   247             profile = NSOpenGLProfileVersion3_2Core;
   248         }
   249         attr[i++] = NSOpenGLPFAOpenGLProfile;
   250         attr[i++] = profile;
   251     }
   252 
   253     attr[i++] = NSOpenGLPFAColorSize;
   254     attr[i++] = SDL_BYTESPERPIXEL(display->current_mode.format)*8;
   255 
   256     attr[i++] = NSOpenGLPFADepthSize;
   257     attr[i++] = _this->gl_config.depth_size;
   258 
   259     if (_this->gl_config.double_buffer) {
   260         attr[i++] = NSOpenGLPFADoubleBuffer;
   261     }
   262 
   263     if (_this->gl_config.stereo) {
   264         attr[i++] = NSOpenGLPFAStereo;
   265     }
   266 
   267     if (_this->gl_config.stencil_size) {
   268         attr[i++] = NSOpenGLPFAStencilSize;
   269         attr[i++] = _this->gl_config.stencil_size;
   270     }
   271 
   272     if ((_this->gl_config.accum_red_size +
   273          _this->gl_config.accum_green_size +
   274          _this->gl_config.accum_blue_size +
   275          _this->gl_config.accum_alpha_size) > 0) {
   276         attr[i++] = NSOpenGLPFAAccumSize;
   277         attr[i++] = _this->gl_config.accum_red_size + _this->gl_config.accum_green_size + _this->gl_config.accum_blue_size + _this->gl_config.accum_alpha_size;
   278     }
   279 
   280     if (_this->gl_config.multisamplebuffers) {
   281         attr[i++] = NSOpenGLPFASampleBuffers;
   282         attr[i++] = _this->gl_config.multisamplebuffers;
   283     }
   284 
   285     if (_this->gl_config.multisamplesamples) {
   286         attr[i++] = NSOpenGLPFASamples;
   287         attr[i++] = _this->gl_config.multisamplesamples;
   288         attr[i++] = NSOpenGLPFANoRecovery;
   289     }
   290 
   291     if (_this->gl_config.accelerated >= 0) {
   292         if (_this->gl_config.accelerated) {
   293             attr[i++] = NSOpenGLPFAAccelerated;
   294         } else {
   295             attr[i++] = NSOpenGLPFARendererID;
   296             attr[i++] = kCGLRendererGenericFloatID;
   297         }
   298     }
   299 
   300     attr[i++] = NSOpenGLPFAScreenMask;
   301     attr[i++] = CGDisplayIDToOpenGLDisplayMask(displaydata->display);
   302     attr[i] = 0;
   303 
   304     fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
   305     if (fmt == nil) {
   306         SDL_SetError("Failed creating OpenGL pixel format");
   307         return NULL;
   308     }
   309 
   310     if (_this->gl_config.share_with_current_context) {
   311         share_context = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
   312     }
   313 
   314     context = [[SDLOpenGLContext alloc] initWithFormat:fmt shareContext:share_context];
   315 
   316     [fmt release];
   317 
   318     if (context == nil) {
   319         SDL_SetError("Failed creating OpenGL context");
   320         return NULL;
   321     }
   322 
   323     /* vsync is handled separately by synchronizing with a display link. */
   324     interval = 0;
   325     [context setValues:&interval forParameter:NSOpenGLCPSwapInterval];
   326 
   327     if ( Cocoa_GL_MakeCurrent(_this, window, context) < 0 ) {
   328         Cocoa_GL_DeleteContext(_this, context);
   329         SDL_SetError("Failed making OpenGL context current");
   330         return NULL;
   331     }
   332 
   333     if (_this->gl_config.major_version < 3 &&
   334         _this->gl_config.profile_mask == 0 &&
   335         _this->gl_config.flags == 0) {
   336         /* This is a legacy profile, so to match other backends, we're done. */
   337     } else {
   338         const GLubyte *(APIENTRY * glGetStringFunc)(GLenum);
   339 
   340         glGetStringFunc = (const GLubyte *(APIENTRY *)(GLenum)) SDL_GL_GetProcAddress("glGetString");
   341         if (!glGetStringFunc) {
   342             Cocoa_GL_DeleteContext(_this, context);
   343             SDL_SetError ("Failed getting OpenGL glGetString entry point");
   344             return NULL;
   345         }
   346 
   347         glversion = (const char *)glGetStringFunc(GL_VERSION);
   348         if (glversion == NULL) {
   349             Cocoa_GL_DeleteContext(_this, context);
   350             SDL_SetError ("Failed getting OpenGL context version");
   351             return NULL;
   352         }
   353 
   354         if (SDL_sscanf(glversion, "%d.%d", &glversion_major, &glversion_minor) != 2) {
   355             Cocoa_GL_DeleteContext(_this, context);
   356             SDL_SetError ("Failed parsing OpenGL context version");
   357             return NULL;
   358         }
   359 
   360         if ((glversion_major < _this->gl_config.major_version) ||
   361            ((glversion_major == _this->gl_config.major_version) && (glversion_minor < _this->gl_config.minor_version))) {
   362             Cocoa_GL_DeleteContext(_this, context);
   363             SDL_SetError ("Failed creating OpenGL context at version requested");
   364             return NULL;
   365         }
   366 
   367         /* In the future we'll want to do this, but to match other platforms
   368            we'll leave the OpenGL version the way it is for now
   369          */
   370         /*_this->gl_config.major_version = glversion_major;*/
   371         /*_this->gl_config.minor_version = glversion_minor;*/
   372     }
   373     return context;
   374 }}
   375 
   376 int
   377 Cocoa_GL_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context)
   378 { @autoreleasepool
   379 {
   380     if (context) {
   381         SDLOpenGLContext *nscontext = (SDLOpenGLContext *)context;
   382         [nscontext setWindow:window];
   383         [nscontext updateIfNeeded];
   384         [nscontext makeCurrentContext];
   385     } else {
   386         [NSOpenGLContext clearCurrentContext];
   387     }
   388 
   389     return 0;
   390 }}
   391 
   392 void
   393 Cocoa_GL_GetDrawableSize(_THIS, SDL_Window * window, int * w, int * h)
   394 {
   395     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
   396     NSView *contentView = [windata->nswindow contentView];
   397     NSRect viewport = [contentView bounds];
   398 
   399     if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
   400         /* This gives us the correct viewport for a Retina-enabled view, only
   401          * supported on 10.7+. */
   402         if ([contentView respondsToSelector:@selector(convertRectToBacking:)]) {
   403             viewport = [contentView convertRectToBacking:viewport];
   404         }
   405     }
   406 
   407     if (w) {
   408         *w = viewport.size.width;
   409     }
   410 
   411     if (h) {
   412         *h = viewport.size.height;
   413     }
   414 }
   415 
   416 int
   417 Cocoa_GL_SetSwapInterval(_THIS, int interval)
   418 { @autoreleasepool
   419 {
   420     SDLOpenGLContext *nscontext = (SDLOpenGLContext *) SDL_GL_GetCurrentContext();
   421     int status;
   422 
   423     if (nscontext == nil) {
   424         status = SDL_SetError("No current OpenGL context");
   425     } else {
   426         SDL_LockMutex(nscontext->swapIntervalMutex);
   427         SDL_AtomicSet(&nscontext->swapIntervalsPassed, 0);
   428         SDL_AtomicSet(&nscontext->swapIntervalSetting, interval);
   429         SDL_UnlockMutex(nscontext->swapIntervalMutex);
   430         status = 0;
   431     }
   432 
   433     return status;
   434 }}
   435 
   436 int
   437 Cocoa_GL_GetSwapInterval(_THIS)
   438 { @autoreleasepool
   439 {
   440     SDLOpenGLContext *nscontext = (SDLOpenGLContext *) SDL_GL_GetCurrentContext();
   441     return nscontext ? SDL_AtomicGet(&nscontext->swapIntervalSetting) : 0;
   442 }}
   443 
   444 int
   445 Cocoa_GL_SwapWindow(_THIS, SDL_Window * window)
   446 { @autoreleasepool
   447 {
   448     SDLOpenGLContext* nscontext = (SDLOpenGLContext*)SDL_GL_GetCurrentContext();
   449     SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
   450     const int setting = SDL_AtomicGet(&nscontext->swapIntervalSetting);
   451 
   452     if (setting == 0) {
   453         /* nothing to do if vsync is disabled, don't even lock */
   454     } else if (setting < 0) {  /* late swap tearing */
   455         SDL_LockMutex(nscontext->swapIntervalMutex);
   456         while (SDL_AtomicGet(&nscontext->swapIntervalsPassed) == 0) {
   457             SDL_CondWait(nscontext->swapIntervalCond, nscontext->swapIntervalMutex);
   458         }
   459         SDL_AtomicSet(&nscontext->swapIntervalsPassed, 0);
   460         SDL_UnlockMutex(nscontext->swapIntervalMutex);
   461     } else {
   462         SDL_LockMutex(nscontext->swapIntervalMutex);
   463         do {  /* always wait here so we know we just hit a swap interval. */
   464             SDL_CondWait(nscontext->swapIntervalCond, nscontext->swapIntervalMutex);
   465         } while ((SDL_AtomicGet(&nscontext->swapIntervalsPassed) % setting) != 0);
   466         SDL_AtomicSet(&nscontext->swapIntervalsPassed, 0);
   467         SDL_UnlockMutex(nscontext->swapIntervalMutex);
   468     }
   469 
   470     /* on 10.14 ("Mojave") and later, this deadlocks if two contexts in two
   471        threads try to swap at the same time, so put a mutex around it. */
   472     SDL_LockMutex(videodata->swaplock);
   473     [nscontext flushBuffer];
   474     [nscontext updateIfNeeded];
   475     SDL_UnlockMutex(videodata->swaplock);
   476     return 0;
   477 }}
   478 
   479 void
   480 Cocoa_GL_DeleteContext(_THIS, SDL_GLContext context)
   481 { @autoreleasepool
   482 {
   483     SDLOpenGLContext *nscontext = (SDLOpenGLContext *)context;
   484 
   485     [nscontext setWindow:NULL];
   486     [nscontext release];
   487 }}
   488 
   489 #endif /* SDL_VIDEO_OPENGL_CGL */
   490 
   491 /* vi: set ts=4 sw=4 expandtab: */