Author: Mike C. Fletcher

# Contributing to OpenGL-ctypes

This document describes how to get started with contributing to the OpenGL-ctypes project, the project which is creating the PyOpenGL 3.0.0 release. It outlines the basic architecture of the system and how to begin work on the system for a new developer. It assumes familiarity with Python, Numpy and ctypes.

## The History of OpenGL-ctypes

OpenGL-ctypes is a re-implementation of the OpenGL bindings for Python. Historically there were two other mainline implementations of OpenGL for Python.

- PyOpenGL up to 1.x was written as a C extension using the raw Python C API. It was characterised by a very intuitive "Pythonic" API that just seemed to do what you expected it to do (in most cases) because the authors had actually sat down and written the code for every function. Maintenance and extension was extremely hard, and support for extensions was minimal or non-existant. This is the code base originally created by David Ascher et. al. This code base has been entirely superceded by PyOpenGL 2.x.
- PyOpenGL 2.x was written as a set of SWIG typemaps (a.k.a. PyOpenGLSWIG. The code was basically a huge set of nested macro expansions, SWIG pattern matching rules and a heavily hacked distutils script that semi-automatically turned OpenGL headers into usable C extensions. PyOpenGL 2.x had far greater support for extensions and far better coverage than the 1.x series, but it was extremely difficult to modify or debug the system. It was also notoriously difficult to compile/install. This is the codebase created by Tarn Weisner Burton.

OpenGL-ctypes is intended to become the 3.x release of "PyOpenGL", that is, it will eventually replace the 2.x stream as the "standard" PyOpenGL. However, to get there, it needs developer attention. In particular, it needs some porting work for new architectures, testing and debugging on all architectures, and lots of work on extension development. If you are interested in the rationale for OpenGL-ctypes, see this posting, which outlines why the reimplementation is being undertaken.

## Getting the Code

OpenGL-ctypes 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):

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

As of 3.0.0a3 OpenGL-ctypes 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 likely already have setuptools installed, but if not, download the ez_setup.py script and run it to install the package. You will probably want to install numpy as well, and in case you missed it, ctypes is a dependency (for Python 2.4 and below), it is declared in the setuptools dependencies for version 3.0.0a5 and above so that it should automatically be installed for you when you "develop" your PyOpenGL working directory.

When you make a change, run `cvs diff` on the OpenGL-ctypes directory to produce a patch file and upload it to the PyOpenGL Patch Tracker 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. We discuss PyOpenGL development on the PyOpenGL-dev mailing list.

## The Architecture of OpenGL-ctypes

Here are the loose design goals of OpenGL-ctypes:

- Access to the all of OpenGL's functionality

- Allow access to OpenGL 1.x and 2.x functionality
- Access to modern extensions

- Backward compatability with PyOpenGL code

- Not a fanatic requirement, but I want 90%+ of code to run without changes and 99% to run with only minor changes such as one would expect from a major release of a large package
- Insulate the PyOpenGL developer from the implementation (ctypes) so that code can continue to work across multiple implementations

- Ease of development

- The whole point of this is to make it possible for more people to work on the system

- Play nicely with others

- Alternate Python implementations (PyPy, IronPython)
- GUI libraries
- Numeric libraries
- Threading behaviour
- Multimedia libraries

- Flexibility and robustness

- Graceful degradation in the face of missing functionality
- Ease of debugging
- Informative error reports

- Ease of installation

- Portability (note, we are limited to ctypes ported platforms, but that should grow faster than we grow, given that ctypes is now part of Python)
- Simplicity of installation process (no hacked up distutils!)
- Use of eggs and easy_install
- Ability to plug in support for new data-types and the like

### Platform Abstraction

OpenGL-ctypes is exposing "platform" (Operating System and Hardware) functionality to the Python environment. Differences among the various platforms are abstracted such that porting OpenGL-ctypes to a new platform is largely a matter of implementing a small module in the "platform" sub-package.

Each platform gets their own `OpenGL.platform.baseplatform.BasePlatform` subclass. `OpenGL.platform.baseplatform.BasePlatform` classes provide:

- ctypes library objects for GL, GLU, GLUT and possibly GLE. How we find these libraries, the names of the libraries, flags needed for loading and the like tends to be system specific
- definition of the ctypes `FunctionType` used for calling functions in the libraries, i.e. what calling convention to use for calling the functions. On Windows, for instance, we have to use windows, rather than C calling conventions.
- functions `GetCurrentContext()` and `CurrentContextIsValid()` to allow code to retrieve and/or test whether we have a current context. This is used to implement context-specific data-storage. These are normally exposed by the platform's OpenGL implementation
- function `getExtensionProcedure( name )` to retrieve an OpenGL extension function by name
- function `getGLUTFontPointer( constant )` to retrieve a `void *` to a GLUT font, different platforms use very different conventions for these values
- flags `HAS_DYNAMIC_EXT` and `EXT_DEFINES_PROTO` which tell OpenGL-ctypes whether the platform has the ability to load dynamic extensions and whether it uses prototype definitions
- function safeGetError() to retrieve OpenGL's error state if doing so would be safe (i.e. we have a valid context or the platform can handle having a call to glGetError when there is no valid context)
- functions/methods to create/retrieve function pointers from libraries (createBaseFunction and createExtensionFunction), which insulate us from changes in ctypes as well as giving us a convenient place to add functionality to any function.

      def createBaseFunction(
          functionName, dll=OpenGL,
          resultType=ctypes.c_int, argTypes=(),
          doc = None, argNames = (),
      ):

  and

      def createExtensionFunction(
      	functionName, dll=OpenGL,
      	resultType=ctypes.c_int,
      	argTypes=(),
      	doc = None, argNames = (),
      ):

New platform implementations are registered in `setup.py` using pkgtools entry points. We use the sys.platform and os.name (in that order of preference) to decide which entry point to load.

### Autogenerated Wrappers

There are two major wrapper generator systems available for use with ctypes. Originally OpenGL-ctypes used the ctypes generator module (based on GCC-XML) to produce its wrappers. We are switching to using the `pyglet/tools/wraptypes` module. Extension modules are currently created via a regex-based generator, eventually we'll switch to using wraptypes for that work as well.

#### wraptypes Generator (new method)

The primary value of wraptypes is that it is easy to install and configure, and allows for parsing headers which are not "native" to the platform on which they are being run. The wrapper generator (`gengl.py`) is not as advanced/finished as the `openglgenerator.py` module, but it can generate the platform-specific module quite nicely, and the underlying code is under active development with a focus on GL-related operations.

To run this generator, simple check out pyglet's svn repository and add the tools/wraptypes package to your `PYTHONPATH`, then run the `src/gengl.py` module in PyOpenGL's source tree.

#### ctypes Wrapper Generator (old method)

ctypes includes a mechanism based on GCC-XML which will autogenerate wrappers for many C libraries. OpenGL-ctypes uses an extended version of this autogenerator (in the src subdirectory of the CVS repository, see `generateraw.py` and `openglgenerator.py`) to produce the C-style "raw" API for the core libraries. These are the modules in the `OpenGL.raw` packages. If you wish to use ctypes directly with a C-style API it is possible to directly import and use these modules.

The generator also produces "annotations" modules in each of the raw.* packages. These contain calls which wrap the base functions in size-of-array-aware wrappers. The constants from the module are also split into a separate module for easier reading (this module is then imported into the main module).

Much of the API for the core libraries can be used as-is from the `raw` packages, so the main package modules normally import all symbols from their `raw.XXX` and `raw.XXX.annotations` module before importing from modules providing customised functionality.

##### Making your own Auto-generated Wrappers

If you want to create new core modules (see below for OpenGL extension modules), you can use the ctypes_codegen module from ctypes svn. This module is dependant on having a very up-to-date (i.e. not yet officially released) gccxml. Windows users can download this gccxml from the ctypes SourceForge download page where it was released with the 0.9.6 ctypes release (note that the release requires the Visual C runtime from VC6, which is not included in the download and may not be present on your machine if you have not installed a VC6-compiled product).

You can check out the ctypeslib project from the Python svn tree like so:

    svn co http://svn.python.org/projects/ctypes/trunk ctypes
    cd ctypes/ctypeslib
    python setup.py install

To create a new PyOpenGL sub-package, such as one to support your platform-native GLX work-alike (e.g. WGL or AGL), you first create an XML description of your library. Here's an example:

    python ~/site-packages/ctypes_codegen/h2xml.py /usr/include/GL/glx.h -o glx.xml -c

which produces an XML file in your local directory (here glx.xml). The `-c` flag tells GCC-XML to attempt to include preprocessor definitions in the XML file, which is normally desirable for GL-related systems, as they tend to have a lot of constants defined in preprocessor directives.

Once we have the XML file, we need to turn it into Python source files. The `src/generateraw.py` script in the OpenGL-ctypes source distribution is an example of how to create new wrappers. Keep in mind that the output here is only going to include those functions which are defined on your system (particularly, in the DLL/.so that you specify with the `-l` flag). That means that locally unavailable functions may not show up in the resulting files.

Autogeneration has gone through many revisions over the course of the project. The newest version requires a heavily restructured version of the ctypes codegenerator module to work.

#### OpenGL Extension Generation

Unlike the core modules, extension modules are auto-generated using a low-level hack that downloads the OpenGL extension registry's current header file and parses the file with regexes, matching the two definitions so that it can generate a single `createExtensionFunction` call that has all of the data-type and naming information for that function.

The OpenGL extension definitions in the `glext.h` header are split into two parts, a macro which retrieves the pointer to the function, and the function itself. One of the definitions provides the parameter list, the other provides the properly-capitalised name. We need both to use the function.

A consequence of this approach is that OpenGL-ctypes can remain up-to-date for OpenGL extensions. Producing the raw versions of all new extensions is normally just a single command away.

glGet() output arrays are handled specially by the auto-generation. It produces calls which register constants against array sizes so that the glGet* family of calls can return the correctly-sized array for a given constant. It does this by combining information from the specification documents and the size specifications stored in the glgetsizes.csv document in the source directory.

The autogeneration process also attempts to copy the "Overview" section of the specification for each module into the docstring of the module, so that users and coders can readily identify the purpose of the module.

The extension modules are written as a single file, with the code for the auto-generated material placed above a comment line which tells you not to edit above it.

    ### DO NOT EDIT above the line "END AUTOGENERATED SECTION" below!
    ...
    ### END AUTOGENERATED SECTION

Customisations of the extension are done after the auto-generated section. This (single file approach) is done mostly to reduce the number of files in the project and to make it easier to hack on a single extension.

It is expected and encouraged that users will hack on an extension module they care about to make it more Pythonic and then contribute the changes back to the project.

If you remove the autogenerated comment then further autogeneration passes will not process the module, keep in mind, however, that improvements to the extension autogeneration will likely occur over time.

### Converters and Wrappers

When a method cannot use the autogenerated ctypes wrapper as-is, we normally fall back to the `OpenGL.wrapper.Wrapper` class, and the converters defined in the `OpenGL.converters` module. The Wrapper class provides a set of argument transformation stages which allow for composing most functions from a common set of simple operations.

This approach will seem familiar to those who have looked at the source code generated by systems such as SWIG. There you define a set of matching rules which include snippets of code which are composed into the C function being compiled. Instead of rule-based matching, we use explicit specification.

In some cases it's just easier to code up a custom wrapper function that uses raw ctypes. We can do so without a problem simply by including the code in the namespace with the appropriate name.

#### Wrapper Objects

The stages in the Wrapper call are as follows:

1. pyConverters -- accept (or suppress) incoming Python arguments

- Used to convert a Python argument to a type which is compatible with the expected data-type
- Can be used to eliminate an argument from the argument list by passing no function, this is useful when we want to imply an argument from the structure of another argument, for instance

2. cConverters -- pull C-compatible argument out of the Python argument list

- Looks at the whole set of Python argument and produces a 1:1 argument list of Python objects for the ctypes call

3. cResolvers -- take the C-compatible Python objects and turn them into the low-level data-type required by the ctypes call
4. ctypes call, (with error checking, errors are annotated with the arguments used during the call)
5. storeValues -- called to store a C-compatible Python object, for instance to prevent garbage collection of an array that is in-use
6. returnValues -- determines what result is required from the wrapped function

Of particular interest is the method `wrapper.Wrapper.setOutput` which allows you to generate output arrays for a function using a passed-in size tuple, dictionary or function to determine the appropriate size for the array. See the `OpenGL.GL.glget` module for examples of usage.

#### Converters

The `OpenGL.converters` module provides a number of conversion "functions" for use with the wrapper module's Wrapper objects. The idea of these converter functions is to produce readily re-used code that describes a common idiom in wrapping a function. The core libraries and extensions then use these idioms to simplify the wrapping of their functions.

### Array Handling

While you can do a great deal of work with OpenGL without array operations, Python's OpenGL interfaces are all fastest when you use array (or display-list) techniques to push as much of your rendering work into the platform implementation as possible. As such, the natural handling of arrays is often a key issue for OpenGL programmers.

Perhaps the most complex mechanisms in OpenGL-ctypes are those which implement the array-based operations which allow for using low-level blocks of formatted data to communicate with the OpenGL implementation. OpenGL-ctypes preferred basic array implementation is the (new) numpy reimplementation of the original Numeric Python.

The array handling functionality provided within OpenGL-ctypes is localised to the `OpenGL.arrays` sub-package. Within the package, there are two major classes, one (the `FormatHandler`) which implements an interface to a way of storing data in Python, and another (the `ArrayDatatype`) which models an OpenGL array format. The `ArrayDatatype` classes use `FormatHandler`s to manipulate array-compatible objects for use in the system.

#### ArrayDatatypes

ArrayDatatype classes provide an API composed primarily of classmethods (that is, methods which are called directly on the class, rather than requiring an instance of the class). The classmethods are used throughout OpenGL-ctypes to provide array-format-specific handling of Python arguments.

Currently we have the following array types defined:

- ArrayDatatype

- Used for generic array operations

- GLclampdArray
- GLclampfArray

- Clamped floating-point types

- GLfloatArray
- GLdoubleArray

- Un-clamped floating-point types

- GLbyteArray
- GLcharArray (GLcharARBArray)
- GLubyteArray

- Character arrays

- GLshortArray
- GLintArray
- GLsizeiArray
- GLenumArray

- Integer array types

When you are coding new OpenGL-ctypes modules, you should always use the ArrayDatatype interfaces. These interfaces allow us to code generic operations such that they dispatch to the appropriate format handlers.

#### Format Handlers

Each format handler is responsible for implementing an API that ArrayDatatypes can use to work with the Python data-format. Data-formats can support a subset of the API, they only need to support those aspects of the data-format which make sense. For instance, a write-only array data-type (such as a Python string) doesn't need to implement the `zeros` method.

At the moment we have the following Format Handlers:

- `nones.NoneHandler`

- Supports passing the None object as an array data-pointer, quite limited in its functionality
- Implemented in Python
- registered as: 'nones'

- `numpymodule.NumpyHandler`, (`numarrays.NumarrayHandler`, `numeric.NumericHandler)`

- Full data-format handlers for the three Numeric Python implementations
- numpy module is implemented in Python using a stable API
- Numeric module is implemented in Python using a bit of a hack
- Numarray currently requires a C extension and, at the moment, is not built by default
- registered as: 'numpy' and 'numeric' ('numarray')

- `numbers.NumberHandler`

- Supports passing single Python integers/floats in as array data-types
- Does not currently support use for output arguments (since Numeric arrays are currently used for single-value output arrays)
- Implemented in Python
- registered as: 'numbers'

- `strings.StringHandler`

- Supports using Python strings as data-sources, no output support
- Implemented in Python (with a bit of a hack in _strings.py)
- registered as: 'strings'

- ctypesarrays.CtypesArrayHandler, ctypespointers.CtypesPointerHandler

- Full data-format handler for ctypes arrays, input-only for pointers
- Implemented in Python
- registered as: 'ctypesarrays','ctypespointers'

- lists.ListHandler

- Backup supports for using Python lists/tuples as data-sources if there is no numpy module available
- registered as 'lists'

##### Registering new FormatHandlers

OpenGL-ctypes uses the simplistic OpenGL.plugins module which allows you to register a plugin instance which defines a class which is to be loaded to handle a given data format.

    from OpenGL.plugins import FormatHandler
    FormatHandler( 'numpy', 'OpenGL.arrays.numpymodule.NumpyHandler', ['numpy.ndarray'] )

The first parameter is just a name used to refer to the plugin. The second is the actual class to load. If there is not third parameter, then the plugin will automatically load. If there is a value, then the value is a list of module.classname values which will be matched against incoming array-parameter values.

OpenGL-ctypes delays resolving the FormatHandler set until the last possible moment (i.e. the first call is made which requires a FormatHandler). Any time before this you can use code like this to declare your application's preference for the handler to be used for creating output argument (this handler must define a `zeros(...)` method):

    from OpenGL.arrays import formathandler
    formathandler.FormatHandler.chooseOutput( 'ctypesarrays' )

Where the strings passed are those under which the handler was registered (see previous section).

There are currently no C-level extension modules in OpenGL-ctypes. However, we have (disabled) implementations for a few format handlers which are C extensions. It should be possible to rewrite each of these as pure Python code using ctypes eventually. They were written as C extensions simply because I had the code handy and I didn't want to have to re-specify the structures for every release of Python or numpy. The _strings.py module is an example of a how such a rewrite could be done. It does a test at run-time to determine the offset required to get a data-pointer from a Python string.

### Image Handling

Most of the complexity of Image handling is taken care of by the Array Handling functionality, as most image data-types are simply arrays of data in a given format. Beyond that, it is necessary to set various OpenGL parameters so that the data-format assumptions of most Python users (e.g. tightly packed image data) will be met.

The `OpenGL.images` module has the basic functions and data-tables which allow for processing image data (both input and output). Eventually we will add APIs to support registering new image-types, but for now we have to directly modify the data-tables to register a new data-type.

The `OpenGL.GL.images` module has implementations of the core OpenGL image-manipulation functions which use the OpenGL.images module. It can serve as an example of how to use the image handling mechanisms.

### Error Handling

As with previous versions of PyOpenGL, OpenGL-ctypes 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-ctypes 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).

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.

### 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`.

You can store a new value for the current context with a call to:

    def setValue( constant, value, context=None, weak=False ):
    	"""Set a stored value for the given context"""

You can retrieve the current value (which will return None if there is no currently set value) with a call to:

    def getValue( constant, context = None ):
    	"""Get a stored value for the given constant"""

Lastly, you can delete any currently set value with:

    def delValue( constant, context=None ):
    	"""Delete the specified value for the given context"""

which will return a boolean telling you whether an existing value was found.

Keep in mind that you must either explicitly clear out each stored value or explicitly clear out the stored data with a call to `OpenGL.contextdata.cleanupContext` when you destroy a rendering context.

### Extensions

The OpenGL extension mechanism is quite well developed, with most new functionality appearing as an extension before it migrates into the OpenGL core. There are hundreds of registered extensions to OpenGL, with a large fraction of the extensions simply introducing new constants or a few new simple functions.

A few of the largest extensions, such as the `GL.ARB.shader_object` or `GL.ARB.vertex_buffer_object` extensions are more involved in their effects on the system. These extensions require considerable custom code beyond that generated by the auto-generation system.

### 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.

Historically, Togl support was one of the most complex and error-prone aspects of the PyOpenGL installation procedure. Although there are lots of scripts which use Togl from PyOpenGL, we just don't have the personel to maintain it as part of the PyOpenGL project. It would likely be best if we could simple include a redistributable Togl package alongside the OpenGL-ctypes package.

## Todo

OpenGL-ctypes is still far from finished. Here is a (partial) list of tasks still needing to be completed, I'll add more here as I discover it:

- Extension development support

- We need more elegant and stable APIs for implementing OpenGL extensions, particularly ones that have far-reaching consequences, that is extensions which affect other extensions or core functionality

- Major extensions needing lots of love

- ARB vertex buffer object
- ARB imaging
- ARB vertex/fragment shaders, shader objects (though they appear to be basically functional)

- Wrapping of OpenGL 1.4, 1.5 and 2.0 support

- Mostly just mechanical stuff wrapping array-handling code, doing image-transfer operations or the like

- Documentation

- Need docstring cleanup and critique

- Re-integrate with the OpenGL, GLU and GLUT documentation set (from PyOpenGL)
- User-oriented documentation

- Testing

- Need to test with lots of PyOpenGL code to either eliminate or document incompatibilities