About ads on Siafoo
Hide Siafoo is here to make coding less frustrating and to save you time. Learn More Join Siafoo
Note: You are viewing an old version of this article. View Latest Version

Prototyping OpenGL applications with PyOpenGL

Abstract: More of a wiki page than an article, containing notes, tips and guidelines for writing OpenGL applications with PyOpenGL.
Languages Python, NumPy

1   Introduction

If you have never encountered OpenGL, then you might not be aware of the fact that it is an excellent 3D graphics API and an absolute pleasure to work with. One caveat however is that setting up your application to use OpenGL can be... kind of painful. The struggle comes from the fact that each GUI library handles the OpenGL rendering context slightly differently and some GUIs require various tricks [1]. Also initializing the viewport can be done in several different ways and getting it right (while trying to hack some code together) can also be painful, especially if you have to recompile every time you change something.

2   PyOpenGL

PyOpenGL, as the name implies, is the Pythonic OpenGL API. If you have OpenGL experience but have never used PyOpenGL you should take the time to read the PyOpenGL for OpenGL Programmers tutorial. Although you can pretty much write C OpenGL in Python the PyOpenGL package also provides a nicer more python oriented interface for the OGL API.

2.1   Advantages of PyOpenGL

  • NumPy arrays are natively supported [2], making manipulation of large data sets easy and fast.
  • OpenGL calls are wrapped with glGetError so you automatically get exceptions when things go wrong.
  • You can copy/paste code to and from C/C++ since most of the OpenGL API is the same, or a slightly pythonified version, as the C API. This makes porting code a breeze.
  • It's Python so No need to compile which means faster changes.
  • It's Open Source so it's getting better every day.
  • Contributing is easy since the APIs calls are not very tightly coupled and once you understand the basics of how PyOpenGL and ctypes work you can easily contribute to the project by providing wrappers and pythonifications [3] for OpenGL calls.

2.2   Disadvantages of PyOpenGL

  • The PyOpenGL API consists of auto generated wrapper code with hand written pythonifications, this makes some errors harder to understand.
  • Lesser used / known APIs are not wrapped as nicely making your code ugly (see :snippet::117).
  • If you don't initialize your buffers correctly python will segfault, but that's what would happen in C/C++ so I guess it's not a disadvantage.
  • It's easier to write slow applications in PyOpenGL. The reason being that Python is obviously slower then C when running loops and calling methods, so drawing using immediate mode (glBegin/glEnd) tends to be much slower than C (in my superficial tests). However using vertex arrays and being 'smart about' your code will ensure that your performance is close to C/C++ code.

3   Tips

3.1   Use the Pythonic functions

A large portion of the PyOpenGL API has pythonic wrappers, try to use those functions instead of the ones you are used to in C, this will make your code cleaner and therefore easier to understand and debug. Look at PyOpenGL for OpenGL Programmers and the PyOpenGL doc pages to see exactly which calls are pythonic.

For example you can call glGenTexture with one argument, the number of texture IDs you want, and it will return either a single integer based texture id or a list of texture IDs. You can also call it the standard way, initializing an ID and providing it to the function.

Another example is you can call glVertexPointer the C way providing the necessary arguments or use the pythonic glVertexPointer[f|b|i|] set and only providing an array of the given type.

3.2   NumPy array data type

When creating NumPy arrays you don't normally have to specify the data type, it will automatically be inferred from the content. However on a 64bit machine NumPy seems to automatically make floats into float64s which are actually doubles. Surely you can already see the problem with this, you are telling OGL that you are giving it an array of 256 floats, but instead it gets 128 doubles... the result is something like this:

/image/37

That is supposed to be a solid cube, surrounded with a wireframe cube

Specifying the data type for a numpy array can be done using the dtype parameter to the array function:

1a = array([0.0, 0.5, 1.0], dtype=float32)

3.3   Loading Image Textures

