Atlas - SDL_cocoaopengl.m

Home / ext / SDL / src / video / cocoa Lines: 3 | Size: 19563 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)]
[FILE BEGIN]
1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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#ifdef 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 <SDL3/SDL_opengl.h> 35#include "../../SDL_hints_c.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// _Nullable is available starting Xcode 7 46#ifdef __has_feature 47#if __has_feature(nullability) 48#define HAS_FEATURE_NULLABLE 49#endif 50#endif 51#ifndef HAS_FEATURE_NULLABLE 52#define _Nullable 53#endif 54 55static bool SDL_opengl_async_dispatch = false; 56 57static void SDLCALL SDL_OpenGLAsyncDispatchChanged(void *userdata, const char *name, const char *oldValue, const char *hint) 58{ 59 SDL_opengl_async_dispatch = SDL_GetStringBoolean(hint, false); 60} 61 62static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) 63{ 64 SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)displayLinkContext; 65 66 // printf("DISPLAY LINK! %u\n", (unsigned int) SDL_GetTicks()); 67 const int setting = SDL_GetAtomicInt(&nscontext->swapIntervalSetting); 68 if (setting != 0) { // nothing to do if vsync is disabled, don't even lock 69 SDL_LockMutex(nscontext->swapIntervalMutex); 70 SDL_AddAtomicInt(&nscontext->swapIntervalsPassed, 1); 71 SDL_SignalCondition(nscontext->swapIntervalCond); 72 SDL_UnlockMutex(nscontext->swapIntervalMutex); 73 } 74 75 return kCVReturnSuccess; 76} 77 78@implementation SDL3OpenGLContext : NSOpenGLContext 79 80- (id)initWithFormat:(NSOpenGLPixelFormat *)format 81 shareContext:(NSOpenGLContext *)share 82{ 83 self = [super initWithFormat:format shareContext:share]; 84 if (self) { 85 self.openglPixelFormat = format; 86 SDL_SetAtomicInt(&self->dirty, 0); 87 self->window = NULL; 88 SDL_SetAtomicInt(&self->swapIntervalSetting, 0); 89 SDL_SetAtomicInt(&self->swapIntervalsPassed, 0); 90 self->swapIntervalCond = SDL_CreateCondition(); 91 self->swapIntervalMutex = SDL_CreateMutex(); 92 if (!self->swapIntervalCond || !self->swapIntervalMutex) { 93 return nil; 94 } 95 96 // !!! FIXME: check return values. 97 CVDisplayLinkCreateWithActiveCGDisplays(&self->displayLink); 98 CVDisplayLinkSetOutputCallback(self->displayLink, &DisplayLinkCallback, (__bridge void *_Nullable)self); 99 CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(self->displayLink, [self CGLContextObj], [format CGLPixelFormatObj]); 100 CVDisplayLinkStart(displayLink); 101 } 102 103 SDL_AddHintCallback(SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH, SDL_OpenGLAsyncDispatchChanged, NULL); 104 return self; 105} 106 107- (void)movedToNewScreen 108{ 109 if (self->displayLink) { 110 CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(self->displayLink, [self CGLContextObj], [[self openglPixelFormat] CGLPixelFormatObj]); 111 } 112} 113 114- (void)scheduleUpdate 115{ 116 SDL_AddAtomicInt(&self->dirty, 1); 117} 118 119// This should only be called on the thread on which a user is using the context. 120- (void)updateIfNeeded 121{ 122 const int value = SDL_SetAtomicInt(&self->dirty, 0); 123 if (value > 0) { 124 // We call the real underlying update here, since -[SDL3OpenGLContext update] just calls us. 125 [self explicitUpdate]; 126 } 127} 128 129// This should only be called on the thread on which a user is using the context. 130- (void)update 131{ 132 // This ensures that regular 'update' calls clear the atomic dirty flag. 133 [self scheduleUpdate]; 134 [self updateIfNeeded]; 135} 136 137// Updates the drawable for the contexts and manages related state. 138- (void)setWindow:(SDL_Window *)newWindow 139{ 140 if (self->window) { 141 SDL_CocoaWindowData *oldwindowdata = (__bridge SDL_CocoaWindowData *)self->window->internal; 142 143 // Make sure to remove us from the old window's context list, or we'll get scheduled updates from it too. 144 NSMutableArray *contexts = oldwindowdata.nscontexts; 145 @synchronized(contexts) { 146 [contexts removeObject:self]; 147 } 148 } 149 150 self->window = newWindow; 151 152 if (newWindow) { 153 SDL_CocoaWindowData *windowdata = (__bridge SDL_CocoaWindowData *)newWindow->internal; 154 NSView *contentview = windowdata.sdlContentView; 155 156 // Now sign up for scheduled updates for the new window. 157 NSMutableArray *contexts = windowdata.nscontexts; 158 @synchronized(contexts) { 159 [contexts addObject:self]; 160 } 161 162 if ([self view] != contentview) { 163 if ([NSThread isMainThread]) { 164 [self setView:contentview]; 165 } else { 166 dispatch_sync(dispatch_get_main_queue(), ^{ 167 [self setView:contentview]; 168 }); 169 } 170 if (self == [NSOpenGLContext currentContext]) { 171 [self explicitUpdate]; 172 } else { 173 [self scheduleUpdate]; 174 } 175 } 176 } else { 177 if ([NSThread isMainThread]) { 178 [self setView:nil]; 179 } else { 180 dispatch_sync(dispatch_get_main_queue(), ^{ [self setView:nil]; }); 181 } 182 } 183} 184 185- (SDL_Window *)window 186{ 187 return self->window; 188} 189 190- (void)explicitUpdate 191{ 192 if ([NSThread isMainThread]) { 193 [super update]; 194 } else { 195 if (SDL_opengl_async_dispatch) { 196 dispatch_async(dispatch_get_main_queue(), ^{ 197 [super update]; 198 }); 199 } else { 200 dispatch_sync(dispatch_get_main_queue(), ^{ 201 [super update]; 202 }); 203 } 204 } 205} 206 207- (void)cleanup 208{ 209 [self setWindow:NULL]; 210 211 SDL_RemoveHintCallback(SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH, SDL_OpenGLAsyncDispatchChanged, NULL); 212 if (self->displayLink) { 213 CVDisplayLinkRelease(self->displayLink); 214 self->displayLink = nil; 215 } 216 if (self->swapIntervalCond) { 217 SDL_DestroyCondition(self->swapIntervalCond); 218 self->swapIntervalCond = NULL; 219 } 220 if (self->swapIntervalMutex) { 221 SDL_DestroyMutex(self->swapIntervalMutex); 222 self->swapIntervalMutex = NULL; 223 } 224} 225 226@end 227 228bool Cocoa_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path) 229{ 230 // Load the OpenGL library 231 if (path == NULL) { 232 path = SDL_GetHint(SDL_HINT_OPENGL_LIBRARY); 233 } 234 if (path == NULL) { 235 path = DEFAULT_OPENGL; 236 } 237 _this->gl_config.dll_handle = SDL_LoadObject(path); 238 if (!_this->gl_config.dll_handle) { 239 return false; 240 } 241 SDL_strlcpy(_this->gl_config.driver_path, path, 242 SDL_arraysize(_this->gl_config.driver_path)); 243 return true; 244} 245 246SDL_FunctionPointer Cocoa_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc) 247{ 248 return SDL_LoadFunction(_this->gl_config.dll_handle, proc); 249} 250 251void Cocoa_GL_UnloadLibrary(SDL_VideoDevice *_this) 252{ 253 SDL_UnloadObject(_this->gl_config.dll_handle); 254 _this->gl_config.dll_handle = NULL; 255} 256 257SDL_GLContext Cocoa_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window) 258{ 259 @autoreleasepool { 260 SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window); 261 SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal; 262 NSOpenGLPixelFormatAttribute attr[32]; 263 NSOpenGLPixelFormat *fmt; 264 SDL3OpenGLContext *context; 265 SDL_GLContext sdlcontext; 266 NSOpenGLContext *share_context = nil; 267 int i = 0; 268 const char *glversion; 269 int glversion_major; 270 int glversion_minor; 271 NSOpenGLPixelFormatAttribute profile; 272 int interval; 273 int opaque; 274 275 if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) { 276#ifdef SDL_VIDEO_OPENGL_EGL 277 // Switch to EGL based functions 278 Cocoa_GL_UnloadLibrary(_this); 279 _this->GL_LoadLibrary = Cocoa_GLES_LoadLibrary; 280 _this->GL_GetProcAddress = Cocoa_GLES_GetProcAddress; 281 _this->GL_UnloadLibrary = Cocoa_GLES_UnloadLibrary; 282 _this->GL_CreateContext = Cocoa_GLES_CreateContext; 283 _this->GL_MakeCurrent = Cocoa_GLES_MakeCurrent; 284 _this->GL_SetSwapInterval = Cocoa_GLES_SetSwapInterval; 285 _this->GL_GetSwapInterval = Cocoa_GLES_GetSwapInterval; 286 _this->GL_SwapWindow = Cocoa_GLES_SwapWindow; 287 _this->GL_DestroyContext = Cocoa_GLES_DestroyContext; 288 289 if (!Cocoa_GLES_LoadLibrary(_this, NULL)) { 290 return NULL; 291 } 292 return Cocoa_GLES_CreateContext(_this, window); 293#else 294 SDL_SetError("SDL not configured with EGL support"); 295 return NULL; 296#endif 297 } 298 299 attr[i++] = NSOpenGLPFAAllowOfflineRenderers; 300 301 profile = NSOpenGLProfileVersionLegacy; 302 if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_CORE) { 303 profile = NSOpenGLProfileVersion3_2Core; 304 } 305 attr[i++] = NSOpenGLPFAOpenGLProfile; 306 attr[i++] = profile; 307 308 attr[i++] = NSOpenGLPFAColorSize; 309 attr[i++] = SDL_BYTESPERPIXEL(display->current_mode->format) * 8; 310 311 attr[i++] = NSOpenGLPFADepthSize; 312 attr[i++] = _this->gl_config.depth_size; 313 314 if (_this->gl_config.double_buffer) { 315 attr[i++] = NSOpenGLPFADoubleBuffer; 316 } 317 318 if (_this->gl_config.stereo) { 319 attr[i++] = NSOpenGLPFAStereo; 320 } 321 322 if (_this->gl_config.stencil_size) { 323 attr[i++] = NSOpenGLPFAStencilSize; 324 attr[i++] = _this->gl_config.stencil_size; 325 } 326 327 if ((_this->gl_config.accum_red_size + 328 _this->gl_config.accum_green_size + 329 _this->gl_config.accum_blue_size + 330 _this->gl_config.accum_alpha_size) > 0) { 331 attr[i++] = NSOpenGLPFAAccumSize; 332 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; 333 } 334 335 if (_this->gl_config.multisamplebuffers) { 336 attr[i++] = NSOpenGLPFASampleBuffers; 337 attr[i++] = _this->gl_config.multisamplebuffers; 338 } 339 340 if (_this->gl_config.multisamplesamples) { 341 attr[i++] = NSOpenGLPFASamples; 342 attr[i++] = _this->gl_config.multisamplesamples; 343 attr[i++] = NSOpenGLPFANoRecovery; 344 } 345 if (_this->gl_config.floatbuffers) { 346 attr[i++] = NSOpenGLPFAColorFloat; 347 } 348 349 if (_this->gl_config.accelerated >= 0) { 350 if (_this->gl_config.accelerated) { 351 attr[i++] = NSOpenGLPFAAccelerated; 352 } else { 353 attr[i++] = NSOpenGLPFARendererID; 354 attr[i++] = kCGLRendererGenericFloatID; 355 } 356 } 357 358 attr[i++] = NSOpenGLPFAScreenMask; 359 attr[i++] = CGDisplayIDToOpenGLDisplayMask(displaydata->display); 360 attr[i] = 0; 361 362 fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; 363 if (fmt == nil) { 364 SDL_SetError("Failed creating OpenGL pixel format"); 365 return NULL; 366 } 367 368 if (_this->gl_config.share_with_current_context) { 369 share_context = (__bridge NSOpenGLContext *)SDL_GL_GetCurrentContext(); 370 } 371 372 context = [[SDL3OpenGLContext alloc] initWithFormat:fmt shareContext:share_context]; 373 374 if (context == nil) { 375 SDL_SetError("Failed creating OpenGL context"); 376 return NULL; 377 } 378 379 sdlcontext = (SDL_GLContext)CFBridgingRetain(context); 380 381 // vsync is handled separately by synchronizing with a display link. 382 interval = 0; 383 [context setValues:&interval forParameter:NSOpenGLCPSwapInterval]; 384 385 opaque = (window->flags & SDL_WINDOW_TRANSPARENT) ? 0 : 1; 386 [context setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity]; 387 388 if (!Cocoa_GL_MakeCurrent(_this, window, sdlcontext)) { 389 SDL_GL_DestroyContext(sdlcontext); 390 SDL_SetError("Failed making OpenGL context current"); 391 return NULL; 392 } 393 394 if (_this->gl_config.major_version < 3 && 395 _this->gl_config.profile_mask == 0 && 396 _this->gl_config.flags == 0) { 397 // This is a legacy profile, so to match other backends, we're done. 398 } else { 399 const GLubyte *(APIENTRY * glGetStringFunc)(GLenum); 400 401 glGetStringFunc = (const GLubyte *(APIENTRY *)(GLenum))SDL_GL_GetProcAddress("glGetString"); 402 if (!glGetStringFunc) { 403 SDL_GL_DestroyContext(sdlcontext); 404 SDL_SetError("Failed getting OpenGL glGetString entry point"); 405 return NULL; 406 } 407 408 glversion = (const char *)glGetStringFunc(GL_VERSION); 409 if (glversion == NULL) { 410 SDL_GL_DestroyContext(sdlcontext); 411 SDL_SetError("Failed getting OpenGL context version"); 412 return NULL; 413 } 414 415 if (SDL_sscanf(glversion, "%d.%d", &glversion_major, &glversion_minor) != 2) { 416 SDL_GL_DestroyContext(sdlcontext); 417 SDL_SetError("Failed parsing OpenGL context version"); 418 return NULL; 419 } 420 421 if ((glversion_major < _this->gl_config.major_version) || 422 ((glversion_major == _this->gl_config.major_version) && (glversion_minor < _this->gl_config.minor_version))) { 423 SDL_GL_DestroyContext(sdlcontext); 424 SDL_SetError("Failed creating OpenGL context at version requested"); 425 return NULL; 426 } 427 428 /* In the future we'll want to do this, but to match other platforms 429 we'll leave the OpenGL version the way it is for now 430 */ 431 // _this->gl_config.major_version = glversion_major; 432 // _this->gl_config.minor_version = glversion_minor; 433 } 434 return sdlcontext; 435 } 436} 437 438bool Cocoa_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context) 439{ 440 @autoreleasepool { 441 if (context) { 442 SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)context; 443 if ([nscontext window] != window) { 444 [nscontext setWindow:window]; 445 [nscontext updateIfNeeded]; 446 } 447 [nscontext makeCurrentContext]; 448 } else { 449 [NSOpenGLContext clearCurrentContext]; 450 } 451 452 return true; 453 } 454} 455 456bool Cocoa_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval) 457{ 458 @autoreleasepool { 459 SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)SDL_GL_GetCurrentContext(); 460 bool result; 461 462 if (nscontext == nil) { 463 result = SDL_SetError("No current OpenGL context"); 464 } else { 465 SDL_LockMutex(nscontext->swapIntervalMutex); 466 SDL_SetAtomicInt(&nscontext->swapIntervalsPassed, 0); 467 SDL_SetAtomicInt(&nscontext->swapIntervalSetting, interval); 468 SDL_UnlockMutex(nscontext->swapIntervalMutex); 469 result = true; 470 } 471 472 return result; 473 } 474} 475 476bool Cocoa_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval) 477{ 478 @autoreleasepool { 479 SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)SDL_GL_GetCurrentContext(); 480 if (nscontext) { 481 *interval = SDL_GetAtomicInt(&nscontext->swapIntervalSetting); 482 return true; 483 } else { 484 return SDL_SetError("no OpenGL context"); 485 } 486 } 487} 488 489bool Cocoa_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window) 490{ 491 @autoreleasepool { 492 SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)SDL_GL_GetCurrentContext(); 493 SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)_this->internal; 494 const int setting = SDL_GetAtomicInt(&nscontext->swapIntervalSetting); 495 496 if (setting == 0) { 497 // nothing to do if vsync is disabled, don't even lock 498 } else if (setting < 0) { // late swap tearing 499 SDL_LockMutex(nscontext->swapIntervalMutex); 500 while (SDL_GetAtomicInt(&nscontext->swapIntervalsPassed) == 0) { 501 SDL_WaitCondition(nscontext->swapIntervalCond, nscontext->swapIntervalMutex); 502 } 503 SDL_SetAtomicInt(&nscontext->swapIntervalsPassed, 0); 504 SDL_UnlockMutex(nscontext->swapIntervalMutex); 505 } else { 506 SDL_LockMutex(nscontext->swapIntervalMutex); 507 do { // always wait here so we know we just hit a swap interval. 508 SDL_WaitCondition(nscontext->swapIntervalCond, nscontext->swapIntervalMutex); 509 } while ((SDL_GetAtomicInt(&nscontext->swapIntervalsPassed) % setting) != 0); 510 SDL_SetAtomicInt(&nscontext->swapIntervalsPassed, 0); 511 SDL_UnlockMutex(nscontext->swapIntervalMutex); 512 } 513 514 // { static Uint64 prev = 0; const Uint64 now = SDL_GetTicks(); const unsigned int diff = (unsigned int) (now - prev); prev = now; printf("GLSWAPBUFFERS TICKS %u\n", diff); } 515 516 /* on 10.14 ("Mojave") and later, this deadlocks if two contexts in two 517 threads try to swap at the same time, so put a mutex around it. */ 518 SDL_LockMutex(videodata.swaplock); 519 [nscontext flushBuffer]; 520 [nscontext updateIfNeeded]; 521 SDL_UnlockMutex(videodata.swaplock); 522 return true; 523 } 524} 525 526static void DispatchedDestroyContext(SDL_GLContext context) 527{ 528 @autoreleasepool { 529 SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)context; 530 [nscontext cleanup]; 531 CFRelease(context); 532 } 533} 534 535bool Cocoa_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context) 536{ 537 if ([NSThread isMainThread]) { 538 DispatchedDestroyContext(context); 539 } else { 540 if (SDL_opengl_async_dispatch) { 541 dispatch_async(dispatch_get_main_queue(), ^{ 542 DispatchedDestroyContext(context); 543 }); 544 } else { 545 dispatch_sync(dispatch_get_main_queue(), ^{ 546 DispatchedDestroyContext(context); 547 }); 548 } 549 } 550 551 return true; 552} 553 554// We still support OpenGL as long as Apple offers it, deprecated or not, so disable deprecation warnings about it. 555#ifdef __clang__ 556#pragma clang diagnostic pop 557#endif 558 559#endif // SDL_VIDEO_OPENGL_CGL 560
[FILE END]
(C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.