Calling some OpenGL functions from Python with pyglet can be a bit tricky due to fact that the functions are only thin wrappers around the C-API.
My sample program demonstrates some calls for using modern OpenGL with pyglet. The program is intentionally quite flat, without any classes. In a larger program I would manage the involved OpenGL objects with Python classes.
This is not a full tutorial, but some aspects of the sample program are explained below.
I used Pyglet because it includes an OpenGL binding and supports Python 3.
pyglet provides an object-oriented programming interface for developing games and other visually-rich applications for Windows, Mac OS X and Linux.
Call by Reference
Many OpenGL calls use a call by reference / pointer for returning values. These can be called using
The example first creates a GLuint initialized to zero and then passes a pointer to glGenBuffers.
from pyglet import gl import ctypes vertexbuffer = gl.GLuint(0) gl.glGenBuffers(1, ctypes.byref(vertexbuffer))
Structs for Vertex Attributes
Using a struct for passing vertex attributes can take advantage of the meta-information provided by ctypes. It is also easier to manage than a flat array of float values. In the example the vertex shader takes a vec2 and a vec4 as attributes.
Note that as in the example above I use the type aliases provided by pyglet.gl in order to ensure compatible types with OpenGL.
In ctypes you create an array by multiplying the type object.
class VERTEX(ctypes.Structure): _fields_ = [ ('position', gl.GLfloat * 2), ('color', gl.GLfloat * 4), ]
The structure fields are given an offset attribute which can be used for passing to glVertexAttribPointer
gl.glVertexAttribPointer(loc_position, 2, gl.GL_FLOAT, False, ctypes.sizeof(VERTEX), ctypes.c_void_p(VERTEX.position.offset)) gl.glVertexAttribPointer(loc_color, 4, gl.GL_FLOAT, False, ctypes.sizeof(VERTEX), ctypes.c_void_p(VERTEX.color.offset))
And the structure is initialized very easily; here I create an array of three vertices, each containing a 2D position and a 4D color.
data = (VERTEX * 3)(((-0.6, -0.5), (1.0, 0.0, 0.0, 1.0)), (( 0.6, -0.5), (0.0, 1.0, 0.0, 1.0)), (( 0.0, 0.5), (0.0, 0.0, 1.0, 1.0))) gl.glBufferData(gl.GL_ARRAY_BUFFER, ctypes.sizeof(data), data, gl.GL_DYNAMIC_DRAW)
OpenGL expects C-style strings, so it is easiest to use byte strings.
ctypes has string buffers which can tranlate between
bytes in Python and
char* in C.
loc_position = gl.glGetAttribLocation(program, ctypes.create_string_buffer(b'position'))
They can also be used for retrieving strings from OpenGL such as the log of a shader compilation.
length = gl.GLint(0) gl.glGetShaderiv(shader_name, gl.GL_INFO_LOG_LENGTH, ctypes.byref(length)) log_buffer = ctypes.create_string_buffer(length.value) gl.glGetShaderInfoLog(shader_name, length, None, log_buffer)
Finding out about getting the compilation log really helped me when writing my own shaders.
The code for passing the shader source to OpenGL is still somewhat messy with a ctypes cast. I would be glad if you can suggest a better alternative in the comments.
src_buffer = ctypes.create_string_buffer(shader_source) buf_pointer = ctypes.cast(ctypes.pointer(ctypes.pointer(src_buffer)), ctypes.POINTER(ctypes.POINTER(ctypes.c_char))) length = ctypes.c_int(len(shader_source) + 1) gl.glShaderSource(shader_name, 1, buf_pointer, ctypes.byref(length))