# Using PyOpenGL 3.x in Python

This document describes how to get started using OpenGL from Python, particularly using the (Py)OpenGL 3.0.0 package from pyopengl.sourceforge.net. This document assumes familiarity some familiarity with Python, OpenGL and Numpy (Numeric Python).

## Installing the Package

The OpenGL package uses the "SetupTools" package, which is a new, common mechanism for distributing and installing Python packages. It is quite likely that Python developers will already have setuptools installed on their system. If you do not have it installed, download and run ez_setup.py. If you want to install your Python egg files in a non-standard location be sure to setup your .pydistutils.cfg to support this before installing the packages.

Once you have setuptools installed already (many Python developers already will), simply issue the command:

    easy_install OpenGL

To have setuptools lookup the current version of the OpenGL package, download and install it. If setuptools fails to install the package, you may need to update your setuptools. Or you can try a direct download of the package with this command in the directory where you downloaded the package:

    easy_install -f . OpenGL

If you do not have administrative permissions on your machine, you can create a .pydistutils.cfg file in your home directory to tell setuptools where to install new .egg files.

If you use setuptools to package your application, you should declare a dependency on "OpenGL" to pull in the latest PyOpenGL 3.x release.

As of 3.0.0a3 OpenGL is dependant on the setuptools package. You cannot run without the setuptools support, as it is used to provide the plugin mechanism used by array data-type plugin mechanism. You will probably want to install numpy as well. ctypes is a dependency for Python 2.4 and Python 2.3 but included with Python 2.5.

### Bleeding Edge Development

If you are working in an area that's currently under active development you may prefer to use the CVS version of the OpenGL package. OpenGL-ctypes (the code-name for the 3.0.0 version of PyOpenGL) is developed and maintained within the PyOpenGL CVS repository. To check out the current version of OpenGL-ctypes:

    cvs -z3 -d:pserver:anonymous@pyopengl.cvs.sourceforge.net:/cvsroot/pyopengl co -P OpenGL-ctypes

You can install the checkout to your path for further development as follows (from the OpenGL-ctypes checkout directory):

    ./setupegg.py develop --install-dir=~/YOUR-WORKING-DIRECTORY-ON-PYTHONPATH-HERE

When you make a change, run `cvs diff` on the OpenGL-ctypes directory to produce a patch file and send it to me as an attachment. I prefer "context" diffs (`cvs diff -c`) for contributed code, as it makes it easier to see where the code fits in. That said, I'm happy to get code in any readily integrated format.

## Accessing OpenGL Functionality

The OpenGL library is a singleton instance for each process that is shared by all in-process code that uses OpenGL commands. Normally you will want to use OpenGL from within a GUI framework, such as wxPython, PyGame, Tkinter, PyGTK or PyQt. For stand-alone 3D programs, you may want to use the simple, but often quite effective,GLUT library, a wrapper for which is included in the OpenGL package.

Your GUI package will generally have a way to define an OpenGL "window" and set that window "active". Once the window is active, PyOpenGL commands will render into the window (as will any commands in the process issued from another language, such as a C extension). Normally this is handled by registering a handler for "events" from the GUI for such things are requests to display, resize, deal with mouse movements and the like.

To get access to basic OpenGL functionality, you should import the OpenGL.GL and OpenGL.GLU packages. This is normally done with global imports [XXX show multi-version-install usage as well]:

    from OpenGL.GL import *
    from OpenGL.GLU import *

If you want to access functions in an extension module, you can load the extensions via a similar import:

    from OpenGL.GL.ARB.shader_objects import *
    from OpenGL.GL.ARB.fragment_shader import *
    from OpenGL.GL.ARB.vertex_shader import *

It's a good idea to use the initialisation functions in the extension modules to check that the extension is available on the current machine before using any of the functions in the extension (note: calling the init functions was a requirement in PyOpenGL 2.x). For example:

    if not glInitShaderObjectsARB():
    	raise RuntimeError(
    		"""ARB Shader Objects extension is required """
    		"""but not supported on this machine!"""
    	)

or more reasonably, to trigger the use of fallback code for the missing functionality from the extension.

### API Changes from C-level OpenGL

Generally speaking, OpenGL 3.x tries to be compatible with the PyOpenGL 2.x series, which tried to provide a "Pythonic" interface to OpenGL. This includes providing support for implying various arguments, such as the dimensions of arrays from array's declaration of their dimensions. The online pydoc documentationprovides the most accurate (because it is auto-generated), though somewhat confusing, reference to the API as provided by PyOpenGL. We will be revising that documentation to be more friendly as work progresses.

If you are updating from PyOpenGL 2.x, see the Upgrading section below.

### Error Handling

As with previous versions of PyOpenGL, OpenGL 3.x tries to follow Python's

Errors should never pass silently.

philosophy, rather than OpenGL's philosophy of always requiring explicit checks for error conditions. OpenGL functions run the function `OpenGL.error.glCheckError` after each function call. This function is glBegin/glEnd aware, that is, the glBegin and glEnd functions enable and disable the checking of errors (because error checking doesn't work between those calls).

If you call C-level glBegin/glEnd the error checking may become confused. You should explicitly disable error checking (described below) in such a case.

You can override the error-handler, either to provide your own custom functionality, or to disable checking entirely. For instance, if you will always have a valid context, you could register the raw glGetError function as the error checker to avoid the overhead of the context-validity checks:

    from OpenGL import error
    error.ErrorChecker.registerChecker( myAlternateFunction )

