Getting OpenGL extensions on MacOS

References

When linking normal C libraries, your code declares the signature of the functions and variables you expect to dynamically link (usually by #including a header file), and the operating system's dynamic linker links those objects into your program at runtime. OpenGL is rather different - although less so on MacOS than Windows/Linux. Apple controls both the hardware and software for their MacOS platform, and they provide their own implementations of OpenGL. Apple deprecated OpenGL in 2018, but their implementation is built on top of Metal so it continues to work well on their new hardware. The last major update to their GL implementation was in MacOS 10.9, upgrading to OpenGL 4.1. So, if you target MacOS 10.9 or later, you can safely assume you'll have access to 2.1, 3.2, and 4.1 contexts with all their symbols. Extensions still need to be retrieved manually at runtime. Note that you can also use this type of mechanism to manually link symbols from other libraries, rather than creating a dynamic executable.

Build with:

clang main.m -std=gnu2y -framework Cocoa -framework OpenGL
const char *gl_extensions = (const char*)glGetString (GL_EXTENSIONS);

OpenGL provides us a list of all available extensions in the form of a space-separated string. Feel free to read through some of the GL extensions your context reports. This works in all OpenGL context versions, but if your context is 3.0 or later, you can also retrieve the number of extensions with glGetIntegerv (GL_NUM_EXTENSIONS, &extension_count) then iterate each extension as an individual string with glGetStringi (GL_EXTENSIONS, i).

bool GLHasExtension (const char *const haystack, const char *const needle) { const char *e = haystack; const size_t len = strlen (needle); for (;;) { e = strstr (e, needle); if (e == NULL) return false; char next = *(e + len); if (next == ' ' || next == '\0') return true; e += len; } }

This function searches for the given extension in an extension string. It's important to check that the found extension is followed by a space or NULL, because some extensions have the same name as another extension, with additional text added on, eg. GL_ARB_shadow and GL_ARB_shadow_ambient. On all of my systems, every extension in the string, including the last, is followed by a space, so I could just check for the extension followed by a space to eliminate the need to check the next character. But according to this nVidia document, page 6, the last extension may not have a space and may just be NULL-terminated.

NSOpenGLPixelFormatAttribute gl_attributes[] = { NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersionLegacy, [...] const GLuint vertex_shader = glCreateShader (GL_VERTEX_SHADER);

glCreateShader was added in version 2.0. Since we've requested a legacy 2.1 context, we can just go right ahead and call functions included in that version.

#include <dlfcn.h> [...] void *gldll = dlopen ("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY);

We used to be able to use NSLookupAndBindSymbol to find dynamic symbols, but Apple has deprecated it. Now, this is the standard way of loading OpenGL extension functions. dlfcn is the POSIX library used for dynamic linking. Here we open the OpenGL dynamic library, from which we will be retrieving GL extension functions.

if (GLHasExtension (gl_extensions, "GL_ARB_vertex_buffer_object")) { do { typedef void (*glGenBuffersARB_t) (GLsizei n, GLuint *buffers); const glGenBuffersARB_t glGenBuffersARB = (glGenBuffersARB_t)dlsym (gldll, "glGenBuffersARB"); if (glGenBuffersARB == NULL) { puts ("Failed to retrieve glGenBuffersARB function pointer"); break; } GLuint buffer = 0; glGenBuffersARB (1, &buffer); printf ("glGenBuffersARB found and called: %d\n", buffer); } while (false); }

First we check for the presence of the extension we want. Then, we create a type for the function pointer. The function signature can be copied from the extension specification. Next, we use dlsym to request the symbol by name, casting the pointer to the correct type. If the symbol exists, we'll have its address. If not, dlsym returns NULL. After retrieving the function pointer and verifying we got it, we call it just to demonstrate that it works.

You may wonder: why check for the version/extension at all? Why not just attempt to retrieve the function pointer without checking those things? In many cases that will work, but it is sometimes necessary to check for the relevant extension first, because the same functions may be implemented with slight differences by different extensions.

The above code works for retrieving any GL extension functions. However, if you find you need to repeat the above code a lot to retrieve a lot of extensions, you might use a library such as GLEW or Glad, or generate your code for loading many extensions.