1/*
2  Simple DirectMedia Layer
3  Copyright (C) 1997-2016 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
29#include <OpenGL/CGLTypes.h>
30#include <OpenGL/OpenGL.h>
31#include <OpenGL/CGLRenderers.h>
32
33#include "SDL_loadso.h"
34#include "SDL_opengl.h"
35
36#define DEFAULT_OPENGL  "/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib"
37
38@implementation SDLOpenGLContext : NSOpenGLContext
39
40- (id)initWithFormat:(NSOpenGLPixelFormat *)format
41        shareContext:(NSOpenGLContext *)share
42{
43    self = [super initWithFormat:format shareContext:share];
44    if (self) {
45        SDL_AtomicSet(&self->dirty, 0);
46        self->window = NULL;
47    }
48    return self;
49}
50
51- (void)scheduleUpdate
52{
53    SDL_AtomicAdd(&self->dirty, 1);
54}
55
56/* This should only be called on the thread on which a user is using the context. */
57- (void)updateIfNeeded
58{
59    int value = SDL_AtomicSet(&self->dirty, 0);
60    if (value > 0) {
61        /* We call the real underlying update here, since -[SDLOpenGLContext update] just calls us. */
62        [super update];
63    }
64}
65
66/* This should only be called on the thread on which a user is using the context. */
67- (void)update
68{
69    /* This ensures that regular 'update' calls clear the atomic dirty flag. */
70    [self scheduleUpdate];
71    [self updateIfNeeded];
72}
73
74/* Updates the drawable for the contexts and manages related state. */
75- (void)setWindow:(SDL_Window *)newWindow
76{
77    if (self->window) {
78        SDL_WindowData *oldwindowdata = (SDL_WindowData *)self->window->driverdata;
79
80        /* Make sure to remove us from the old window's context list, or we'll get scheduled updates from it too. */
81        NSMutableArray *contexts = oldwindowdata->nscontexts;
82        @synchronized (contexts) {
83            [contexts removeObject:self];
84        }
85    }
86
87    self->window = newWindow;
88
89    if (newWindow) {
90        SDL_WindowData *windowdata = (SDL_WindowData *)newWindow->driverdata;
91
92        /* Now sign up for scheduled updates for the new window. */
93        NSMutableArray *contexts = windowdata->nscontexts;
94        @synchronized (contexts) {
95            [contexts addObject:self];
96        }
97
98        if ([self view] != [windowdata->nswindow contentView]) {
99            [self setView:[windowdata->nswindow contentView]];
100            if (self == [NSOpenGLContext currentContext]) {
101                [self update];
102            } else {
103                [self scheduleUpdate];
104            }
105        }
106    } else {
107        [self clearDrawable];
108        if (self == [NSOpenGLContext currentContext]) {
109            [self update];
110        } else {
111            [self scheduleUpdate];
112        }
113    }
114}
115
116@end
117
118
119int
120Cocoa_GL_LoadLibrary(_THIS, const char *path)
121{
122    /* Load the OpenGL library */
123    if (path == NULL) {
124        path = SDL_getenv("SDL_OPENGL_LIBRARY");
125    }
126    if (path == NULL) {
127        path = DEFAULT_OPENGL;
128    }
129    _this->gl_config.dll_handle = SDL_LoadObject(path);
130    if (!_this->gl_config.dll_handle) {
131        return -1;
132    }
133    SDL_strlcpy(_this->gl_config.driver_path, path,
134                SDL_arraysize(_this->gl_config.driver_path));
135    return 0;
136}
137
138void *
139Cocoa_GL_GetProcAddress(_THIS, const char *proc)
140{
141    return SDL_LoadFunction(_this->gl_config.dll_handle, proc);
142}
143
144void
145Cocoa_GL_UnloadLibrary(_THIS)
146{
147    SDL_UnloadObject(_this->gl_config.dll_handle);
148    _this->gl_config.dll_handle = NULL;
149}
150
151SDL_GLContext
152Cocoa_GL_CreateContext(_THIS, SDL_Window * window)
153{ @autoreleasepool
154{
155    SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window);
156    SDL_DisplayData *displaydata = (SDL_DisplayData *)display->driverdata;
157    SDL_bool lion_or_later = floor(NSAppKitVersionNumber) > NSAppKitVersionNumber10_6;
158    NSOpenGLPixelFormatAttribute attr[32];
159    NSOpenGLPixelFormat *fmt;
160    SDLOpenGLContext *context;
161    NSOpenGLContext *share_context = nil;
162    int i = 0;
163    const char *glversion;
164    int glversion_major;
165    int glversion_minor;
166
167    if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) {
168        SDL_SetError ("OpenGL ES is not supported on this platform");
169        return NULL;
170    }
171    if ((_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_CORE) && !lion_or_later) {
172        SDL_SetError ("OpenGL Core Profile is not supported on this platform version");
173        return NULL;
174    }
175
176    attr[i++] = NSOpenGLPFAAllowOfflineRenderers;
177
178    /* specify a profile if we're on Lion (10.7) or later. */
179    if (lion_or_later) {
180        NSOpenGLPixelFormatAttribute profile = NSOpenGLProfileVersionLegacy;
181        if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_CORE) {
182            profile = NSOpenGLProfileVersion3_2Core;
183        }
184        attr[i++] = NSOpenGLPFAOpenGLProfile;
185        attr[i++] = profile;
186    }
187
188    attr[i++] = NSOpenGLPFAColorSize;
189    attr[i++] = SDL_BYTESPERPIXEL(display->current_mode.format)*8;
190
191    attr[i++] = NSOpenGLPFADepthSize;
192    attr[i++] = _this->gl_config.depth_size;
193
194    if (_this->gl_config.double_buffer) {
195        attr[i++] = NSOpenGLPFADoubleBuffer;
196    }
197
198    if (_this->gl_config.stereo) {
199        attr[i++] = NSOpenGLPFAStereo;
200    }
201
202    if (_this->gl_config.stencil_size) {
203        attr[i++] = NSOpenGLPFAStencilSize;
204        attr[i++] = _this->gl_config.stencil_size;
205    }
206
207    if ((_this->gl_config.accum_red_size +
208         _this->gl_config.accum_green_size +
209         _this->gl_config.accum_blue_size +
210         _this->gl_config.accum_alpha_size) > 0) {
211        attr[i++] = NSOpenGLPFAAccumSize;
212        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;
213    }
214
215    if (_this->gl_config.multisamplebuffers) {
216        attr[i++] = NSOpenGLPFASampleBuffers;
217        attr[i++] = _this->gl_config.multisamplebuffers;
218    }
219
220    if (_this->gl_config.multisamplesamples) {
221        attr[i++] = NSOpenGLPFASamples;
222        attr[i++] = _this->gl_config.multisamplesamples;
223        attr[i++] = NSOpenGLPFANoRecovery;
224    }
225
226    if (_this->gl_config.accelerated >= 0) {
227        if (_this->gl_config.accelerated) {
228            attr[i++] = NSOpenGLPFAAccelerated;
229        } else {
230            attr[i++] = NSOpenGLPFARendererID;
231            attr[i++] = kCGLRendererGenericFloatID;
232        }
233    }
234
235    attr[i++] = NSOpenGLPFAScreenMask;
236    attr[i++] = CGDisplayIDToOpenGLDisplayMask(displaydata->display);
237    attr[i] = 0;
238
239    fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
240    if (fmt == nil) {
241        SDL_SetError("Failed creating OpenGL pixel format");
242        return NULL;
243    }
244
245    if (_this->gl_config.share_with_current_context) {
246        share_context = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
247    }
248
249    context = [[SDLOpenGLContext alloc] initWithFormat:fmt shareContext:share_context];
250
251    [fmt release];
252
253    if (context == nil) {
254        SDL_SetError("Failed creating OpenGL context");
255        return NULL;
256    }
257
258    if ( Cocoa_GL_MakeCurrent(_this, window, context) < 0 ) {
259        Cocoa_GL_DeleteContext(_this, context);
260        SDL_SetError("Failed making OpenGL context current");
261        return NULL;
262    }
263
264    if (_this->gl_config.major_version < 3 &&
265        _this->gl_config.profile_mask == 0 &&
266        _this->gl_config.flags == 0) {
267        /* This is a legacy profile, so to match other backends, we're done. */
268    } else {
269        const GLubyte *(APIENTRY * glGetStringFunc)(GLenum);
270
271        glGetStringFunc = (const GLubyte *(APIENTRY *)(GLenum)) SDL_GL_GetProcAddress("glGetString");
272        if (!glGetStringFunc) {
273            Cocoa_GL_DeleteContext(_this, context);
274            SDL_SetError ("Failed getting OpenGL glGetString entry point");
275            return NULL;
276        }
277
278        glversion = (const char *)glGetStringFunc(GL_VERSION);
279        if (glversion == NULL) {
280            Cocoa_GL_DeleteContext(_this, context);
281            SDL_SetError ("Failed getting OpenGL context version");
282            return NULL;
283        }
284
285        if (SDL_sscanf(glversion, "%d.%d", &glversion_major, &glversion_minor) != 2) {
286            Cocoa_GL_DeleteContext(_this, context);
287            SDL_SetError ("Failed parsing OpenGL context version");
288            return NULL;
289        }
290
291        if ((glversion_major < _this->gl_config.major_version) ||
292           ((glversion_major == _this->gl_config.major_version) && (glversion_minor < _this->gl_config.minor_version))) {
293            Cocoa_GL_DeleteContext(_this, context);
294            SDL_SetError ("Failed creating OpenGL context at version requested");
295            return NULL;
296        }
297
298        /* In the future we'll want to do this, but to match other platforms
299           we'll leave the OpenGL version the way it is for now
300         */
301        /*_this->gl_config.major_version = glversion_major;*/
302        /*_this->gl_config.minor_version = glversion_minor;*/
303    }
304    return context;
305}}
306
307int
308Cocoa_GL_MakeCurrent(_THIS, SDL_Window * window, SDL_GLContext context)
309{ @autoreleasepool
310{
311    if (context) {
312        SDLOpenGLContext *nscontext = (SDLOpenGLContext *)context;
313        [nscontext setWindow:window];
314        [nscontext updateIfNeeded];
315        [nscontext makeCurrentContext];
316    } else {
317        [NSOpenGLContext clearCurrentContext];
318    }
319
320    return 0;
321}}
322
323void
324Cocoa_GL_GetDrawableSize(_THIS, SDL_Window * window, int * w, int * h)
325{
326    SDL_WindowData *windata = (SDL_WindowData *) window->driverdata;
327    NSView *contentView = [windata->nswindow contentView];
328    NSRect viewport = [contentView bounds];
329
330    /* This gives us the correct viewport for a Retina-enabled view, only
331     * supported on 10.7+. */
332    if ([contentView respondsToSelector:@selector(convertRectToBacking:)]) {
333        viewport = [contentView convertRectToBacking:viewport];
334    }
335
336    if (w) {
337        *w = viewport.size.width;
338    }
339
340    if (h) {
341        *h = viewport.size.height;
342    }
343}
344
345int
346Cocoa_GL_SetSwapInterval(_THIS, int interval)
347{ @autoreleasepool
348{
349    NSOpenGLContext *nscontext;
350    GLint value;
351    int status;
352
353    if (interval < 0) {  /* no extension for this on Mac OS X at the moment. */
354        return SDL_SetError("Late swap tearing currently unsupported");
355    }
356
357    nscontext = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
358    if (nscontext != nil) {
359        value = interval;
360        [nscontext setValues:&value forParameter:NSOpenGLCPSwapInterval];
361        status = 0;
362    } else {
363        status = SDL_SetError("No current OpenGL context");
364    }
365
366    return status;
367}}
368
369int
370Cocoa_GL_GetSwapInterval(_THIS)
371{ @autoreleasepool
372{
373    NSOpenGLContext *nscontext;
374    GLint value;
375    int status = 0;
376
377    nscontext = (NSOpenGLContext*)SDL_GL_GetCurrentContext();
378    if (nscontext != nil) {
379        [nscontext getValues:&value forParameter:NSOpenGLCPSwapInterval];
380        status = (int)value;
381    }
382
383    return status;
384}}
385
386void
387Cocoa_GL_SwapWindow(_THIS, SDL_Window * window)
388{ @autoreleasepool
389{
390    SDLOpenGLContext* nscontext = (SDLOpenGLContext*)SDL_GL_GetCurrentContext();
391    [nscontext flushBuffer];
392    [nscontext updateIfNeeded];
393}}
394
395void
396Cocoa_GL_DeleteContext(_THIS, SDL_GLContext context)
397{ @autoreleasepool
398{
399    SDLOpenGLContext *nscontext = (SDLOpenGLContext *)context;
400
401    [nscontext setWindow:NULL];
402    [nscontext release];
403}}
404
405#endif /* SDL_VIDEO_OPENGL_CGL */
406
407/* vi: set ts=4 sw=4 expandtab: */
408