OpenGL-ctypes has a set of errors defined in the `OpenGL.error` module. It can also raise standard Python exceptions, such as ValueError or TypeError. Finally, it can raise ctypes errors when argument conversion fails. (XXX that's sub-optimal, it has implementation details poking out to user code).

Wrapper objects catch OpenGL errors and annotate the error with extra information to make it easier to debug failures during the wrapping process.

### Tkinter (Legacy GUI) Togl Support

We have included the Python wrapper for the Tk Togl widget in the OpenGL-ctypes package. We do not, however, currently include the Togl widget itself. If you would like to use Togl in your package, please use your system's package manager to install the Togl package (or compile from source). You may have to recompile Python with Tk support as well.

## Performance Tips for Python + OpenGL

Python is (currently) a fairly slow language due to the incredible generality of it's execution model. This means that certain approaches to coding OpenGL that would work well in a language such a C or C++ will be painfully slow in Python. The key idea to remember is that, as much as possible, you want to push the work of iteration and repetition into the OpenGL implementation (which is implemented in C and/or Hardware).

There are two major approaches taken to accomplishing this:

- Use array-based geometry
- Use display-lists

Array based geometry uses OpenGL 1.1 features (supported almost everywhere) that allow for passing large arrays of data to be processed with a single call. Using numpy (or Numeric) arrays, you can readily pass your data into those functions without any need for Python-level iteration. This is the more flexible of the two approaches, as it allows for readily mutating the data being rendered without heavy recompilation costs and allows for special effects such as translucency. For static geometry, however, the display-list approach may be faster.

Display-list geometry normally uses slower individual-element rendering functions to render a piece of geometry in a special OpenGL mode which records the commands used to do the rendering. In future rendering passes the geometry can be rendered with a single call. You can create trees of display lists (where a root display list calls dozens of other display-lists) or call multiple display lists at once from an array.

### Avoiding Array-data Copying

One of the biggest slowdowns you will see in array-based PyOpenGL code is where you are passing:

- a compatible but inexactly matching array data-type
- a non-contiguous array (one which is not stored as a simple data-pointer)

In these cases PyOpenGL implementation will cause an extra copy of the data-set every time it is passed into any function. That copying, though done at the C level, can cause a signficant unnecessary overhead.

The copying is done by default to prevent new OpenGL coders from being confronted by mystifying errors about differences between "floats" and "double floats". It also makes PyOpenGL 3.x compatible with the PyOpenGL 2.x series.

PyOpenGL 3.0.0 provides a flag that allows you to raise Errors in situations where numpy data-arrays are being copied. You can use these error messages to optimise your application data-paths to eliminate the copying.To enable the checking:

    from OpenGL.arrays import numpymodule
    numpymodule.NumpyHandler.ERROR_ON_COPY = True

This will raise errors of the type:

    OpenGL.error.CopyError

With a description of the condition and why the copy was to be done.

### Context-specific Data

Because of the way OpenGL and ctypes handle, for instance, pointers, to array data, it is often necessary to ensure that a Python data-structure is retained (i.e. not garbage collected). This is done by storing the data in an array of data-values that are indexed by a context-specific key. The functions to provide this functionality are provided by the `OpenGL.contextdata` module.

The key that is used to index the storage array is provided by the platform module's `GetCurrentContext()` function. The current context is used if the context argument is passed in as `None`.

If you are creating and destroying rendering contexts, you need to be aware of the Context-specific storage. If you do not explicitly clean up the storage, you will produce a memory leak of all of the objects passed in as array data-sources and the like. You can either explicitly set the values to None (the standard OpenGL approach), or use code like the following:

    from OpenGL import contextdata
    def cleanupCallback( context=None ):
    	"""Create a cleanup callback to clear context-specific storage for the current context"""
    	def callback( context = contextdata.getContext( context ) ):
    		"""Clean up the context, assumes that the context will *not* render again!"""
    		contextdata.cleanupContext( context )
    	return callback

registering the callback to be called after the context is destroyed.

It is very important that you call the cleanup operation after the context is destroyed, as any attempt to render the context after the cleanup call will almost certainly result in memory-access errors and potentially core dumps!

## Get Involved

PyOpenGL is an Open Source project, and PyOpenGL 3.x is written in Python to allow Python coders to enhance the package. If you see something that's wrong, or something that you'd like to fix, let people know, or fix it and send a patch. We have documentation for those who are interested in contributing that should help you get started hacking on the code-base.

## Upgrading

PyOpenGL 3.x tries to be API-compatible with PyOpenGL 2.x as much as possible, but there are some differences that need to be considered when porting code:

- numpy versus Numeric

- PyOpenGL 2.x used (only) the older "Numeric" module. PyOpenGL 3.x can use Numeric (with an optional C extension), but uses numpy by default.

- different error types

- In a number of locations PyOpenGL 3.x will raise a different error type than PyOpenGL 2.x [XXX document which cases, at minimum every ctypes-level error is new (and should be eliminated, likely)]

- no "contiguous" function

- This function was provided to allow users to force a Numeric array to be a contiguous data-structure. Given that PyOpenGL 3.x is polyglot with regard to Numeric modules, it does not provide the function any more. See the vrml.array module's contiguous function for an example of how to write your own such function.

- [XXX there's more, I'm sure]