SIGGRAPH '97

Course 24: OpenGL and Window System Integration

Using OpenGL Extensions



Contents



1. Introduction

The designers of OpenGL anticipated the need to extend OpenGL in the future. Thus they clearly defined how extensions are to be implemented and used. To be sure your application is portable it is very important that one uses extensions correctly.

There are three tenets to using extensions:

  1. Compile-time extension testing
  2. Run-time extension testing
  3. Fall-back scenarios
These are discussed below. Furthermore, one may also have to deal with different versions of OpenGL and the GLU and GLX libraries.

We begin with a discussing of extension naming conventions.



2. Naming conventions

OpenGL extension are named according to the convention:

GL_type_name

Where type is EXT or a vendor-specific identifier such as SGI or IBM.

The EXT indentifier generally indicates that an extension has been adopted by at least two vendors.

Vendors may also extend the type convention to indicate the class of the extension. Silicon Graphics, for example, use SGIS to indicate an extension may only be available on particular systems and SGIX to indicate that the extension is experimental.

name is a string of lowercase characters such as polygon_offset.

Example extension names:

If an extension defines any new GLenum values they will be suffixed with the extension type. For example, the GL_EXT_blend_minmax extension adds the following GLenum values: If an extension defines any new API functions they will be suffixed with the extension type as well. For example, the GL_EXT_polygon_offset extension adds the function:

void glPolygonOffsetEXT( GLfloat factor, GLfloat bias )



3. An extension sampler

This section lists some OpenGL extensions with short descriptions. Many extensions are implemented in groups. For example, the blending extensions are interdependent and usually implemented together. See your OS/OpenGL release notes and man pages for detailed descriptions.

Note the naming schemes.

Core extensions

Many of these extensions to OpenGL 1.0 have been incorporated into OpenGL 1.1.

SGI-specific core extensions

GLX Extensions (see section 8)

SGI-specific GLX extensions

Microsoft OpenGL Extensions



4. Compile-time extension testing

If an OpenGL extension is supported at compile-time the host's gl.h file will define a preprocessor symbol named for that extension. For example, the gl.h file will have
#define GL_EXT_texture3D 1
if the GL_EXT_texture3D extension is supported.

Any references to constants or functions defined by the extension must be surrounded by #ifdef/#endif. For example:

#ifdef GL_EXT_texture3D
   glTexImage3DEXT(GL_TEXTURE_3D_EXT, 0, format, w, h, d, border,
                   format, type, pixels);
#endif
Failure to test for extensions at compile time can result in compilation and link errors such as Undefined symbol or Undefined function.

It is critical to properly test for extensions at compile time if you want your application to be recompilable on different systems.



5. Run-time extension testing

We must also test for OpenGL extensions at runtime. There are two reasons for this:
  1. An OpenGL application may be dynamically linked to the OpenGL library. When the application is moved to another system with a different OpenGL library there's no guarantee that this library will implement the same extensions as the first library.
  2. OpenGL on the X Window System supports remote display and there's no guarantee that any X server's OpenGL renderer will support a given extension.

4.1 Function Bindings

When using dynamic libraries, the binding of function calls to library functions can be either be done when the program is loaded into memory or when the library function is called for the first time.

The former case dictates that our application should not have any calls to extension functions which may be missing in the OpenGL library. The later case dictates that we simply must not call a missing extension function during execution.

To maximize portability we should obey the more stringent first case.

The solution is to query the dynamic library for extension functions and call them via function pointers. This is the recommended practice for Windows 95 and NT.

Operating systems which support dynamic libraries have an API for working with dynamic libraries. The only feature we need is the ability to get the address of function by name.

Two examples follow.

4.1.1 Microsoft Windows 95 and NT and SGI Cosmo OpenGL

The WGL interface to OpenGL has wglGetProcAddress. It is used to get the address of OpenGL extension functions.

For example, suppose we want to use the glArrayElementEXT function in our OpenGL application. We would use the following code:

