1

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
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153

2 Answers2

2

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()
Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • That resolves the exception, but I still have no output (like the other StackOverflow poster initially complained about). I expect the window to be filled with green, since the triangles cover the entire coordinate space and the colour is specified in the fragment shader. – Karl Knechtel May 26 '20 at 19:54
  • Any other ideas, then? I copied and pasted below the original `setup_window` and `get_program` definitions and still just have a black window. And I was successfully drawing stuff with other approaches (not involving an index buffer) before. :/ – Karl Knechtel May 26 '20 at 19:58
  • I tried the other approach of just changing `glfw.OPENGL_CORE_PROFILE` to `glfw.OPENGL_COMPAT_PROFILE` and that *also* doesn't give me any rendered output. – Karl Knechtel May 26 '20 at 20:01
  • @KarlKnechtel The indices have to be 0, 1, 2, 0, 2, 3 – Rabbid76 May 26 '20 at 20:26
  • 1
    AHA! That's it (setting W coords to 1) - and I had a problem like the before, actually. In the actual code, I will be using the ZW coords for texture and have them set properly by the shader. And I had the correct index values and type for the index array the first time, but messed up when I was producing the SSCCE. Egg on my face! Thanks for all your help. – Karl Knechtel May 26 '20 at 20:26
2

To summarize the results from the discussion, and my testing:

  • The VBO class works fine; the with block works fine.

  • A VAO must be created to work in the OPENGL_CORE_PROFILE. I had been confused as to how to make VAOs work with index buffers, but it turns out there isn't particularly anything to it - just set up the VAO and the rest of the code is the same. It can be as simple as vao = glGenVertexArrays(1); glBindVertexArray(vao). This needs to happen before any function that needs the VAO; in this code, that essentially means "before the with block is entered". But of course, I also want it outside the while loop, since there is no point in re-creating it constantly (and I would also have to clean it up).

  • I had messed up in several places in producing the data for the SSCCE. The index buffer needs to have data of ctypes.c_int16 type (which it had originally); the actual indices in the buffer were wrong (again, I had them correct originally, following the gamedev.stackexchange example); and the W coordinates of the vertices need to be 1.0 rather than 0.0 (in my actual code base, the Z and W components are filled in by the shader, since I'm trying to do exclusively 2D rendering, and the vertex data will use those values for texture coordinates).

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153