src/video/cocoa/SDL_cocoaopengl.m
author Alex Szpakowski <slime73@gmail.com>
Sat, 13 Jul 2019 17:04:02 -0300
changeset 12940 1e6980ce45c0
parent 12863 bddd4ba329cc
permissions -rw-r--r--
macOS: Fix SDL_GL_CreateContext/MakeCurrent on non-main threads causing a Main Thread Checker warning when built with Xcode 11 / the macOS 10.15 SDK.

Fixes bug #4714.
     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 /* We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. */
    40 #ifdef __clang__
    41 #pragma clang diagnostic push
    42 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
    43 #endif
    44 
    45 @implementation SDLOpenGLContext : NSOpenGLContext
    46 
    47 - (id)initWithFormat:(NSOpenGLPixelFormat *)format
    48         shareContext:(NSOpenGLContext *)share
    49 {
    50     self = [super initWithFormat:format shareContext:share];
    51     if (self) {
    52         SDL_AtomicSet(&self->dirty, 0);
    53         self->window = NULL;
    54     }
    55     return self;
    56 }
    57 
    58 - (void)scheduleUpdate
    59 {
    60     SDL_AtomicAdd(&self->dirty, 1);
    61 }
    62 
    63 /* This should only be called on the thread on which a user is using the context. */
    64 - (void)updateIfNeeded
    65 {
    66     int value = SDL_AtomicSet(&self->dirty, 0);
    67     if (value > 0) {
    68         /* We call the real underlying update here, since -[SDLOpenGLContext update] just calls us. */
    69         [super update];
    70     }
    71 }
    72 
    73 /* This should only be called on the thread on which a user is using the context. */
    74 - (void)update
    75 {
    76     /* This ensures that regular 'update' calls clear the atomic dirty flag. */
    77     [self scheduleUpdate];
    78     [self updateIfNeeded];
    79 }
    80 
    81 /* Updates the drawable for the contexts and manages related state. */
    82 - (void)setWindow:(SDL_Window *)newWindow
    83 {
    84     if (self->window) {
    85         SDL_WindowData *oldwindowdata = (SDL_WindowData *)self->window->driverdata;
    86 
    87         /* Make sure to remove us from the old window's context list, or we'll get scheduled updates from it too. */
    88         NSMutableArray *contexts = oldwindowdata->nscontexts;
    89         @synchronized (contexts) {
    90             [contexts removeObject:self];
    91         }
    92     }
    93 
    94     self->window = newWindow;
    95 
    96     if (newWindow) {
    97         SDL_WindowData *windowdata = (SDL_WindowData *)newWindow->driverdata;
    98         NSView *contentview = windowdata->sdlContentView;
    99 
   100         /* This should never be nil since sdlContentView is only nil if the
   101            window was created via SDL_CreateWindowFrom, and SDL doesn't allow
   102            OpenGL contexts to be created in that case. However, it doesn't hurt
   103            to check. */
   104         if (contentview == nil) {
   105             /* Prefer to access the cached content view above instead of this,
   106                since as of Xcode 11 + SDK 10.15, [window contentView] causes
   107                Apple's Main Thread Checker to output a warning. */
   108             contentview = [windowdata->nswindow contentView];
   109         }
   110 
   111         /* Now sign up for scheduled updates for the new window. */
   112         NSMutableArray *contexts = windowdata->nscontexts;
   113         @synchronized (contexts) {
   114             [contexts addObject:self];
   115         }
   116 
   117         if ([self view] != contentview) {
   118             [self setView:contentview];
   119             if (self == [NSOpenGLContext currentContext]) {
   120                 [self update];
   121             } else {
   122                 [self scheduleUpdate];
   123             }
   124         }
   125     } else {
   126         [self clearDrawable];
   127         if (self == [NSOpenGLContext currentContext]) {
   128             [self update];
   129         } else {
   130             [self scheduleUpdate];
   131         }
   132     }
   133 }
   134 
   135 @end
   136 
   137 
   138 int
   139 Cocoa_GL_LoadLibrary(_THIS, const char *path)
   140 {
   141     /* Load the OpenGL library */
   142     if (path == NULL) {
   143         path = SDL_getenv("SDL_OPENGL_LIBRARY");
   144     }
   145     if (path == NULL) {
   146         path = DEFAULT_OPENGL;
   147     }
   148     _this->gl_config.dll_handle = SDL_LoadObject(path);
   149     if (!_this->gl_config.dll_handle) {
   150         return -1;
   151     }
   152     SDL_strlcpy(_this->gl_config.driver_path, path,
   153                 SDL_arraysize(_this->gl_config.driver_path));
   154     return 0;
   155 }
   156 
   157 void *
   158 Cocoa_GL_GetProcAddress(_THIS, const char *proc)
   159 {
   160     return SDL_LoadFunction(_this->gl_config.dll_handle, proc);
   161 }
   162 
   163 void
   164 Cocoa_GL_UnloadLibrary(_THIS)
   165 {
   166     SDL_UnloadObject(_this->gl_config.dll_handle);
   167     _this->gl_config.dll_handle = NULL;
   168 }
   169 
   170 SDL_GLContext
   171 Cocoa_GL_CreateContext(_THIS, SDL_Window * window)
   172 { @autoreleasepool
   173 {
   174     SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
   175     SDL_DisplayData *displaydata = (SDL_DisplayData *)display->driverdata;
   176     SDL_bool lion_or_later = floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6;
   177     NSOpenGLPixelFormatAttribute attr[32];
   178     NSOpenGLPixelFormat *fmt;
   179     SDLOpenGLContext *context;
   180     NSOpenGLContext *share_context = nil;
   181     int i = 0;
   182     const char *glversion;
   183     int glversion_major;
   184     int glversion_minor;
   185 
   186     if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) {
   187 #if SDL_VIDEO_OPENGL_EGL
   188         /* Switch to EGL based functions */
   189         Cocoa_GL_UnloadLibrary(_this);
   190         _this->GL_LoadLibrary = Cocoa_GLES_LoadLibrary;
   191         _this->GL_GetProcAddress = Cocoa_GLES_GetProcAddress;
   192         _this->GL_UnloadLibrary = Cocoa_GLES_UnloadLibrary;
   193         _this->GL_CreateContext = Cocoa_GLES_CreateContext;
   194         _this->GL_MakeCurrent = Cocoa_GLES_MakeCurrent;
   195         _this->GL_SetSwapInterval = Cocoa_GLES_SetSwapInterval;
   196         _this->GL_GetSwapInterval = Cocoa_GLES_GetSwapInterval;
   197         _this->GL_SwapWindow = Cocoa_GLES_SwapWindow;
   198         _this->GL_DeleteContext = Cocoa_GLES_DeleteContext;
   199         
   200         if (Cocoa_GLES_LoadLibrary(_this, NULL) != 0) {
   201             return NULL;
   202         }
   203         return Cocoa_GLES_CreateContext(_this, window);
   204 #else
   205         SDL_SetError("SDL not configured with EGL support");
   206         return NULL;
   207 #endif
   208     }
   209     if ((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_CORE) && !lion_or_later) {
   210         SDL_SetError ("OpenGL Core Profile is not supported on this platform version");
   211         return NULL;
   212     }
   213 
   214     attr[i++] = NSOpenGLPFAAllowOfflineRenderers;
   215 
   216     /* specify a profile if we're on Lion (10.7) or later. */
   217     if (lion_or_later) {
   218         NSOpenGLPixelFormatAttribute profile = NSOpenGLProfileVersionLegacy;
   219         if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_CORE) {
   220             profile = NSOpenGLProfileVersion3_2Core;
   221         }
   222         attr[i++] = NSOpenGLPFAOpenGLProfile;
   223         attr[i++] = profile;
   224     }
   225 
   226     attr[i++] = NSOpenGLPFAColorSize;
   227     attr[i++] = SDL_BYTESPERPIXEL(display->current_mode.format)*8;
   228 
   229     attr[i++] = NSOpenGLPFADepthSize;
   230     attr[i++] = _this->gl_config.depth_size;
   231 
   232     if (_this->gl_config.double_buffer) {
   233         attr[i++] = NSOpenGLPFADoubleBuffer;
   234     }
   235 
   236     if (_this->gl_config.stereo) {
   237         attr[i++] = NSOpenGLPFAStereo;
   238     }
   239 
   240     if (_this->gl_config.stencil_size) {
   241         attr[i++] = NSOpenGLPFAStencilSize;
   242         attr[i++] = _this->gl_config.stencil_size;
   243     }
   244 
   245     if ((_this->gl_config.accum_red_size +
   246          _this->gl_config.accum_green_size +
   247          _this->gl_config.accum_blue_size +
   248          _this->gl_config.accum_alpha_size) > 0) {
   249         attr[i++] = NSOpenGLPFAAccumSize;
   250         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;
   251     }
   252 
   253     if (_this->gl_config.multisamplebuffers) {
   254         attr[i++] = NSOpenGLPFASampleBuffers;
   255         attr[i++] = _this->gl_config.multisamplebuffers;
   256     }
   257 
   258     if (_this->gl_config.multisamplesamples) {
   259         attr[i++] = NSOpenGLPFASamples;
   260         attr[i++] = _this->gl_config.multisamplesamples;
   261         attr[i++] = NSOpenGLPFANoRecovery;
   262     }
   263 
   264     if (_this->gl_config.accelerated >= 0) {
   265         if (_this->gl_config.accelerated) {
   266             attr[i++] = NSOpenGLPFAAccelerated;
   267         } else {
   268             attr[i++] = NSOpenGLPFARendererID;
   269             attr[i++] = kCGLRendererGenericFloatID;
   270         }
   271     }
   272 
   273     attr[i++] = NSOpenGLPFAScreenMask;
   274     attr[i++] = CGDisplayIDToOpenGLDisplayMask(displaydata->display);
   275     attr[i] = 0;
   276 
   277     fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
   278     if (fmt == nil) {
   279         SDL_SetError("Failed creating OpenGL pixel format");
   280         return NULL;
   281     }
   282 
   283     if (_this->gl_config.share_with_current_context) {
   284         share_context = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
   285     }
   286 
   287     context = [[SDLOpenGLContext alloc] initWithFormat:fmt shareContext:share_context];
   288 
   289     [fmt release];
   290 
   291     if (context == nil) {
   292         SDL_SetError("Failed creating OpenGL context");
   293         return NULL;
   294     }
   295 
   296     if ( Cocoa_GL_MakeCurrent(_this, window, context) < 0 ) {
   297         Cocoa_GL_DeleteContext(_this, context);
   298         SDL_SetError("Failed making OpenGL context current");
   299         return NULL;
   300     }
   301 
   302     if (_this->gl_config.major_version < 3 &&
   303         _this->gl_config.profile_mask == 0 &&
   304         _this->gl_config.flags == 0) {
   305         /* This is a legacy profile, so to match other backends, we're done. */
   306     } else {
   307         const GLubyte *(APIENTRY * glGetStringFunc)(GLenum);
   308 
   309         glGetStringFunc = (const GLubyte *(APIENTRY *)(GLenum)) SDL_GL_GetProcAddress("glGetString");
   310         if (!glGetStringFunc) {
   311             Cocoa_GL_DeleteContext(_this, context);
   312             SDL_SetError ("Failed getting OpenGL glGetString entry point");
   313             return NULL;
   314         }
   315 
   316         glversion = (const char *)glGetStringFunc(GL_VERSION);
   317         if (glversion == NULL) {
   318             Cocoa_GL_DeleteContext(_this, context);
   319             SDL_SetError ("Failed getting OpenGL context version");
   320             return NULL;
   321         }
   322 
   323         if (SDL_sscanf(glversion, "%d.%d", &glversion_major, &glversion_minor) != 2) {
   324             Cocoa_GL_DeleteContext(_this, context);
   325             SDL_SetError ("Failed parsing OpenGL context version");
   326             return NULL;
   327         }
   328 
   329         if ((glversion_major < _this->gl_config.major_version) ||
   330            ((glversion_major == _this->gl_config.major_version) && (glversion_minor < _this->gl_config.minor_version))) {
   331             Cocoa_GL_DeleteContext(_this, context);
   332             SDL_SetError ("Failed creating OpenGL context at version requested");
   333             return NULL;
   334         }
   335 
   336         /* In the future we'll want to do this, but to match other platforms
   337            we'll leave the OpenGL version the way it is for now
   338          */
   339         /*_this->gl_config.major_version = glversion_major;*/
   340         /*_this->gl_config.minor_version = glversion_minor;*/
   341     }
   342     return context;
   343 }}
   344 
   345 int
   346 Cocoa_GL_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context)
   347 { @autoreleasepool
   348 {
   349     if (context) {
   350         SDLOpenGLContext *nscontext = (SDLOpenGLContext *)context;
   351         [nscontext setWindow:window];
   352         [nscontext updateIfNeeded];
   353         [nscontext makeCurrentContext];
   354     } else {
   355         [NSOpenGLContext clearCurrentContext];
   356     }
   357 
   358     return 0;
   359 }}
   360 
   361 void
   362 Cocoa_GL_GetDrawableSize(_THIS, SDL_Window * window, int * w, int * h)
   363 {
   364     SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
   365     NSView *contentView = [windata->nswindow contentView];
   366     NSRect viewport = [contentView bounds];
   367 
   368     if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) {
   369         /* This gives us the correct viewport for a Retina-enabled view, only
   370          * supported on 10.7+. */
   371         if ([contentView respondsToSelector:@selector(convertRectToBacking:)]) {
   372             viewport = [contentView convertRectToBacking:viewport];
   373         }
   374     }
   375 
   376     if (w) {
   377         *w = viewport.size.width;
   378     }
   379 
   380     if (h) {
   381         *h = viewport.size.height;
   382     }
   383 }
   384 
   385 int
   386 Cocoa_GL_SetSwapInterval(_THIS, int interval)
   387 { @autoreleasepool
   388 {
   389     NSOpenGLContext *nscontext;
   390     GLint value;
   391     int status;
   392 
   393     if (interval < 0) {  /* no extension for this on Mac OS X at the moment. */
   394         return SDL_SetError("Late swap tearing currently unsupported");
   395     }
   396 
   397     nscontext = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
   398     if (nscontext != nil) {
   399         value = interval;
   400         [nscontext setValues:&value forParameter:NSOpenGLCPSwapInterval];
   401         status = 0;
   402     } else {
   403         status = SDL_SetError("No current OpenGL context");
   404     }
   405 
   406     return status;
   407 }}
   408 
   409 int
   410 Cocoa_GL_GetSwapInterval(_THIS)
   411 { @autoreleasepool
   412 {
   413     NSOpenGLContext *nscontext;
   414     GLint value;
   415     int status = 0;
   416 
   417     nscontext = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
   418     if (nscontext != nil) {
   419         [nscontext getValues:&value forParameter:NSOpenGLCPSwapInterval];
   420         status = (int)value;
   421     }
   422 
   423     return status;
   424 }}
   425 
   426 int
   427 Cocoa_GL_SwapWindow(_THIS, SDL_Window * window)
   428 { @autoreleasepool
   429 {
   430     SDLOpenGLContext* nscontext = (SDLOpenGLContext*)SDL_GL_GetCurrentContext();
   431     SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
   432 
   433     /* on 10.14 ("Mojave") and later, this deadlocks if two contexts in two
   434        threads try to swap at the same time, so put a mutex around it. */
   435     SDL_LockMutex(videodata->swaplock);
   436     [nscontext flushBuffer];
   437     [nscontext updateIfNeeded];
   438     SDL_UnlockMutex(videodata->swaplock);
   439     return 0;
   440 }}
   441 
   442 void
   443 Cocoa_GL_DeleteContext(_THIS, SDL_GLContext context)
   444 { @autoreleasepool
   445 {
   446     SDLOpenGLContext *nscontext = (SDLOpenGLContext *)context;
   447 
   448     [nscontext setWindow:NULL];
   449     [nscontext release];
   450 }}
   451 
   452 /* We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. */
   453 #ifdef __clang__
   454 #pragma clang diagnostic pop
   455 #endif
   456 
   457 #endif /* SDL_VIDEO_OPENGL_CGL */
   458 
   459 /* vi: set ts=4 sw=4 expandtab: */