/* Get pointer to the function, if it exists. */
PFNGLARRAYELEMENTEXTPROC glArrayElementPtr;
glArrayElementPtr = (PFNGLARRAYELEMENTEXTPROC)
		wglGetProcAddress("glArrayElementEXT");

/* ... */

/* instead of glArrayElementEXT(i) we do this: */
if (glArrayElementPtr) {
	(*glArrayElementPtr)(i);
}

4.1.2 IRIX

On SGI systems, the dlsym function is used to get the address of a named function.

For example, suppose we want to use the glArrayElementEXT function in our OpenGL application. We would use the following code:

#include <dlfcn.h>

typedef void (*glArrayElementProc)(int i);

void *libHandle;
glArrayElementProc glArrayElementPtr;

/* Open GL library and get pointer to the function, if it exists. */
libHandle = dlopen("libgl.so", RTLD_LAZY);
glArrayElementPtr = (glArrayElementProc)
		dlsym(libHandle, "glArrayElementEXT");

/* ... */

/* instead of glArrayElementEXT(i) we do this: */
if (glArrayElementPTr) {
	(*glArrayElementPtr)(i);
}

4.2 Renderer Testing

With GLX, OpenGL rendering can be directed to a non-local X server. The target X server may not support the extension(s) your application uses. To prevent problems we must query the renderer to determine which extensions it supports.

The glGetString(GL_EXTENSIONS) function returns a list of extensions which are supported by the OpenGL renderer. This list can be searched to determine if a specific extension is supported.

Be aware that glGetString(GL_EXTENSIONS) must be called after we've established an active OpenGL rendering context. For example, we must call glXMakeCurrent or wglMakeCurrent before calling glGetString. The reason is that OpenGL extensions are dependant on the OpenGL renderer and the renderer isn't bound until MakeCurrent is called.

Be careful when searching the extensions list! The C library function strstr is not sufficient because it may match a substring of the extension name you're testing for. For example, if you're testing for the GL_EXT_texture extension and glGetString(GL_EXTENSIONS) returns "GL_EXT_texture3D" then simply using strstr will incorrectly tell you that GL_EXT_texture is supported.

The following function can be used for reliable runtime extension testing:

      GLboolean CheckExtension( char *extName )
      {
        /*
         ** Search for extName in the extensions string.  Use of strstr()
         ** is not sufficient because extension names can be prefixes of
         ** other extension names.  Could use strtok() but the constant
         ** string returned by glGetString can be in read-only memory.
         */
        char *p = (char *) glGetString(GL_EXTENSIONS);
        char *end;
        int extNameLen;

        extNameLen = strlen(extName);
        end = p + strlen(p);
    
        while (p < end) {
            int n = strcspn(p, " ");
            if ((extNameLen == n) && (strncmp(extName, p, n) == 0)) {
                return GL_TRUE;
            }
            p += (n + 1);
        }
        return GL_FALSE;
      }


6. OpenGL 1.1

Many extensions designed for OpenGL 1.0 have been incorporated into OpenGL 1.1 as standard features.

A program written for OpenGL 1.0 which uses no extensions will work with OpenGL 1.1 unchanged. However, a program written for OpenGL 1.0 with extensions may require some modifications to work with OpenGL 1.1.

If you want your program to compile and execute cleanly with either OpenGL 1.0 or OpenGL 1.1 you will need to observe the following guidelines.


Compile time

To detect whether a particular feature is available at compile time you will need to use the C preprocessor to test for either an OpenGL 1.0 extension name or test for the OpenGL 1.1 version symbol: GL_VERSION_1_1.

For example:

#if defined(GL_EXT_texture_object) || defined(GL_VERSION_1_1)
   your code
#endif
Sometime in the future you may need
#if defined(GL_EXT_texture_object) || defined(GL_VERSION_1_1) || defined(GL_VERSION_1_2)
   your code
#endif

Runtime

At runtime you must check if the renderer supports OpenGL 1.1 or the 1.0 extension:

