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