Archive

Tag Archives: Python

The sample Program from the previous post not only demonstrated some OpenGL calls and concepts, but also illustrates that code organization quickly becomes important as soon as the program grows beyond a couple of lines.

So I refactored some of the OpenGL Interface code into classes. I noted that for each draw call I need to setup the OpenGL context by binding some Objects like the shader program, the target framebuffer or the source texture.

I also learned that after you are done using any OpenGL object it is helpful to unbind it. The reason for this is not that they would use too much resources, but rather decoupling. It might be that your program just coincidentally has the right objects bound but as soon as you move any of the code, something will break. Explicitly unbinding helps avoid these dependencies of different parts of your program on what GL objects other parts might have bound last.

So my drawing operations started to look like this

# Bind needed objects 
framebuffer.bind()
render_program.use()
texture.bind()

gl.glDrawSomething(...)

# Unbind Objects
texture.unbind()
render_program.end_use()
framebuffer.unbind()

Just by renaming the matching method pairs .bind() and .unbind() to __enter__ and __exit__ the objects become context managers and the code above becomes much cleaner

with framebufer, render_program, texture:
    gl.glDrawSomething(...)

This style has the advantage of being able to specify the objects used for a draw call in a single with: line. On the other hand it also removes flexibility on the bind and unbind times of the objects. I think in most cases this is not a disadvantage because most times many objects depend on each other logically (e.g. use this texture with that shader together).

A refactored version of the last posts example which uses the context managers is available on github.

Advertisements

This post will show you how to set up a framebuffer in OpenGL using Python and Pyglet.

See my previous post for some tips how to use OpenGL functions from Python.

Structure of the Program

As this is a demo program I did not structure it with python classes. However the complexity is already big enough to see that this flat structure is not suitable for any larger program.

The following illustrates the drawing steps. The OpenGL objects in the grey boxes are globals
in my python program.
Flowchart for the Draw Event

There are two steps: first a triangle is rendered to a texture using a framebuffer, and second the texture is copied to the screen. Each step uses

  • A Shader Program containing the GLSL code used for rendering
  • A Vertex Buffer containing vertex data for the objects being drawn
  • A Vertex Array Object containing the information how the data
    in the buffer is linked to the vertex attributes in the shader program

The full program is available on github.

Scene description

In this example the framebuffer is used to render the triangle from the previous post in a very low resolution (30×20 px). This Image is then used for texturing two rectangles in the main window.

Note that both in the framebuffer and on the main screen I do not use any vertex transformation. Therefore only the x and y coordinates are used. The lower left corner of the screen has coordinates (-1, -1) and the upper right corner (1, 1). Texture coordinates however run from 0 to 1.

framebuffer

Vertex Array Objects

In the previous program there was only one GLSL program and only one vertex buffer. Therefore it was convenient to store the vertex attribute bindings in the default OpenGL state. In this program however we switch back and forth between the triangle rendering program which uses color attributes for the vertices and the copy to screen program which uses texture coordinates.

A vertex array object stores the information how data in a buffer is linked to vertex attributes of the shader program. So I can define this connection once (using glVertexAttribPointer) and then I only need to select the correct vertex array object when drawing.

def setup_render_vertexbuffer():
    ...
    gl.glBindVertexArray(render_vao)

    gl.glEnableVertexAttribArray(loc_position)
    gl.glEnableVertexAttribArray(loc_color)

    # the following bindings will be remembered by the vao
    gl.glBindBuffer(gl.GL_ARRAY_BUFFER, render_vertexbuffer)

    gl.glVertexAttribPointer(loc_position, 2, gl.GL_FLOAT, False, 
         ctypes.sizeof(COLOR_VERTEX), 
         ctypes.c_void_p(COLOR_VERTEX.position.offset))
    gl.glVertexAttribPointer(loc_color, 4, gl.GL_FLOAT, False, 
         ctypes.sizeof(COLOR_VERTEX), 
         ctypes.c_void_p(COLOR_VERTEX.color.offset))
    gl.glBindVertexArray(0)


def render_to_texture():
    ...
    # draw using the vertex array for vertex information
    gl.glBindVertexArray(render_vao)
    gl.glDrawArrays(gl.GL_TRIANGLES, 0, 3)
    gl.glBindVertexArray(0)

Gotcha: The Viewport

OpenGL needs to know the size of the rendering target. In this program there are two targets, so you need to tell OpenGL the new size every time you switch the drawing target.

def render_to_texture():
    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, framebuffer)
    gl.glViewport(0, 0, FB_WIDTH, FB_HEIGHT)
    ...

def copy_texture_to_screen():
    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
    gl.glViewport(0, 0, window.width, window.height)
    ...

Putting it together

The program has 300 lines, and as I mentioned before, it could benefit well from more structure and also from deduplication of code. Setting up two GLSL shader programs, two vertex array objects, two vertex buffers and two drawing steps has lead to some duplication, which could be avoided if these were all each two instances of a shader class, a vertex array class and a vertex buffer class.

def draw():
    render_to_texture()
    copy_texture_to_screen()

def main():
    global window
    window = pyglet.window.Window()

    setup_framebuffer()
    setup_render_program()
    setup_render_vertexbuffer()
    setup_copy_program()
    setup_copy_vertexbuffer()

    window.on_draw = draw
    pyglet.clock.schedule_interval(lambda dt:None, 0.01)
    pyglet.app.run()

Try out the complete program. You can get it on github.
Please comment below if something does not work or what I could explain better.

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.

triangle in a program window

This is not a full tutorial, but some aspects of the sample program are explained below.

Pyglet

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 ctypes.byref.

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)

String Arguments

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))