/* After calling MakeCurrent()! */
char *version = (char*) glGetString(GL_VERSION);
GLboolean HaveTexObjExtension;

if (strncmp(version,"1.1",3)==0
    || CheckExtension("GL_EXT_texture_object")) {
    HaveTexObjExtension = GL_TRUE;
}
else {
    HaveTexObjExtension = GL_FALSE;
}


7. GLU extensions and versions

There have been several versions of the GLU (GL Utility) library and the library may have extensions. Again, for safety, the GLU version and extensions should be tested for at compile-time and run-time if you need their specific features. At this time, there are no known GLU extensions but a few different GLU versions.

Compile-time testing

If a GLU extension is available at runtime the glu.h file will define a preprocessor symbol with the prefix GLU_EXT_. As with OpenGL extensions, there should be #ifdef/#endif tests surrounding any references to functions or symbols unique to the extension.

Run-time testing

GLU version 1.0 had no function to call at run-time to query the GLU version or extensions list. GLU version 1.1 added the gluGetString function which takes two possible values: GLU_EXTENSIONS or GLU_VERSION.

Therefore, if you want to get a list of GLU extensions you'll need to use something like this:

char *extensions;
#ifdef GLU_VERSION_1_1
extensions = (char *) gluGetString(GLU_EXTENSIONS);
#else
extensions = "";
#endif
Be careful of accidently matching substrings while searching the string.

GLU versions

There have been several versions of the GLU library. As shown above, you can test for the GLU version at compile-time by checking for preprocessor symbols like GLU_VERSION_1_1 and GLU_VERSION_1_2. At run-time you can determine the GLU version by calling gluGetString(GLU_VERSION).

Version 1.1 of GLU only added the gluGetString function.

Version 1.2 of GLU introduced a new polygon tessellator. The new tessellator functions all begin with the prefix gluTess. For more information about the changes in the GLU tesselator from version 1.0 to 1.1 see http://www.digital.com:80/pub/doc/opengl/opengl_new_glu.html

Note that if the GLU_VERSION_1_2 symbol is defined then the GLU_VERSION_1_1 symbol is also defined. One can expect this trend of backward compatibility to continue.



8. GLX extensions

The GLX interface offers extensions in a manner very similar to core OpenGL. Again, extensions must be tested for both at compile-time and run-time. If a GLX extension is not available there should be a fall-back strategy.

Compile-time testing

If a GLX extension is available at runtime the glx.h file will define a corresponding preprocessor symbol. For example, if the GLX_EXT_import_context extension is available, then glx.h (or glxtokens.h) will contain
#define GLX_EXT_import_context	1

Run-time testing

After we've established a connection to an X server we can determine which GLX extensions are available by calling glXQueryExtensionsString(dpy, screen). This function returns a list of supported GLX extensions separated by white space. Again, we have to be careful when searching the extensions list. A function similar to CheckExtension should be used.

GLX version testing

There have been several versions of the GLX interface. Version 1.0 was the first version. Version 1.1 added the glXQueryExtensionsString, glXQueryServerString and glXGetClientString functions. Version 1.2 may include several of the 1.0 and 1.1 GLX extension features.

Testing for the GLX version at runtime involves checking for a preprocessor symbol such as GLX_VERSION_1_1 or GLX_VERSION_1_2.

The GLX version can be determined at runtime by calling glXQueryVersion.



9. Fall-back scenarios

Your program should be prepared for the likely situation in which a desired extension is not available. Depending on the nature of the extension you may elect to limit functionality, fall-back to an equivalent but slower implementation, or to simply abort.

Examples:

Aborting when an extension isn't available is strongly discouraged. In most cases users will prefer reduced performance/functionality over complete failure. At the very least, the user should be informed why an OpenGL application can't operate if an extension isn't present.



10. Putting it all Together

Dealing with OpenGL versions and extensions is a bit complicated. This is an example, using texture objects, of how one may cope with the complexity. The basic idea is to write wrapper functions for extensions to hide the complexity.

