Simple GLSL wrapper for Pyglet

Ever wanted to use GLSL with Pyglet, but became lost in the ctypes circumlocutions required to compile and link shaders? If so, then here is the class for you – a basic, simple GLSL wrapper class, which takes the leg work out of using shaders.

This provides most of what you will need for simple GLSL programs, allowing you to compile and link shaders, bind and unbind them, and set uniforms (integer, float and matrix). It doesn’t support custom attributes, but that would be a trivial addition.

#
# Copyright Tristam Macdonald 2008.
#
# Distributed under the Boost Software License, Version 1.0
# (see http://www.boost.org/LICENSE_1_0.txt)
#

from pyglet.gl import *

class Shader:
# vert, frag and geom take arrays of source strings
# the arrays will be concattenated into one string by OpenGL
def __init__(self, vert = [], frag = [], geom = []):
# create the program handle
self.handle = glCreateProgram()
# we are not linked yet
self.linked = False

# create the vertex shader
self.createShader(vert, GL_VERTEX_SHADER)
# create the fragment shader
self.createShader(frag, GL_FRAGMENT_SHADER)
# the geometry shader will be the same, once pyglet supports the extension
# self.createShader(frag, GL_GEOMETRY_SHADER_EXT)

# attempt to link the program
self.link()

def createShader(self, strings, type):
count = len(strings)
# if we have no source code, ignore this shader
if count < 1: return # create the shader handle shader = glCreateShader(type) # convert the source strings into a ctypes pointer-to-char array, and upload them # this is deep, dark, dangerous black magick - don't try stuff like this at home! src = (c_char_p * count)(*strings) glShaderSource(shader, count, cast(pointer(src), POINTER(POINTER(c_char))), None) # compile the shader glCompileShader(shader) temp = c_int(0) # retrieve the compile status glGetShaderiv(shader, GL_COMPILE_STATUS, byref(temp)) # if compilation failed, print the log if not temp: # retrieve the log length glGetShaderiv(shader, GL_INFO_LOG_LENGTH, byref(temp)) # create a buffer for the log buffer = create_string_buffer(temp.value) # retrieve the log text glGetShaderInfoLog(shader, temp, None, buffer) # print the log to the console print buffer.value else: # all is well, so attach the shader to the program glAttachShader(self.handle, shader); def link(self): # link the program glLinkProgram(self.handle) temp = c_int(0) # retrieve the link status glGetProgramiv(self.handle, GL_LINK_STATUS, byref(temp)) # if linking failed, print the log if not temp: # retrieve the log length glGetProgramiv(self.handle, GL_INFO_LOG_LENGTH, byref(temp)) # create a buffer for the log buffer = create_string_buffer(temp.value) # retrieve the log text glGetProgramInfoLog(self.handle, temp, None, buffer) # print the log to the console print buffer.value else: # all is well, so we are linked self.linked = True def bind(self): # bind the program glUseProgram(self.handle) def unbind(self): # unbind whatever program is currently bound - not necessarily this program, # so this should probably be a class method instead glUseProgram(0) # upload a floating point uniform # this program must be currently bound def uniformf(self, name, *vals): # check there are 1-4 values if len(vals) in range(1, 5): # select the correct function { 1 : glUniform1f, 2 : glUniform2f, 3 : glUniform3f, 4 : glUniform4f # retrieve the uniform location, and set }[len(vals)](glGetUniformLocation(self.handle, name), *vals) # upload an integer uniform # this program must be currently bound def uniformi(self, name, *vals): # check there are 1-4 values if len(vals) in range(1, 5): # select the correct function { 1 : glUniform1i, 2 : glUniform2i, 3 : glUniform3i, 4 : glUniform4i # retrieve the uniform location, and set }[len(vals)](glGetUniformLocation(self.handle, name), *vals) # upload a uniform matrix # works with matrices stored as lists, # as well as euclid matrices def uniform_matrixf(self, name, mat): # obtian the uniform location loc = glGetUniformLocation(self.Handle, name) # uplaod the 4x4 floating point matrix glUniformMatrix4fv(loc, 1, False, (c_float * 16)(*mat)) [/sourcecode] Note that there isn't anything restricting this class to use with Pyglet - a simple change to the import statement should allow it to be used with any setup based on OpenGL/ctypes. I hope someone finds this useful, and I would love to hear if you use it to make anything cool/interesting.


15 comments

  1. Pingback: Conway’s Game of Life in GLSL/Pyglet « swiftcoding

  2. Yo!

    Great code! I’ve just started using python (and pyglet) and did a google to see if using shaders with python would be hard, luckily for me you made it really simple with this example code.

    Thanks for saving a python-n00b for hours of trying and failing :>

  3. There’s a missing import in the shader module.

    File “/shader.py”, line 40, in createShader
    src = (c_char_p * count)(*strings)
    NameError: global name ‘c_char_p’ is not defined

    Resolved by adding

    from ctypes import *

    Cheers,
    Adam

  4. Adam Griffiths,
    File “C:\Python33\lib\site-packages\shader.py”, line 41, in createShader
    src = (c_char_p * count)(*strings)
    TypeError: bytes or integer address expected instead of str instance

    PS I try to use python33

      • I tried to make this work under Python 3 but couldn’t get it done. There is an invalid value GLException with a standard vertex and fragment shader. Can you help me by updating your code to be compatible with Python 3? Thanks in advance!

      • I still couldn’t figure out what to change in your code to make it python 3 compatible, the following lines won’t work under python3:
        src = (c_char_p * count)(*strings)
        glShaderSource(shader, count, cast(pointer(src), POINTER(POINTER(c_char))), None)

      • I struggled a lot to find out from the exceptions that I actually solved this problem, but it arose at another part of the Shader class. The uniformi and uniformf methods are also using strings to locate the uniform variable, so these have to be also encoded to ascii.

        These lines have to be added or modified to make this useful program work under python3:
        – before line 40 add the following:
        strings = [s.encode(‘ascii’) for s in strings]
        – modify line 106 and 119 to:
        }[len(vals)](glGetUniformLocation(self.handle, name.encode(‘ascii’)), *vals)

    • Yes, but it will take a little effort. There are trivial items to fix (i.e. print statement syntax), but the more complicated part is the handling of ctypes strings in python 3 – I think you need to adapt those to the buffer type instead.

      • The changes Nexor (above) identified were sufficient to make your code work for me in python 3 (in addition to the print statements). Thanks, Nexor! And, thanks Tristam!


Leave a reply to Rael Persone Cancel reply