Search code examples
pythonopenglglfwpyopengl

Why is this glDrawElements call failing?


I am trying to integrate the advice from https://gamedev.stackexchange.com/questions/10727/fastest-way-to-draw-quads-in-opengl-es and How to draw with Vertex Array Objects and glDrawElements in PyOpenGL to render quads using an index buffer implemented by OpenGL.arrays.vbo.VBO, in a context provided by GLFW and using the helper class. (I am also, at least for now, relying on ctypes for raw data rather than using Numpy.)

I produced the following minimal example (if I can fix this, I should be able to fix the actual code):

import ctypes
import glfw
from OpenGL.arrays import vbo
from OpenGL.GL import *
from OpenGL.GL import shaders


def setup_window():
    glfw.init()
    glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
    glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
    glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, True)
    glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
    window = glfw.create_window(256,256,'test',None,None)
    glfw.make_context_current(window)
    glfw.set_input_mode(window, glfw.STICKY_KEYS, True)
    return window


def get_program():
    VERTEX_SHADER = shaders.compileShader("""
    #version 330
    layout(location = 0) in vec4 position;
    void main()
    {
        gl_Position = position;
    }
    """, GL_VERTEX_SHADER)
    FRAGMENT_SHADER = shaders.compileShader("""
    #version 330
    out vec4 outputColor;
    void main()
    {
        outputColor = vec4(0.0f, 1.0f, 0.0f, 1.0f);
    }
    """, GL_FRAGMENT_SHADER)
    return shaders.compileProgram(VERTEX_SHADER, FRAGMENT_SHADER)


window = setup_window()
glUseProgram(get_program())
vertices = vbo.VBO(
    (ctypes.c_float * 16)(
        -1, -1, 0, 0,
        -1, 1, 0, 0,
        1, 1, 0, 0,
        1, -1, 0, 0
    )
)
indices = vbo.VBO(
    (ctypes.c_float * 6)(
        0, 1, 2, 1, 2, 3
    ),
    target=GL_ELEMENT_ARRAY_BUFFER
)


while (
    glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS
    and not glfw.window_should_close(window)
):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    with vertices, indices:
        glVertexAttribPointer(0, 4, GL_FLOAT, False, 0, None)
        glEnableVertexAttribArray(0)
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, None)
    glfw.swap_buffers(window)
    glfw.poll_events()

The glDrawElements call fails with an entirely unhelpful "invalid operation" exception. When commented out, everything else seems to work fine (except that of course nothing is drawn).

What exactly is wrong here? I had understood that using the VBO instances as context managers (the with block) should ensure that the data is bound and thus should be available for glDrawElements to render.

I have also tried the following, to no effect:

  • using explicit .bind() calls on the VBOs rather than the with block
  • using empty data
  • changing the size argument in the glDrawElements call to every other number I could imagine being right

Solution

  • You're using a core profile OpenGL context:

    glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
    

    In a core profile context you have to use a Vertex Array Object, because the default VAO (0) is not valid.
    Either switch to a compatibility profile (glfw.OPENGL_COMPAT_PROFILE) or create a Vertex Array Object. I recommend to create an VAO.

    Anyway the buffers have to be bound after the VAO has been bound:

    indices.bind()
    vertices.bind()
    

    And the type of the indices has to be integral and the type which is specified at glDrawElements has to correspond to this type. For instance c_int16 and GL_UNSIGNED_SHORT:

    indices = vbo.VBO(
        (ctypes.c_int16 * 6)(
            0, 1, 2, 0, 2, 3
        ),
        target=GL_ELEMENT_ARRAY_BUFFER
    )
    

    Note the Index buffers is stated in the VAO, thus the VAO has to be bound before the ELEMENT_ARRAY_BUFFER is bound.
    glVertexAttribPointer associates, the currently bound ARRAY_BUFFER, to the specified attribute index in the currently bound VAO. THus the VAO and the Vertex Buffer Object have to be bound before.

    Example:

    window = setup_window()
    program = get_program()
    glUseProgram(program)
    
    vao = glGenVertexArrays(1)
    glBindVertexArray(vao)
    
    vertices = vbo.VBO(
        (ctypes.c_float * 16)(
            -1, -1, 0, 1,
            -1, 1, 0, 1,
            1, 1, 0, 1,
            1, -1, 0, 1
        )
    )
    indices = vbo.VBO(
        (ctypes.c_int16 * 6)(
            0, 1, 2, 0, 2, 3
        ),
        target=GL_ELEMENT_ARRAY_BUFFER
    )
    
    indices.bind()
    vertices.bind()
    glVertexAttribPointer(0, 4, GL_FLOAT, False, 0, None)
    glEnableVertexAttribArray(0)
    
    while (
        glfw.get_key(window, glfw.KEY_ESCAPE) != glfw.PRESS
        and not glfw.window_should_close(window)
    ):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, None)
        glfw.swap_buffers(window)
        glfw.poll_events()