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