//======================================================================== // GLFW 3.3 macOS - www.glfw.org //------------------------------------------------------------------------ // Copyright (c) 2009-2019 Camilla Löwy // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would // be appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. // //======================================================================== #include "internal.h" // Display link callback for manual swap interval implementation // This is based on a similar workaround added to SDL2 // static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* userInfo) { _GLFWwindow* window = (_GLFWwindow *) userInfo; const int interval = atomic_load(&window->context.nsgl.swapInterval); if (interval > 0) { [window->context.nsgl.swapIntervalCond lock]; window->context.nsgl.swapIntervalsPassed++; [window->context.nsgl.swapIntervalCond signal]; [window->context.nsgl.swapIntervalCond unlock]; } return kCVReturnSuccess; } static void makeContextCurrentNSGL(_GLFWwindow* window) { @autoreleasepool { if (window) [window->context.nsgl.object makeCurrentContext]; else [NSOpenGLContext clearCurrentContext]; _glfwPlatformSetTls(&_glfw.contextSlot, window); } // autoreleasepool } static void swapBuffersNSGL(_GLFWwindow* window) { @autoreleasepool { const int interval = atomic_load(&window->context.nsgl.swapInterval); if (interval > 0) { [window->context.nsgl.swapIntervalCond lock]; do { [window->context.nsgl.swapIntervalCond wait]; } while (window->context.nsgl.swapIntervalsPassed % interval != 0); window->context.nsgl.swapIntervalsPassed = 0; [window->context.nsgl.swapIntervalCond unlock]; } // ARP appears to be unnecessary, but this is future-proof [window->context.nsgl.object flushBuffer]; } // autoreleasepool } static void swapIntervalNSGL(int interval) { @autoreleasepool { _GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot); atomic_store(&window->context.nsgl.swapInterval, interval); [window->context.nsgl.swapIntervalCond lock]; window->context.nsgl.swapIntervalsPassed = 0; [window->context.nsgl.swapIntervalCond unlock]; } // autoreleasepool } static int extensionSupportedNSGL(const char* extension) { // There are no NSGL extensions return GLFW_FALSE; } static GLFWglproc getProcAddressNSGL(const char* procname) { CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, procname, kCFStringEncodingASCII); GLFWglproc symbol = CFBundleGetFunctionPointerForName(_glfw.nsgl.framework, symbolName); CFRelease(symbolName); return symbol; } // Destroy the OpenGL context // static void destroyContextNSGL(_GLFWwindow* window) { @autoreleasepool { if (window->context.nsgl.displayLink) { if (CVDisplayLinkIsRunning(window->context.nsgl.displayLink)) CVDisplayLinkStop(window->context.nsgl.displayLink); CVDisplayLinkRelease(window->context.nsgl.displayLink); } [window->context.nsgl.swapIntervalCond release]; window->context.nsgl.swapIntervalCond = nil; [window->context.nsgl.pixelFormat release]; window->context.nsgl.pixelFormat = nil; [window->context.nsgl.object release]; window->context.nsgl.object = nil; } // autoreleasepool } ////////////////////////////////////////////////////////////////////////// ////// GLFW internal API ////// ////////////////////////////////////////////////////////////////////////// // Initialize OpenGL support // GLFWbool _glfwInitNSGL(void) { if (_glfw.nsgl.framework) return GLFW_TRUE; _glfw.nsgl.framework = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")); if (_glfw.nsgl.framework == NULL) { _glfwInputError(GLFW_API_UNAVAILABLE, "NSGL: Failed to locate OpenGL framework"); return GLFW_FALSE; } return GLFW_TRUE; } // Terminate OpenGL support // void _glfwTerminateNSGL(void) { } // Create the OpenGL context // GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window, const _GLFWctxconfig* ctxconfig, const _GLFWfbconfig* fbconfig) { if (ctxconfig->client == GLFW_OPENGL_ES_API) { _glfwInputError(GLFW_API_UNAVAILABLE, "NSGL: OpenGL ES is not available on macOS"); return GLFW_FALSE; } if (ctxconfig->major > 2) { if (ctxconfig->major == 3 && ctxconfig->minor < 2) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "NSGL: The targeted version of macOS does not support OpenGL 3.0 or 3.1 but may support 3.2 and above"); return GLFW_FALSE; } if (!ctxconfig->forward || ctxconfig->profile != GLFW_OPENGL_CORE_PROFILE) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "NSGL: The targeted version of macOS only supports forward-compatible core profile contexts for OpenGL 3.2 and above"); return GLFW_FALSE; } } // Context robustness modes (GL_KHR_robustness) are not yet supported by // macOS but are not a hard constraint, so ignore and continue // Context release behaviors (GL_KHR_context_flush_control) are not yet // supported by macOS but are not a hard constraint, so ignore and continue // Debug contexts (GL_KHR_debug) are not yet supported by macOS but are not // a hard constraint, so ignore and continue // No-error contexts (GL_KHR_no_error) are not yet supported by macOS but // are not a hard constraint, so ignore and continue #define addAttrib(a) \ { \ assert((size_t) index < sizeof(attribs) / sizeof(attribs[0])); \ attribs[index++] = a; \ } #define setAttrib(a, v) { addAttrib(a); addAttrib(v); } NSOpenGLPixelFormatAttribute attribs[40]; int index = 0; addAttrib(NSOpenGLPFAAccelerated); addAttrib(NSOpenGLPFAClosestPolicy); if (ctxconfig->nsgl.offline) { addAttrib(NSOpenGLPFAAllowOfflineRenderers); // NOTE: This replaces the NSSupportsAutomaticGraphicsSwitching key in // Info.plist for unbundled applications // HACK: This assumes that NSOpenGLPixelFormat will remain // a straightforward wrapper of its CGL counterpart addAttrib(kCGLPFASupportsAutomaticGraphicsSwitching); } #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 if (ctxconfig->major >= 4) { setAttrib(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion4_1Core); } else #endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/ if (ctxconfig->major >= 3) { setAttrib(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core); } if (ctxconfig->major <= 2) { if (fbconfig->auxBuffers != GLFW_DONT_CARE) setAttrib(NSOpenGLPFAAuxBuffers, fbconfig->auxBuffers); if (fbconfig->accumRedBits != GLFW_DONT_CARE && fbconfig->accumGreenBits != GLFW_DONT_CARE && fbconfig->accumBlueBits != GLFW_DONT_CARE && fbconfig->accumAlphaBits != GLFW_DONT_CARE) { const int accumBits = fbconfig->accumRedBits + fbconfig->accumGreenBits + fbconfig->accumBlueBits + fbconfig->accumAlphaBits; setAttrib(NSOpenGLPFAAccumSize, accumBits); } } if (fbconfig->redBits != GLFW_DONT_CARE && fbconfig->greenBits != GLFW_DONT_CARE && fbconfig->blueBits != GLFW_DONT_CARE) { int colorBits = fbconfig->redBits + fbconfig->greenBits + fbconfig->blueBits; // macOS needs non-zero color size, so set reasonable values if (colorBits == 0) colorBits = 24; else if (colorBits < 15) colorBits = 15; setAttrib(NSOpenGLPFAColorSize, colorBits); } if (fbconfig->alphaBits != GLFW_DONT_CARE) setAttrib(NSOpenGLPFAAlphaSize, fbconfig->alphaBits); if (fbconfig->depthBits != GLFW_DONT_CARE) setAttrib(NSOpenGLPFADepthSize, fbconfig->depthBits); if (fbconfig->stencilBits != GLFW_DONT_CARE) setAttrib(NSOpenGLPFAStencilSize, fbconfig->stencilBits); if (fbconfig->stereo) { #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "NSGL: Stereo rendering is deprecated"); return GLFW_FALSE; #else addAttrib(NSOpenGLPFAStereo); #endif } if (fbconfig->doublebuffer) addAttrib(NSOpenGLPFADoubleBuffer); if (fbconfig->samples != GLFW_DONT_CARE) { if (fbconfig->samples == 0) { setAttrib(NSOpenGLPFASampleBuffers, 0); } else { setAttrib(NSOpenGLPFASampleBuffers, 1); setAttrib(NSOpenGLPFASamples, fbconfig->samples); } } // NOTE: All NSOpenGLPixelFormats on the relevant cards support sRGB // framebuffer, so there's no need (and no way) to request it addAttrib(0); #undef addAttrib #undef setAttrib window->context.nsgl.pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; if (window->context.nsgl.pixelFormat == nil) { _glfwInputError(GLFW_FORMAT_UNAVAILABLE, "NSGL: Failed to find a suitable pixel format"); return GLFW_FALSE; } NSOpenGLContext* share = NULL; if (ctxconfig->share) share = ctxconfig->share->context.nsgl.object; window->context.nsgl.object = [[NSOpenGLContext alloc] initWithFormat:window->context.nsgl.pixelFormat shareContext:share]; if (window->context.nsgl.object == nil) { _glfwInputError(GLFW_VERSION_UNAVAILABLE, "NSGL: Failed to create OpenGL context"); return GLFW_FALSE; } if (fbconfig->transparent) { GLint opaque = 0; [window->context.nsgl.object setValues:&opaque forParameter:NSOpenGLContextParameterSurfaceOpacity]; } if (window->ns.retina) [window->ns.view setWantsBestResolutionOpenGLSurface:YES]; GLint interval = 0; [window->context.nsgl.object setValues:&interval forParameter:NSOpenGLContextParameterSwapInterval]; [window->context.nsgl.object setView:window->ns.view]; window->context.nsgl.swapIntervalCond = [NSCondition new]; window->context.makeCurrent = makeContextCurrentNSGL; window->context.swapBuffers = swapBuffersNSGL; window->context.swapInterval = swapIntervalNSGL; window->context.extensionSupported = extensionSupportedNSGL; window->context.getProcAddress = getProcAddressNSGL; window->context.destroy = destroyContextNSGL; CVDisplayLinkCreateWithActiveCGDisplays(&window->context.nsgl.displayLink); CVDisplayLinkSetOutputCallback(window->context.nsgl.displayLink, &displayLinkCallback, window); CVDisplayLinkStart(window->context.nsgl.displayLink); _glfwUpdateDisplayLinkDisplayNSGL(window); return GLFW_TRUE; } void _glfwUpdateDisplayLinkDisplayNSGL(_GLFWwindow* window) { CGDirectDisplayID displayID = [[[window->ns.object screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue]; if (!displayID) return; CVDisplayLinkSetCurrentCGDisplay(window->context.nsgl.displayLink, displayID); } ////////////////////////////////////////////////////////////////////////// ////// GLFW native API ////// ////////////////////////////////////////////////////////////////////////// GLFWAPI id glfwGetNSGLContext(GLFWwindow* handle) { _GLFWwindow* window = (_GLFWwindow*) handle; _GLFW_REQUIRE_INIT_OR_RETURN(nil); if (window->context.client == GLFW_NO_API) { _glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL); return NULL; } return window->context.nsgl.object; }