Notes on experimental pyglet.graphics API
=========================================

The graphics module provides an efficient low-level wrapper around OpenGL
functions.  The module can be used in an immediate or retained mode to draw
OpenGL primitives.  It's not intended for use by applications, but will be
used by sprite, text, image, drawing and 3D modules as they are written.

The graphics module hides the complexity of working with OpenGL via ctypes by
handling conversion of Python lists into ctypes arrays in the most efficient
way possible (this is not the most obvious or simple way).  Programmers using
this module need to have a good understanding of OpenGL but need not use
ctypes directly at all.

Immediate mode
--------------

Example of drawing a shaded triangle using the graphics API::

    from pyglet import graphics

    graphics.draw(3, GL_TRIANGLES,
        ('v2f', [10., 10., 
                 40., 10., 
                 40., 40.]),
        ('c3b', [255, 0, 0,
                 0, 255, 0,
                 0, 0, 255]))

Internally, the `draw` function constructs vertex arrays of the given formats
and copies the given list data into them before calling glDrawArrays.  Indexed
arrays can also be drawn in immediate mode:

    graphics.draw_indexed(GL_TRIANGLE_STRIP,
        [0, 1, 2, 0, 2, 3, ... ],
        ('v3f', [ ... ]),
        ('n3f', [ ... ]),

Use of this API will replace the existing ad-hoc implementation of
Texture.blit().

Retained mode
-------------

Retained mode rendering stores vertex and index data in vertex buffer objects
(or vertex arrays if the context does not support VBOs), and renders multiple
primitives in a single batch operation.  This permits the highest-performance
rendering possible with pyglet.

To use retained mode, create a Batch object::

    batch = graphics.Batch()
    
Then add any number of primitives to the batch object.  Conceptually, a batch
object is similar to a display list, except that the primitives can be
modified or removed after they have been added, and the batch object performs
better than display lists on current generation hardware.

For example, to add the shaded triangle from the previous example to the batch
object::

    batch.add(3, GL_TRIANGLES,
        ('v2f', [10., 10., 
                 40., 10., 
                 40., 40.]),
        ('c3b', [255, 0, 0,
                 0, 255, 0,
                 0, 0, 255]))

The `add` method actually returns a `Primitive` object, which can subsequently
be modified.  In fact, no initial data for the primitive needs to be given at
all.  The following is equivalent to the previous example::

    prim = batch.add(3, GL_TRIANGLES, 'v2f', 'c3b')
    prim.vertices = [10., 10.,
                     40., 10.,
                     40., 40.]
    prim.colors = [255, 0, 0,
                   0, 255, 0,
                   0, 0, 255]

The `vertices` and `colors` arrays can also be modified in-place::

    prim.vertices[0] += 1.

To draw the batch object::

    batch.draw()

No guarantee about the order of rendering is given for the primitives inside a
batch object.  They may be re-ordered for efficiency reasons.  A 2D
application would typically use one batch object for each "layer" of
rendering; a 3D application could use one batch object for all 3D objects with
the depth buffer enabled.

State changes
-------------

Primitives can also have a rendering state associated with them.  Rendering
state is represented by any object of class `AbstractState`::

    class AbstractState:
        def set(self):
            pass

        def unset(self):
            pass

Applications are free to define their own states making use of any aspect of
OpenGL.  A simple example that is likely to be used by text, image and sprite
modules is a state object that represents only a change in texture::

    class TextureState:
        def __init__(self, texture):
            self.texture = texture

        def set(self):
            glEnable(self.texture.target)
            glBindTexture(self.texture.target, self.texture.id)

        def unset(self):
            glDisable(self.texture.target)

This state can be associated with a primitive when it is added to the batch
object::

    kitten = image.load('kitten.jpg').texture
    batch.add(1, GL_QUADS, 'v2f', 't2f', state=TextureState(kitten))

The batch object will call the `set` and `unset` methods of the state objects
before and after rendering the primitive, and will group primitives having the
same state together.  

The batch object has no knowledge of the internals of the state object, and
does not attempt to minimise state changes beyond this simple grouping.
Primitives cannot change state once they have been added to the batch object,
however the state object itself is free to change in any way (this change will
obviously be propogated among all primitives sharing that state).

