PyOpenGL for OpenGL Programmers

This document describes those features of PyOpenGL which are likely to be unfamiliar to OpenGL programmers. It also explains various features of PyOpenGL which are not covered in general OpenGL documentation.

Speed Concerns

PyOpenGL 3.x is far slower than PyOpenGL 2.x, and PyOpenGL 2.x was not fast. Out of the box PyOpenGL 3.x is configured to be as helpful and robust as possible, both traits which make the system much slower than it could be. PyOpenGL is now implemented using ctypes, rather than SWIG, the flexibility of ctypes has a significant cost in terms of performance. PyOpenGL 3.x is also far more flexible with regard to data-types, that flexibility has a noticeable performance cost. Throughout this document you will see descriptions for how to trade off flexibility, helpfulness and robustness in order to claw back speed.

If you are attempting to do per-vertex operations (e.g. glColor, glNormal, glVertex) then you will find that PyOpenGL 3.x is ridiculously slow. Luckily, all of those features are now deprecated. For array-based geometry where the arrays are large and in native-format-compatible formats, PyOpenGL's overhead tends to be amortized over the size of the arrays being processed.

Method Signatures vs. C OpenGL

Most of the functions which appear in PyOpenGL 3 are identical in calling method and functionality to that of the appropriate C specification. There are a few exceptions because of the differences between C and Python. Most of these exceptions are due to the difference between C and Python in the way that they access arrays. For example, a C function like this:

void foo(int count, const int *args);

will normally have the Python binding:

foo(args) -> None

Also C functions which write array data to a function argument like:

void bar(int args[4]);

will normally have the Python binding:

bar() -> args[]

The following sections will document changes other than simple changes like the above examples.

Errors raise Exceptions

PyOpenGL generally uses "strict" OpenGL operation, which is closer to the operation of Python itself, i.e. errors are raised as exceptions, rather than requiring the user to check return values and/or call glCheckError manually.

You can disable PyOpenGL's error checking by setting a module-level flag in the OpenGL package before importing any of the sub-modules, like so:

import OpenGL
OpenGL.ERROR_CHECKING = False
from OpenGL.GL import *
from OpenGL.GLU import *
...

This will tend to cause a huge speed increase in your code, as the number of OpenGL calls issued will roughly halve compared to the error-checking version of the same script. You will normally want to develop with error-checking on and only switch it off for production/release code.  Note, however, that most PyOpenGL code (including helper code in PyOpenGL itself) is written with the assumption that errors raise exceptions, so you will have to review all code you use to be sure you are calling glGetError where appropriate.

The exceptions normally raised by PyOpenGL are:

GL.GLerror is a subclass of GL.Error and has a large amount of extra information regarding the call wrapping process:

Logging

By default, PyOpenGL will log errors to the Python logging module. You can disable this by setting a flag before importing the OpenGL sub-modules:

import OpenGL
OpenGL.ERROR_LOGGING = False
from OpenGL.GL import *
...

which will provide a performance improvement for non-development code.

Conversely, if you are finding that your code is crashing and you need to trace through the operation of PyOpenGL then it is possible to instruct PyOpenGL to trace the calls to OpenGL to the logging module:

import OpenGL
OpenGL.FULL_LOGGING = True
from OpenGL.GL import *
...

this is extremely slow, but often useful during the debugging process.

Wrapped Operations

Wrapped Python arguments may go through a number of possible wrapper operations in order to produce a final call from the Python argument-set:

These operations are implemented by the OpenGL.wrapper.Wrapper class, which is the focus of most of theOpenGL_accelerate module's optimizations (for obvious reasons).

Array Handling

PyOpenGL 3.x supports a significant range of array-compatible data-types, among the default plug-ins are:

when a PyOpenGL 3.x entry point requires an "array" data-type (or a void* data-type), it will use the PyOpenGL FormatHandler plugins (OpenGL.arrays.formathandler) to decide how to convert the array to a format compatible with the low-level OpenGL APIs.

Certain format-types may allow for use as array types while not actually containing a compatible copy of the data. Python lists, tuples and numbers all require the creation of a temporary variable to hold their data. As such you generally should not use them for performance-critical operations. Similarly, if you pass a numpy array with an incompatible data-type the wrapper may have to copy the data in order to pass it into the C-level functions. For numpy arrays, where the behavior is fast enough that you might not realize it, but can negatively impact your performance, you can prevent this behaviour by setting a flag in the OpenGL package before importing any of the OpenGL modules:

import OpenGL
OpenGL.ERROR_ON_COPY = True
from OpenGL.GL import *

which will cause the numpy format handler to raise errors if it finds itself copying an array to another data-type.

Keep in mind that with OpenGL 3.1 it will become required that all non-index arrays be managed as Vertex Buffer Objects.

Type-Specialized Array Functions

Each call which sets an array pointer, such as glVertexPointer, may have many variants. First there will a function which is identical that of the specification. For the pointer argument one should pass a string. Also note that the stride values are used.

Next there will a set of functions named:

glXPointer{ub|b|us|s|ui|i|f|d}

These will usually take as a single argument a multidimensional array of values. The type argument is controlled by the suffix of the function (ub is unsigned byte, b is byte, f is float, d is double etc.) Most other arguments are derived from the dimensions of the array.

So for glColorPointer we have:

glColorPointer(size, type, stride, pointer) -> None
glColorPointerub(pointer[][]) -> None
glColorPointerb(pointer[][]) -> None
glColorPointerus(pointer[][]) -> None
glColorPointers(pointer[][]) -> None
glColorPointerui(pointer[][]) -> None
glColorPointeri(pointer[][]) -> None
glColorPointerf(pointer[][]) -> None
glColorPointerd(pointer[][]) -> None

