Why does glXGetProcAddress never return NULL?

The issue of using glXGetProcAddress with GL and GLX extensions often comes up on various X, DRI, Mesa, and OpenGL related mailing lists. It turns out that a lot of developers, even developers experienced with OpenGL and GLX related issues, have a few very, very wrong misconceptions. This document tries to clear up the most common of those misconceptions. I'm not trying to pick on any one here. Every issue in this document has come up numerous times over the years.

Misconception #1: glXGetProcAddress and glXGetProcAddressARB may not be available at run-time

In section 3.6 Linux OpenGL ABI requires that all libGL implementations on Linux statically export glXGetProcAddressARB. It also common practice, though not required by the ABI, to statically export glXGetProcAddress. At the very least, Nvidia's libGL does not export glXGetProcAddress, so it is probably not wise for applications to depend on that symbol being available.

Misconception #2: Pointers returned by glXGetProcAddress are context-dependent

The GLX_ARB_get_proc_address specification details this quite clearly in the issues section, and that text won't be repeated here. glXGetProcAddress is used to get pointers to both GL extension functions and GLX extension functions. Many GLX extension functions are called before the application has a context bound (e.g., glXMakeCurrentReadSGI). In order for pointers returned by glXGetProcAddress to be context-dependent the application would have to have a context to call glXGetProcAddress. The chicken-and-egg problem here should be obvious.

Note, however, that this is not the case with wglGetProcAddress. The pointers returned by wglGetProcAddress are context-dependent. This is probably the source of this misconception.

Misconception #3: glXGetProcAddress will return NULL if the function is not supported

Go back and look at misconception #2 for a few moments. Now, repeat this phrase ten times: glXGetProcAddress can be called before there is a context. Before a context is bound, it is impossible to know what extensions or which OpenGL version is available. At the time glXGetProcAddress is called by an application, it may not even be possible for libGL to know what driver or graphics card will be used. Because of that, the default X.org libGL will always return a non-NULL pointer.

Each GL context contains a dispatch table. This table operates similarly to the virtual function table in a C++ object. When glXGetProcAddress is called for an as yet unknown function a new slot is added to the dispatch table for that function. In addition a short code segment that calls the function pointer stored in the dispatch table is created. A pointer to this short function is returned by glXGetProcAddress. Here's the catch: if the driver does not support the function, it won't put anything in the function's dispatch table entry. If the application calls the function returned by glXGetProcAddress, that function will try to call a NULL function pointer, and the application will crash.

To illustrate this, use X.org's default libGL and call:

fptr = glXGetProcAddressARB( (const GLubyte *) "glThisFunctionWillNeverExist" );

Create and bind a context and try to call that function.

Keep in mind that Nvidia's libGL does not behave this way. Nvidia's libGL knows that it will only ever load Nvidia's drivers, so it can make assumptions about what functions may or may not be made available at run-time. If an application calls glXGetProcAddress on a function that it has never heard of, it knows that none of the drivers that it will ever load will ever export that function. In that case it can return NULL.

You can try the same trick on Nvidia's libGL, however. Try calling the pointer returned by glXGetProcAddressARB( (const GLubyte *) "glActiveStencilFaceEXT" ) on a TNT. My guess is that glXGetProcAddress will return non-NULL because the function is known. However, a TNT does not support GL_EXT_stencil_two_side, so the function won't actually be supported. It is entirely possible that Nvidia's driver just routes the call to a no-op function in cases like this. Nvidia does function dispatching a little differently than X.org's default libGL, so this is purely conjecture.

Things work slightly differently with GLX extension functions. All GLX functions require at least some processing inside libGL. In addition, there is no dispatch table for GLX functions. Because of this, X.org's default libGL will return NULL when glXGetProcAddress is called for an unknown GLX functions (i.e., any unknown function whose name begins with "glX").

Misconception #4: Naming function pointers the same thing as the function makes code more readable

A common tactic used by eh-hem Windows developers is to name the pointer to an extension function the same thing as the extension function (see the sample code below). On Windows, where opengl32.dll only exports functions for OpenGL 1.1, this is a semi-reasonable practice. On Linux, it is pure evil.

PFNGLACTIVESTENCILFACEEXT glActiveStencilFaceEXT = NULL;

...

glActiveStencilFaceEXT = (PFNGLACTIVESTENCILFACEEXT) glXGetProcAddressARB( (const GLubyte *) "glActiveStencilFaceEXT" );

...

glActiveStencilFaceEXT( GL_BACK );

Conceptually, the problem is that an application has now redefined a symbol exported by a library with a symbol of a different type. This is morally equivalent to putting 'void * printf' in a C program and expecting things to work correctly. What happens when another library is loaded that expects the original symbol to have it's original meaning? In the example above, if the application linked to a library that directly called glActiveStencilFaceEXT, that library would bind to the symbol in the application. Since the library is expecting glActiveStencilFaceEXT to be a function (but it's actually a pointer), it will jump to the symbol and crash.

In X.org 6.8.0 and earlier, this exactly what happened with the open-source 3D drivers. The drivers called several GL extension functions directly. If an application overrode those symbols with its own the driver would jump to function pointers, as if they were functions, and crash. The worst part is that backtraces from these crashes showed the problem to be in the driver.

Even though more resent open-source drivers have worked around this issue, it is still an application bug to give any symbol the same name as an existing symbol that has a different type. Period.


CategoryFaq