This is just one approach however. For your application it may be appropriate to implement things differently.


First, we need some function pointers:

typedef void (*glGenTexturesProc)( GLsizei n, GLuint *textures );
typedef void (*glDeleteTexturesProc)( GLsizei n, const GLuint *textures );
typedef void (*glBindTextureProc)( GLenum target, GLuint texture );

glGenTexturesProc glGenTexturesPtr = NULL;
glDeleteTexturesProc glDeleteTexturesPtr = NULL;
glBindTextureProc glBindTexturePtr = NULL;

Second, we'll call this function after we've made current our OpenGL context:

void ExtensionSetup(void)
{
    char *version = (char*) glGetString(GL_VERSION);

    if (strncmp(version,"1.1",3)==0) {
        /* OpenGL 1.1 */
        glGenTexturesPtr = LookupFunction("glGenTextures");
        glDeleteTexturesPtr = LookupFunction("glDeleteTextures");
        glBindTexturePtr = LookupFunction("glBindTexture");
    }
    else {
        /* OpenGL 1.0 */
        int haveTextureObjects = CheckExtension("GL_EXT_texture_object");
	if (haveTextureObjects) {
            glGenTexturesPtr = LookupFunction("glGenTexturesEXT");
            glDeleteTexturesPtr = LookupFunction("glDeleteTexturesEXT");
            glBindTexturePtr = LookupFunction("glBindTextureEXT");
        }
        else {
            glGenTexturesPtr = NULL;
            glDeleteTexturesPtr = NULL;
            glBindTexturePtr = NULL;
        }
    }
}
The LookupFunction function finds function pointers in dynamic libraries. It may be implemented with something like this:
void *LookupFunction(const char *funcName)
{
#if defined(__WIN32__)
    return wglGetProcAddress(funcName);
#elif defined(IRIX)
    void *libHandle = dlopen("libgl.so", RTLD_LAZY);
    void *func = dlsym(libHandle, funcName);
    dlclose(libHandle);
    return func;
#else
    /* other OSes... */
#endif
}

Third, we'll implement wrapper functions for texture object functionality.

/*
 * Allocate a set of texture objects.
 */
void myGenTextures( GLsizei n, GLuint *textures )
{
    if (glGenTexturesPtr) {
        /* Call OpenGL 1.1 or 1.0 extension function */
        (*glGenTexturesPtr)( n, textures );
    }
    else {
        /* fallback code: use display lists */
        GLuint first;
        first = glGenLists( n );
        if (first>0) {
            GLuint i;
            for (i=0; i < n; i++) {
                textures[i] = first+i;
            }
        }
    }
}


/*
 * Begin definition of a texture object.
 */
void myBeginTexture( GLenum target, GLuint texture )
{
    if (glBindTexturePtr) {
        (*glBindTexturePtr)( target, texture );
    }
    else {
        /* fallback code: use display lists */
        glNewList( texture, GL_COMPILE );
    }
}


/*
 * End definition of a texture object.
 */
void myEndTexture( GLenum target )
{
    if (glBindTexturePtr) {
        (*glBindTexturePtr)( target, 0 );
    }
    else {
        /* fallback code: use display lists */
        glEndList();
    }
}



/*
 * Bind (use) a texture object.
 */
void myBindTexture( GLenum target, GLuint texture )
{
    if (glBindTexturePtr) {
        (*glBindTexturePtr)( target, texture );
    }
    else {
        /* fallback code: use display lists */
        glCallList( texture );
    }
}

Finally, in our application we'll use the myGenTextures, myBeginTexture, myEndTexture, myBindTexture functions to actually use texture objects.

When designing wrapper functions it's probably best to look at the big picture and design simple, high-level wrappers rather than try to make wrappers which directly corresponds to individual OpenGL functions.

A collection of wrappers like these may be put in a separate source file and reused in many applications.



11. References

Other sources of information about OpenGL extensions can be found at:


Last edited on July 9, 1997 by Brian Paul.