This same decoration strategy is used for other array functions besides glXPointer.

For instance, glDrawElements has the Python binding:

glDrawElements(mode, count, type, indices) -> None

where indices is expected to be a string. There are also the decorated bindings:

glDrawElementsub(mode, indices[]) -> None
glDrawElementsus(mode, indices[]) -> None
glDrawElementsui(mode, indices[]) -> None

where "indices" is now a single dimensional array.

When calling a glColorPointer, glVertexPointer, etc. Python needs to allocate memory to store the values that OpenGL needs. This memory is reference counted and takes into account function calls like glPushClientAttrib and glPopClientAttrib. To force this memory to be released one just need to make a call glColorPointerub(None).

Currently glPushClientAttrib will always set the GL_CLIENT_VERTEX_ARRAY_BIT flag as glPopClientAttrib has no way of knowing that flag was set and the state of the flag is needed to know whether or not to decrement the pointer locks on the allocated memory.

This may change in the future. That said, surrounding array use glPushClientAttrib/glPopClientAttrib is a good way to force the release of any allocated memory, but make sure that all calls to glXPointer, etc. are within the ClientAttrib block if you chose to use this scheme.

Note that since all the memory allocation is automatic there is no need for glGetPointerv function, so it is excluded.

Note that the function glInterleavedArrays is also present, but it does not have the variants that the others do (i.e., no glInterleavedArraysf). glInterleavedArrays has been unofficially deprecated for a long time, and is officially deprecated as of OpenGL 3.x

Note that for performance you may wish to use the "raw" version of array-handling functions, as these often have less processing applied than the "raw" version.

Image Routines

glDrawPixels and the other image/texturing functions have much the same decoration scheme as the array functions. For glDrawPixels there is the standard function which expects a string as the pixel data:

glDrawPixels(width, height, format, type, pixels) -> None

This function will respect the parameters set by glPixelStore{i|f}.

There is also a collection of variants which take a multidimensional array as the data source and set glPixelStore{i|f} automatically. For example:

glDrawPixelsub(format, pixels) -> None

Notice that width and height are inferred from the pixel data and the type is GLubyte.

PyOpenGL sets up "normal" pixel-transfer mode when using imaging APIs, as almost all Python image-aware modules/extensions assume, for instance, tightly packed data-structures and would potentially cause access errors when operating in standard OpenGL mode.

Extensions and Conditional Functionality

PyOpenGL has support for most OpenGL extensions. Extensions are available as "normal" function pointers by importing the constructed package name for the extension, for instance:

from OpenGL.GL.ARB.vertex_buffer_object import *
buffer = glGenBuffersARB(1)

there is no need to call initialization functions or the like for the extension module. You can, if you like, call the "init" function for the extension to retrieve a boolean indicating whether the local machine supports a given extension, like so:

if glInitVertexBufferObjectARB():
...

However, it is normally clearer to test for the boolean truth of the entry points you wish to use:

if (glGenBuffersARB):
buffers = glGenBuffersARB( 1 )

There are often a number of entry points which implement the same API, for which you would like to use whichever implementation is available (likely with some preference in order). The OpenGL.extensions module provides an easy mechanism to support this:

from OpenGL.extensions import alternate
glCreateProgram = alternate( 'glCreateProgram', glCreateProgram, glCreateProgramObjectARB)
glCreateProgram = alternate( glCreateProgram, glCreateProgramObjectARB)

If the first element is a string it will be used as the name of the alternate object, otherwise the name is taken from the first argument.

Selection and Feedback Buffers

Note that both selection and feedback buffers are deprecated in OpenGL 3.x. You should be replacing selection-buffer usage with either "unique color" selection rendering or mathematical generated selection operations.

Normally in OpenGL to use a selection buffer one would do the following:

GLuint buffer[SIZE];
glSelectBuffer(SIZE, buffer);
glRenderMode(GL_SELECT);
/* draw some stuff */
GLint count = glRenderMode(GL_RENDER);
/* parse the selection buffer */

In Python this accomplished like this:

glSelectBuffer(SIZE) # allocate a selection buffer of SIZE elements
glRenderMode(GL_SELECT)
# draw some stuff
buffer = glRenderMode(GL_RENDER)
for hit_record in buffer:
    min_depth, max_depth, names = hit_record
    # do something with the record

Feedback buffers are used in the same way except that each item in the buffer is tuple (token, value), where value is either a passthrough token or a list of vertices.

Note that if glRenderMode returns a buffer then it also resets OpenGL's pointer for the corresponding buffer. This means that the buffer returned by glRenderMode is independent of future calls to glRenderMode, i.e. it will not be overwritten by any such future calls. This makes the returned buffer somewhat thread-safe. It also means that every call to glRenderMode(GL_SELECT | GL_FEEDBACK) needs to preceded by a call to glSelectBuffer or glFeedbackBuffer first, i.e. the following code will not work:

### THIS DOESN'T WORK!!!
glSelectBuffer(SIZE) # allocate a selection buffer of SIZE elements
glRenderMode(GL_SELECT)
# draw some stuff
buffer = glRenderMode(GL_RENDER)
# do another selection
glRenderMode(GL_SELECT)
# draw some stuff
buffer = glRenderMode(GL_RENDER)

Instead one must do:

glSelectBuffer(SIZE) # allocate a selection buffer of SIZE elements
glRenderMode(GL_SELECT)
# draw some stuff
buffer = glRenderMode(GL_RENDER)
# do another selection
glSelectBuffer(SIZE) allocate a new selection buffer
glRenderMode(GL_SELECT)
# draw some stuff
buffer = glRenderMode(GL_RENDER)

Function Aliases

PyOpenGL has historically provided a number of aliases for functions.  For backwards compatibility, these aliases continue to be provided: