TEGL
----

Tegl is Python script for generating OpenGL extenstions.  I wrote it
because I was disappointed with some other OpenGL solutions for
Python.

Tegl generates a C extension that can be compiled into module to make
OpenGL available to Python programs.  (You're on your own compiling
it, though.)

Unlike PyOpenGL, the C extension is a thin wrapper around OpenGL.  It
performs only minor argument checking.  Nevertheless, it is mostly
compatible with PyOpenGL, and depending on how the module is
generated, it should only need minor changes to migrate from PyOpenGL.
(In fact, I was able to migrate a recent application simply by adding
a couple wrapper functions that mimicked PyOpenGL semantics.)

This is version 002.


USAGE
-----

To use, simply run maketegl.py.  It will generate two files, tegl.c
and teglu.c in the current directory, which you can compile it to
create Python GL and GLU modules.  The modules comprise (most of)
OpenGL 1.1.

For more control, open maketegl.py with a text editor.  At the top are
some options for controlling the generated code.  You can change the
names of the modules, and even combine them into one.  You can control
whether the "gl" and "GL_" prefixes appear on the exported symbols.

You can choose to include or exclude various OpenGL extensions, or use
more recent versions of OpenGL.  I recommend plain OpenGL 1.1 for
maximum portability, and selected ARB extensions if you need more.

There are some other switches I will cover below.

If you really want fine-tuned control, you can scroll past the options
and comment out any functions and constants you don't want.  This may
be a good idea since currently Tegl modules are quite large when
compiled.



USING THE MODULE
----------------

Calling OpenGL functions is simple.  Just import the module with "from
tegl import *" (or just "import tegl as gl" if you have built it
without prefixes, or whatever else suits you).

Most OpenGL functions can be called just as in C:

    glBegin(GL_COMPILE)        or   gl.Begin(gl.COMPILE)
    glVertex3d(1.0,2.0,3.0)    or   gl.Vertex3d(1.0,2.0,3.0)
    glEnable(GL_CULL_FACE)
    glRotated(1.0,90.0,0.0,0.0)
    texon = glIsEnabled(GL_TEXTURE_2D)

It's usually pretty straightforward to use OpenGL functions that
accept data through a pointer: you just pass it a list (or tuple, or
numpy array, or any sequence).

    glVertex3dv([1.0,2.0,3.0])
    glMaterialfv(GL_FRONT,GL_AMBIENT,[0.2,0.2,0.2,1.0])

OpenGL functions that return data through a pointer are somewhat
trickier, you have to pass it an empty sequence that is large enough
to contain the data the function returns.  The sequence type has to be
mutable, of course.

    retval = [None]*4
    glGetMaterialfv(GL_FRONT,GL_AMBIENT,retval)

(This is a departure from how PyOpenGL operates; it will simply create
an array and return it.  But Tegl is intended to be a thin wrapper, so
it follows the C usage closely.)

In many cases, it's preferrable to use a buffer object instead of a
Python sequence to pass data to the function; for instance, texture
data.  In these cases, you would pass in an object that supports
buffer protocol, such as a string, a numpy array, or an array from the
array module.

    imagedata = Image.open(imgfile).tostring()
    glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,256,256,0,GL_RGBA,
                 GL_UNSIGNED_BYTE,imagedata)

To use an OpenGL function that returns image data, you need to pass in
a object that supports writable buffer protocol.  The buffer must be
large enough to hold the output of the function, otherwise Bad Things
can happen.  Strings are not mutable so you'll have to use some other
type.  Arrays from the array module can be your friend here.

    retval = array.array("B",[0])*640*480*4
    glReadPixels(0,0,640,480,GL_RGBA,GL_UNSIGNED_BYTE,retval)


The question is, how do you know whether you should pass a buffer type
or a sequence type to OpenGL?  (Currently, you can only pass one or
the other; this may change in a future version.)  Here is the general
rule of thumb:

You need to use a buffer if:
  * The type of the argument in C is GLvoid* (or just void*)
  * OR the size of the data is theoretically unbounded.

You need to use a sequence if:
  * The type if te argument is not GLvoid*
  * AND the size of the data is fixed or bounded.

So glMaterialfv takes a sequence argument because it has a type
(GLfloat*) and has a size of 4 at most (it's bounded).  On the other
hand, glTexImage2D uses a buffer argument because the type of the data
is GLvoid*, and the image can be arbitrarily large.

This rule of thumb does not always hold, however.  I decided to go
against it where it seemed useful.

Several functions for modifying textures (glGenTextures,
glDeleteTextures, glPrioritizeTextures, and glAreTexturesResident)
accept arrays of unbounded size, but I felt that sequence types were
far more useful here, so I defined a constant (MAX_TEXTURE_ARRAY_SIZE)
that lets the user specify a bound on the size.  (Otherwise I would
have had to muck around with malloc, and I did not want to do that.)
Functions for creating and destroying buffer object, occlusion
queries, and shader programs (ARB only) are treated likewise.

glCallList accepts buffer objects.  Even though the size of the call
list is unbounded, it seemed as if a sequence would be more
appropriate.  However, a common use case of glCallList (in conjunction
with glListBase) is to pass it a string, where each character is the
name of a display list that draws that character.  Thus I left is as a
buffer.

If I chose wrongly for your application, I apologize.  Numpy arrays
and arrays from the array module implement both the sequence and
buffer protocols; these may be of assistence to you.  (Be careful
about numpy strides, though.)

If worse comes to worst, you can change the definition inside the
maketegl.py script to suit you.


ADVANCED: THE GIL
-----------------

It's possible that if you send a lot of data to the OpenGL pipeline
quickly enough, the OpenGL pipeline could fill up, an an OpenGL call
in progress would block until the pipeline clears up.  This is
possible with functions like glCallList, which runs a display list
that could be enormous.

In threaded programs, the time spent waiting for an OpenGL block could
be used for other tasks.  In Python, only a single thread can run
Python code at a given time (a thread has to have the GIL, global
interpreter lock, to run Python code).  However, extensions may
release the GIL while not manipulating Python objects (which is the
case during an OpenGL call).

Therefore, there is an option in Tegl for releasing the GIL around
certain OpenGL calls, thus allowing other Python code to run if it
should happen to block.  Simply add the name of the function (without
the "gl") to the set of functions to release the GIL for at the top of
maketegl.py.


UNSUPPORTED FUNCTIONS
---------------------

There are a few OpenGL functions that aren't supported by Tegl simply
because they have an unusual signature that the machinery doesn't
easily support.  In this early release, I didn't bother.  Here's the
list:

glGetString
glGetPointerv
glMultiDrawArrays(ARB)
glMultiDrawElements(ARB)
glGetBufferPointerv(ARB)
glGetVertexAttribPointerv(ARB)
glShaderSource(ARB)
All glu* functions that implement nurbs, tesselators, and quadrics.


Additionally, a couple functions are supported with limited
functionality:

glMapBuffer(ARB) returns a CObject.  This is not too useful from
Python; however, you can get at it from a C extension.  Also, ctypes
and numpy wizards should be able to map a numpy array to the pointer
location.

glShaderSource(ARB) is handcoded, and only supports passing it a
single string.  (You must still pass a singleton sequence of strings;
this is to preserve upward compatibility.)  For instance, you'd call
it like this:
    glShaderSourceARB(shaderobj,1,[source],[len(source)])



IMPROVEMENTS
------------

The two main improvements I have in mind (aside from fixing up bugs
and adding stuff) are these:

* To take advantage of the parallelism of OpenGL to make the tegl
  module a lot smaller.  Many OpenGL function have the same signature
  (same argument and return types) and I can save a lot of code by
  generating one function with that signature, and calling it with a
  function pointer (and maybe a size index or two).

* To support sequences and buffers for the same argument at the same
  time.  This isn't as straightforward as it looks, though, since for
  objects that support both protocols, the results could be different
  depending on whether it's treated as a buffer or a sequence.  I have
  to figure out what the most reasonable behavior is.


BUGS
----

Yes.


CHANGES FROM 001
----------------

Added #define GL_EXT_PROTOTYPES.  Since Tegl depends on the C compiler
having prototypes available for typecasting arguments, this is pretty
important.

Added handcoded version of glShaderSource(ARB) that only accepts a
single string in the source.

Fixed specification of gluUnProject4.


LICENCE AND COPYRIGHT
---------------------

maketegl.py and maketegl.txt are copyrighted by Carl Banks.

These files, which comprise the tegl package, are released under a BSD
style license.  See the file copying for details.


WHY NOT PYOPENGL
----------------

The standard OpenGL package in Python, PyOpenGL, has some limitations
(mostly speedwise) that made it undesirable for me.

Probably the biggest bottleneck is that PyOpenGL calls glGetError
after every call.  I understand the rationale for this, but its cost
is too high for my needs.  Another problem is, as far as I know, it
does not allow the user to release the GIL around some GL calls.
Thirdly, PyOpenGL 3 is based on ctypes.  There's nothing wrong with
ctypes, but for a usage where speed really does matter, it's much too
slow.  PyOpenGL 2 is a C extension, and is much faster than PyOpenGL
3, but doesn't support recent OpenGL extensions.

A minor thing I didn't like was that PyOpenGL didn't get rid of the gl
prefixes.  I prefer namespaces, and would prefer to simply import a gl
module and call functions this way: "gl.End()".  But PyOpenGL did not
support that; I had to write wrappers to pull the symbols apart.

But the most irritating thing about PyOpenGL is the setuptools entry
points, which mean that you cannot simply import the OpenGL modules;
you have to go through setuptools.  This, among other things, makes
distributing programs using PyOpenGL (such as with py2exe) a pain.
