summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoaopengl.m
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoaopengl.m')
-rw-r--r--contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoaopengl.m559
1 files changed, 559 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoaopengl.m b/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoaopengl.m
new file mode 100644
index 0000000..34002ec
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/video/cocoa/SDL_cocoaopengl.m
@@ -0,0 +1,559 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 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#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