#include "SDL_internal.h"
#ifdef SDL_VIDEO_OPENGL_CGL
#include "SDL_cocoavideo.h"
#include "SDL_cocoaopengl.h"
#include "SDL_cocoaopengles.h"
#include <OpenGL/CGLTypes.h>
#include <OpenGL/OpenGL.h>
#include <OpenGL/CGLRenderers.h>
#include <SDL3/SDL_opengl.h>
#include "../../SDL_hints_c.h"
#define DEFAULT_OPENGL "/System/Library/Frameworks/OpenGL.framework/Libraries/libGL.dylib"
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#endif
#ifdef __has_feature
#if __has_feature(nullability)
#define HAS_FEATURE_NULLABLE
#endif
#endif
#ifndef HAS_FEATURE_NULLABLE
#define _Nullable
#endif
static bool SDL_opengl_async_dispatch = false;
static void SDLCALL SDL_OpenGLAsyncDispatchChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
{
SDL_opengl_async_dispatch = SDL_GetStringBoolean(hint, false);
}
static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp *now, const CVTimeStamp *outputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext)
{
SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)displayLinkContext;
const int setting = SDL_GetAtomicInt(&nscontext->swapIntervalSetting);
if (setting != 0) { SDL_LockMutex(nscontext->swapIntervalMutex);
SDL_AddAtomicInt(&nscontext->swapIntervalsPassed, 1);
SDL_SignalCondition(nscontext->swapIntervalCond);
SDL_UnlockMutex(nscontext->swapIntervalMutex);
}
return kCVReturnSuccess;
}
@implementation SDL3OpenGLContext : NSOpenGLContext
- (id)initWithFormat:(NSOpenGLPixelFormat *)format
shareContext:(NSOpenGLContext *)share
{
self = [super initWithFormat:format shareContext:share];
if (self) {
self.openglPixelFormat = format;
SDL_SetAtomicInt(&self->dirty, 0);
self->window = NULL;
SDL_SetAtomicInt(&self->swapIntervalSetting, 0);
SDL_SetAtomicInt(&self->swapIntervalsPassed, 0);
self->swapIntervalCond = SDL_CreateCondition();
self->swapIntervalMutex = SDL_CreateMutex();
if (!self->swapIntervalCond || !self->swapIntervalMutex) {
return nil;
}
CVDisplayLinkCreateWithActiveCGDisplays(&self->displayLink);
CVDisplayLinkSetOutputCallback(self->displayLink, &DisplayLinkCallback, (__bridge void *_Nullable)self);
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(self->displayLink, [self CGLContextObj], [format CGLPixelFormatObj]);
CVDisplayLinkStart(displayLink);
}
SDL_AddHintCallback(SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH, SDL_OpenGLAsyncDispatchChanged, NULL);
return self;
}
- (void)movedToNewScreen
{
if (self->displayLink) {
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(self->displayLink, [self CGLContextObj], [[self openglPixelFormat] CGLPixelFormatObj]);
}
}
- (void)scheduleUpdate
{
SDL_AddAtomicInt(&self->dirty, 1);
}
- (void)updateIfNeeded
{
const int value = SDL_SetAtomicInt(&self->dirty, 0);
if (value > 0) {
[self explicitUpdate];
}
}
- (void)update
{
[self scheduleUpdate];
[self updateIfNeeded];
}
- (void)setWindow:(SDL_Window *)newWindow
{
if (self->window) {
SDL_CocoaWindowData *oldwindowdata = (__bridge SDL_CocoaWindowData *)self->window->internal;
NSMutableArray *contexts = oldwindowdata.nscontexts;
@synchronized(contexts) {
[contexts removeObject:self];
}
}
self->window = newWindow;
if (newWindow) {
SDL_CocoaWindowData *windowdata = (__bridge SDL_CocoaWindowData *)newWindow->internal;
NSView *contentview = windowdata.sdlContentView;
NSMutableArray *contexts = windowdata.nscontexts;
@synchronized(contexts) {
[contexts addObject:self];
}
if ([self view] != contentview) {
if ([NSThread isMainThread]) {
[self setView:contentview];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[self setView:contentview];
});
}
if (self == [NSOpenGLContext currentContext]) {
[self explicitUpdate];
} else {
[self scheduleUpdate];
}
}
} else {
if ([NSThread isMainThread]) {
[self setView:nil];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{ [self setView:nil]; });
}
}
}
- (SDL_Window *)window
{
return self->window;
}
- (void)explicitUpdate
{
if ([NSThread isMainThread]) {
[super update];
} else {
if (SDL_opengl_async_dispatch) {
dispatch_async(dispatch_get_main_queue(), ^{
[super update];
});
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
[super update];
});
}
}
}
- (void)cleanup
{
[self setWindow:NULL];
SDL_RemoveHintCallback(SDL_HINT_MAC_OPENGL_ASYNC_DISPATCH, SDL_OpenGLAsyncDispatchChanged, NULL);
if (self->displayLink) {
CVDisplayLinkRelease(self->displayLink);
self->displayLink = nil;
}
if (self->swapIntervalCond) {
SDL_DestroyCondition(self->swapIntervalCond);
self->swapIntervalCond = NULL;
}
if (self->swapIntervalMutex) {
SDL_DestroyMutex(self->swapIntervalMutex);
self->swapIntervalMutex = NULL;
}
}
@end
bool Cocoa_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path)
{
if (path == NULL) {
path = SDL_GetHint(SDL_HINT_OPENGL_LIBRARY);
}
if (path == NULL) {
path = DEFAULT_OPENGL;
}
_this->gl_config.dll_handle = SDL_LoadObject(path);
if (!_this->gl_config.dll_handle) {
return false;
}
SDL_strlcpy(_this->gl_config.driver_path, path,
SDL_arraysize(_this->gl_config.driver_path));
return true;
}
SDL_FunctionPointer Cocoa_GL_GetProcAddress(SDL_VideoDevice *_this, const char *proc)
{
return SDL_LoadFunction(_this->gl_config.dll_handle, proc);
}
void Cocoa_GL_UnloadLibrary(SDL_VideoDevice *_this)
{
SDL_UnloadObject(_this->gl_config.dll_handle);
_this->gl_config.dll_handle = NULL;
}
SDL_GLContext Cocoa_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL_VideoDisplay *display = SDL_GetVideoDisplayForWindow(window);
SDL_DisplayData *displaydata = (SDL_DisplayData *)display->internal;
NSOpenGLPixelFormatAttribute attr[32];
NSOpenGLPixelFormat *fmt;
SDL3OpenGLContext *context;
SDL_GLContext sdlcontext;
NSOpenGLContext *share_context = nil;
int i = 0;
const char *glversion;
int glversion_major;
int glversion_minor;
NSOpenGLPixelFormatAttribute profile;
int interval;
int opaque;
if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_ES) {
#ifdef SDL_VIDEO_OPENGL_EGL
Cocoa_GL_UnloadLibrary(_this);
_this->GL_LoadLibrary = Cocoa_GLES_LoadLibrary;
_this->GL_GetProcAddress = Cocoa_GLES_GetProcAddress;
_this->GL_UnloadLibrary = Cocoa_GLES_UnloadLibrary;
_this->GL_CreateContext = Cocoa_GLES_CreateContext;
_this->GL_MakeCurrent = Cocoa_GLES_MakeCurrent;
_this->GL_SetSwapInterval = Cocoa_GLES_SetSwapInterval;
_this->GL_GetSwapInterval = Cocoa_GLES_GetSwapInterval;
_this->GL_SwapWindow = Cocoa_GLES_SwapWindow;
_this->GL_DestroyContext = Cocoa_GLES_DestroyContext;
if (!Cocoa_GLES_LoadLibrary(_this, NULL)) {
return NULL;
}
return Cocoa_GLES_CreateContext(_this, window);
#else
SDL_SetError("SDL not configured with EGL support");
return NULL;
#endif
}
attr[i++] = NSOpenGLPFAAllowOfflineRenderers;
profile = NSOpenGLProfileVersionLegacy;
if (_this->gl_config.profile_mask == SDL_GL_CONTEXT_PROFILE_CORE) {
profile = NSOpenGLProfileVersion3_2Core;
}
attr[i++] = NSOpenGLPFAOpenGLProfile;
attr[i++] = profile;
attr[i++] = NSOpenGLPFAColorSize;
attr[i++] = SDL_BYTESPERPIXEL(display->current_mode->format) * 8;
attr[i++] = NSOpenGLPFADepthSize;
attr[i++] = _this->gl_config.depth_size;
if (_this->gl_config.double_buffer) {
attr[i++] = NSOpenGLPFADoubleBuffer;
}
if (_this->gl_config.stereo) {
attr[i++] = NSOpenGLPFAStereo;
}
if (_this->gl_config.stencil_size) {
attr[i++] = NSOpenGLPFAStencilSize;
attr[i++] = _this->gl_config.stencil_size;
}
if ((_this->gl_config.accum_red_size +
_this->gl_config.accum_green_size +
_this->gl_config.accum_blue_size +
_this->gl_config.accum_alpha_size) > 0) {
attr[i++] = NSOpenGLPFAAccumSize;
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;
}
if (_this->gl_config.multisamplebuffers) {
attr[i++] = NSOpenGLPFASampleBuffers;
attr[i++] = _this->gl_config.multisamplebuffers;
}
if (_this->gl_config.multisamplesamples) {
attr[i++] = NSOpenGLPFASamples;
attr[i++] = _this->gl_config.multisamplesamples;
attr[i++] = NSOpenGLPFANoRecovery;
}
if (_this->gl_config.floatbuffers) {
attr[i++] = NSOpenGLPFAColorFloat;
}
if (_this->gl_config.accelerated >= 0) {
if (_this->gl_config.accelerated) {
attr[i++] = NSOpenGLPFAAccelerated;
} else {
attr[i++] = NSOpenGLPFARendererID;
attr[i++] = kCGLRendererGenericFloatID;
}
}
attr[i++] = NSOpenGLPFAScreenMask;
attr[i++] = CGDisplayIDToOpenGLDisplayMask(displaydata->display);
attr[i] = 0;
fmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr];
if (fmt == nil) {
SDL_SetError("Failed creating OpenGL pixel format");
return NULL;
}
if (_this->gl_config.share_with_current_context) {
share_context = (__bridge NSOpenGLContext *)SDL_GL_GetCurrentContext();
}
context = [[SDL3OpenGLContext alloc] initWithFormat:fmt shareContext:share_context];
if (context == nil) {
SDL_SetError("Failed creating OpenGL context");
return NULL;
}
sdlcontext = (SDL_GLContext)CFBridgingRetain(context);
interval = 0;
[context setValues:&interval forParameter:NSOpenGLCPSwapInterval];
opaque = (window->flags & SDL_WINDOW_TRANSPARENT) ? 0 : 1;
[context setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];
if (!Cocoa_GL_MakeCurrent(_this, window, sdlcontext)) {
SDL_GL_DestroyContext(sdlcontext);
SDL_SetError("Failed making OpenGL context current");
return NULL;
}
if (_this->gl_config.major_version < 3 &&
_this->gl_config.profile_mask == 0 &&
_this->gl_config.flags == 0) {
} else {
const GLubyte *(APIENTRY * glGetStringFunc)(GLenum);
glGetStringFunc = (const GLubyte *(APIENTRY *)(GLenum))SDL_GL_GetProcAddress("glGetString");
if (!glGetStringFunc) {
SDL_GL_DestroyContext(sdlcontext);
SDL_SetError("Failed getting OpenGL glGetString entry point");
return NULL;
}
glversion = (const char *)glGetStringFunc(GL_VERSION);
if (glversion == NULL) {
SDL_GL_DestroyContext(sdlcontext);
SDL_SetError("Failed getting OpenGL context version");
return NULL;
}
if (SDL_sscanf(glversion, "%d.%d", &glversion_major, &glversion_minor) != 2) {
SDL_GL_DestroyContext(sdlcontext);
SDL_SetError("Failed parsing OpenGL context version");
return NULL;
}
if ((glversion_major < _this->gl_config.major_version) ||
((glversion_major == _this->gl_config.major_version) && (glversion_minor < _this->gl_config.minor_version))) {
SDL_GL_DestroyContext(sdlcontext);
SDL_SetError("Failed creating OpenGL context at version requested");
return NULL;
}
}
return sdlcontext;
}
}
bool Cocoa_GL_MakeCurrent(SDL_VideoDevice *_this, SDL_Window *window, SDL_GLContext context)
{
@autoreleasepool {
if (context) {
SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)context;
if ([nscontext window] != window) {
[nscontext setWindow:window];
[nscontext updateIfNeeded];
}
[nscontext makeCurrentContext];
} else {
[NSOpenGLContext clearCurrentContext];
}
return true;
}
}
bool Cocoa_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval)
{
@autoreleasepool {
SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)SDL_GL_GetCurrentContext();
bool result;
if (nscontext == nil) {
result = SDL_SetError("No current OpenGL context");
} else {
SDL_LockMutex(nscontext->swapIntervalMutex);
SDL_SetAtomicInt(&nscontext->swapIntervalsPassed, 0);
SDL_SetAtomicInt(&nscontext->swapIntervalSetting, interval);
SDL_UnlockMutex(nscontext->swapIntervalMutex);
result = true;
}
return result;
}
}
bool Cocoa_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval)
{
@autoreleasepool {
SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)SDL_GL_GetCurrentContext();
if (nscontext) {
*interval = SDL_GetAtomicInt(&nscontext->swapIntervalSetting);
return true;
} else {
return SDL_SetError("no OpenGL context");
}
}
}
bool Cocoa_GL_SwapWindow(SDL_VideoDevice *_this, SDL_Window *window)
{
@autoreleasepool {
SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)SDL_GL_GetCurrentContext();
SDL_CocoaVideoData *videodata = (__bridge SDL_CocoaVideoData *)_this->internal;
const int setting = SDL_GetAtomicInt(&nscontext->swapIntervalSetting);
if (setting == 0) {
} else if (setting < 0) { SDL_LockMutex(nscontext->swapIntervalMutex);
while (SDL_GetAtomicInt(&nscontext->swapIntervalsPassed) == 0) {
SDL_WaitCondition(nscontext->swapIntervalCond, nscontext->swapIntervalMutex);
}
SDL_SetAtomicInt(&nscontext->swapIntervalsPassed, 0);
SDL_UnlockMutex(nscontext->swapIntervalMutex);
} else {
SDL_LockMutex(nscontext->swapIntervalMutex);
do { SDL_WaitCondition(nscontext->swapIntervalCond, nscontext->swapIntervalMutex);
} while ((SDL_GetAtomicInt(&nscontext->swapIntervalsPassed) % setting) != 0);
SDL_SetAtomicInt(&nscontext->swapIntervalsPassed, 0);
SDL_UnlockMutex(nscontext->swapIntervalMutex);
}
SDL_LockMutex(videodata.swaplock);
[nscontext flushBuffer];
[nscontext updateIfNeeded];
SDL_UnlockMutex(videodata.swaplock);
return true;
}
}
static void DispatchedDestroyContext(SDL_GLContext context)
{
@autoreleasepool {
SDL3OpenGLContext *nscontext = (__bridge SDL3OpenGLContext *)context;
[nscontext cleanup];
CFRelease(context);
}
}
bool Cocoa_GL_DestroyContext(SDL_VideoDevice *_this, SDL_GLContext context)
{
if ([NSThread isMainThread]) {
DispatchedDestroyContext(context);
} else {
if (SDL_opengl_async_dispatch) {
dispatch_async(dispatch_get_main_queue(), ^{
DispatchedDestroyContext(context);
});
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
DispatchedDestroyContext(context);
});
}
}
return true;
}
#ifdef __clang__
#pragma clang diagnostic pop
#endif
#endif