Although there are plenty of good imaging libraries for C/C++ I doubt that any of them are as easy to use as the Python Image Library (PIL). Although PIL's commercialy licensed version has an OpenGL Image interface for the free version to load an image as a texture you can do something like:

 1import Image
2img = Image.open('some_img.png') # .jpg, .bmp, etc. also work
3img_data = numpy.array(list(earth.getdata()), numpy.int8)
4
5texture = glGenTextures(1)
6glPixelStorei(GL_UNPACK_ALIGNMENT,1)
7glBindTexture(GL_TEXTURE_2D, texture)
8glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
9glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
10glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
11glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
12glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.size[0], img.size[1], 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)

4   Hacks

Warning

The accuracy of the information below is not guaranteed. Try doing things 'the standard way' first then try the hacks below.

4.1   Zero is not NULL

Some OpenGL calls require a pointer to a data buffer, and sometimes you must call these functions with a NULL pointer. In C/C++ it is perfectly legal to do the following:

1glBindBuffer(GL_ARRAY_BUFFER, buffer)
2glVertexPointer(3, GL_FLOAT, 0, 0)

but in Python you have to do this:

1glBindBuffer(GL_ARRAY_BUFFER, buffer)
2glVertexPointer(3, GL_FLOAT, 0, None)

4.2   Generating Buffers

Sometimes you would need to generate some buffers, for example using the glGenBuffers VBO call or the old version of the glGenTextures [4] texture generation call.

In this case you must define the variable before hand and initialize it to the correct type:

1buffer = 0
2glGenTextures(1, buffer)
3
4# Sometimes you need the exact data type like above:
5from OpenGL.raw import GL
6buffer = GL.GLuint(0)
7glGenBuffers(1, buffer)

4.3   Type Errors

If you ever get strange errors about wrong types, try to manually coerce your data type into whatever type the API expects. Look in the OpenGL.raw package, there you will find all the proper OpenGL typedefs such as GLuint and GLfloat You can create a new variable with the correct type using something like this:

1from OpenGL.raw import GL as simple
2foo = simple.GLuint( 0 )

4.4   Random Exceptions

The PyOpenGL wrappers call glGetError automatically and throw exceptions when needed. However if you have defined some custom wrappers (see :snippet::117) you won't get automatic exception handling. The effect is that if one of your APIs generates an error you will get an exception the first time a proper PyOpenGL API is called. This should remind you of compiler errors from "back in the day" when the actual error is a hundred lines above the error message.

5   Conclusion

PyOpenGL is not perfect but it's nonetheless a fantastic framework for developing 3D applications, prototyping OpenGL code, or playing with shaders (or other OGL features). Vastly increasing it's RAD capabilities is the fact that PyOpenGL integrates well with the SciPy/NumPy numerical processing library allowing you to develop algorithms and process data with much less code than C or FORTRAN but with very similar performance [5] and then render the data in OpenGL.

Although the title of this article implies that all PyOpenGL is good for is prototyping, that is not true. You can write full blown applications with PyOpenGL combined with pretty much any GUI toolkit you choose.

6   Footnotes

[1]For example the wxGLCanvas object from wxWidgets should have it's erase background background event overriden with an empty body to prevent flickering. I am fairly certain this is true for the Win32 API as well.
[2]NumPy is supported in most of the API, but if you run into problems you can always use the numpy.array.ctypes.data method or the OpenGL.arrays.ArrayDatatype class in order to extract the data and manually pass it to the call.
[3]Yes, I am making up words.
[4]glGenTextures is actually pythonic, you only need to provide one argument specifying how many textures you want and either an integer id or a list of texture ids will be returned
[5]NumPy is a set of FORTRAN and C libraries with Python wrappers. If you use the library correctly and avoid Python loops (which you can in most cases) your NumPy code performs most of it's heavy computation inside the Fortran or C libraries resulting in lighting fast speed.
Owner: Stou S.
Viewable by Everyone
Editable by All Siafoo Users
Sponsored Links
About ads on Siafoo