Search code examples
pythonpython-3.xopenglglutopengl-3

running into roadblock with opengl function glBeginTransformFeedback


So I'm having problem with the opengl function glBeginTransformFeedback When I uncomment the lines, an error is occurring. It says 1282 invalid operation though that's quite vague.

My question is what should be done with the source code to uncomment these three lines:

#glBeginTransformFeedback(GL_POINTS)
#glDrawArrays(GL_POINTS, 0, POINTS_TOTAL)
#glEndTransformFeedback()

A broader question is any suggestions on how to get it to render correctly?

Update: With the help of Rabbid76 excellent code the program is now working!!! Thank you very much!

Success!!!! Current output same as expected output: current output

supporting files: springmass_support.zip

source code:

#!/usr/bin/python3

import sys
import time
import ctypes

fullscreen = True

sys.path.append("./shared")

from sbmloader import SBMObject    # location of sbm file format loader

from sbmath import m3dDegToRad, m3dRadToDeg, m3dTranslateMatrix44, m3dRotationMatrix44, m3dMultiply, m3dOrtho, m3dPerspective, rotation_matrix, translate, m3dScaleMatrix44, \
    scale, m3dLookAt, normalize

try:
    from OpenGL.GLUT import *
    from OpenGL.GL import *
    from OpenGL.GLU import *
    from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, glBindVertexArray
except:
    print ('''
    ERROR: PyOpenGL not installed properly.
        ''')
    sys.exit()

from array import array
from enum import Enum

import numpy as np 

import glm


from math import cos, sin 
identityMatrix = [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1]

render_program = GLuint(0)

myobject = SBMObject()

POSITION_A = 0
POSITION_B = 1
VELOCITY_A = 2
VELOCITY_B = 3
CONNECTION = 4

POINTS_X            = 50
POINTS_Y            = 50
POINTS_TOTAL        = POINTS_X * POINTS_Y
CONNECTIONS_TOTAL = (POINTS_X - 1) * POINTS_Y + (POINTS_Y - 1) * POINTS_X


m_vao = [GLuint(0) for _ in range(2)]
m_vbo = [GLuint(0) for _ in range(5)]

m_index_buffer = GLuint(0)
m_pos_tbo = [GLuint(0), GLuint(0)]
m_update_program = GLuint(0)
m_render_program = GLuint(0)
m_C_loc = GLuint(0)
m_iteration_index = 0

draw_points = True
draw_lines = True
iterations_per_frame = 16


def shader_load(filename, shader_type):

    result = GLuint(0)

    with open ( filename, "rb") as data:

        result = glCreateShader(shader_type)

        glShaderSource(result, data.read() )

    glCompileShader(result)

    return result


def load_shaders():
    global m_update_program
    global m_render_program

    vs = GLuint(0)
    fs = GLuint(0)
    buffer = ''

    vs = shader_load("update.vs.glsl", GL_VERTEX_SHADER)

    if (m_update_program):
        glDeleteProgram(m_update_program)

    m_update_program = glCreateProgram()
    glAttachShader(m_update_program, vs)


    # static const char * tf_varyings[] = 
    # {
        # "tf_position_mass",
        # "tf_velocity"
    # }


    # tricky to convert string array to string pointer

    tf_varyings = ["tf_position_mass", "tf_velocity"]

    # Prepare ctypes data containing the list tf_varyings of strings
    array_type = ctypes.c_char_p * len(tf_varyings)
    buff = array_type()
    for i, e in enumerate(tf_varyings):
        buff[i] = e.encode()
                                                                            #       ctypes.c_char
    tf_varyings_chrpp = ctypes.cast(ctypes.pointer(buff), ctypes.POINTER(ctypes.POINTER(GLchar)))


    glTransformFeedbackVaryings(m_update_program, 2, tf_varyings_chrpp, GL_SEPARATE_ATTRIBS)

    glLinkProgram(m_update_program)

    glGetShaderInfoLog(vs)
    glGetProgramInfoLog(m_update_program)

    glDeleteShader(vs)

    vs = shader_load("render.vs.glsl", GL_VERTEX_SHADER)
    fs = shader_load("render.fs.glsl", GL_FRAGMENT_SHADER)

    if (m_render_program):
        glDeleteProgram(m_render_program)
    m_render_program = glCreateProgram()
    glAttachShader(m_render_program, vs)
    glAttachShader(m_render_program, fs)

    glLinkProgram(m_render_program)



class Scene:

    def __init__(self, width, height):
        global m_vao
        global m_vbo

        i = 0
        j = 0

        load_shaders()


        initial_positions = [glm.vec4() for _ in range(POINTS_TOTAL)]
        initial_velocities = [glm.vec3() for _ in range(POINTS_TOTAL)]
        connection_vectors = [glm.ivec3() for _ in range(POINTS_TOTAL)]


        n=0
        for j in range(0, POINTS_Y):
            fj = float(j) / float(POINTS_Y)

            for i in range(0, POINTS_X):

                fi = float(i) / float(POINTS_X)

                initial_positions[n] = glm.vec4((fi - 0.5) * float(POINTS_X), (fj - 0.5) * float(POINTS_Y), 0.6 * sin(fi) * cos(fj), 1.0)
                initial_velocities[n] = glm.vec3(0.0)
                connection_vectors[n] = glm.ivec4(-1)

                if (j != (POINTS_Y - 1)):

                    if (i != 0):
                        connection_vectors[n][0] = n - 1

                    if (j != 0):
                        connection_vectors[n][1] = n - POINTS_X

                    if (i != (POINTS_X - 1)):
                        connection_vectors[n][2] = n + 1

                    if (j != (POINTS_Y - 1)):
                        connection_vectors[n][3] = n + POINTS_X
                n+=1


        for i in range(0, 2):
            glGenVertexArrays(1, m_vao[i])

        for i in range(0, 5):
            glGenBuffers(i+1, m_vbo[i])

        for i in range(0, 2):

            glBindVertexArray(m_vao[i])

            glBindBuffer(GL_ARRAY_BUFFER, m_vbo[POSITION_A + i])


            # POSITION_A
            glBindBuffer(GL_ARRAY_BUFFER, m_vbo[POSITION_A + i])

            ar_position = np.empty([POINTS_TOTAL, 4], dtype='float32')
            for j, e in enumerate(initial_positions):
                ar_position[j] = e

            glBufferData(GL_ARRAY_BUFFER, POINTS_TOTAL * glm.sizeof(glm.vec4()), ar_position, GL_DYNAMIC_COPY)
            glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)
            glEnableVertexAttribArray(0)

            # VELOCITY_A
            glBindBuffer(GL_ARRAY_BUFFER, m_vbo[VELOCITY_A + i])

            ar_velocities = np.empty([POINTS_TOTAL, 3], dtype='float32')
            for j, e in enumerate(initial_velocities):
                ar_velocities[j] = e

            glBufferData(GL_ARRAY_BUFFER, POINTS_TOTAL * glm.sizeof(glm.vec3()), ar_velocities, GL_DYNAMIC_COPY)
            glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, None)
            glEnableVertexAttribArray(1)

            # CONNECTION
            glBindBuffer(GL_ARRAY_BUFFER, m_vbo[CONNECTION])

            ar_connection = np.empty([POINTS_TOTAL, 4], dtype='uint32')
            for j, e in enumerate(connection_vectors):
                ar_connection[j] = e

            glBufferData(GL_ARRAY_BUFFER, POINTS_TOTAL * glm.sizeof(glm.ivec4()), ar_connection, GL_STATIC_DRAW)
            glVertexAttribIPointer(2, 4, GL_INT, 0, None)
            glEnableVertexAttribArray(2)

        glGenTextures(2, m_pos_tbo)
        glBindTexture(GL_TEXTURE_BUFFER, m_pos_tbo[0])
        glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, m_vbo[POSITION_A])
        glBindTexture(GL_TEXTURE_BUFFER, m_pos_tbo[1])
        glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, m_vbo[POSITION_B])

        lines = (POINTS_X - 1) * POINTS_Y + (POINTS_Y - 1) * POINTS_X

        glGenBuffers(1, m_index_buffer)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_index_buffer)
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, lines * 2 * ctypes.sizeof(ctypes.c_int), None, GL_STATIC_DRAW)

        e = glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, 0, lines * 2 * ctypes.sizeof(ctypes.c_int), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT)

        int_array = (ctypes.c_int * (4 * lines * 2)).from_address(e) 
        n = 0
        for j in range(0, POINTS_Y):
            for i in range(0, POINTS_X - 1):
                int_array[n] = i + j * POINTS_X
                n+=1

                int_array[n] = 1 + i + j * POINTS_X
                n+=1

        for i in range(0, POINTS_X):

            for j in range(0, POINTS_Y - 1):
                int_array[n] = i + j * POINTS_X
                n+=1

                int_array[n] = POINTS_X + i + j * POINTS_X
                n+=1


        glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER)


    def display(self):
        global m_iteration_index

        glUseProgram(m_update_program)

        glEnable(GL_RASTERIZER_DISCARD)


        for i in range( iterations_per_frame, 0, -1):

            glBindVertexArray(m_vao[m_iteration_index & 1])
            glBindTexture(GL_TEXTURE_BUFFER, m_pos_tbo[m_iteration_index & 1])
            m_iteration_index +=1
            glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_vbo[POSITION_A + (m_iteration_index & 1)])
            glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1, m_vbo[VELOCITY_A + (m_iteration_index & 1)])

            glBeginTransformFeedback(GL_POINTS)
            glDrawArrays(GL_POINTS, 0, POINTS_TOTAL)
            glEndTransformFeedback()

        glDisable(GL_RASTERIZER_DISCARD)

        black = [ 0.0, 0.0, 0.0, 0.0 ]

        glViewport(0, 0, self.width, self.height)
        glClearBufferfv(GL_COLOR, 0, black)

        glUseProgram(m_render_program)

        if (draw_points):
            glPointSize(4.0)
            glDrawArrays(GL_POINTS, 0, POINTS_TOTAL)

        if (draw_lines):
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_index_buffer)
            glDrawElements(GL_LINES, CONNECTIONS_TOTAL * 2, GL_UNSIGNED_INT, None)

        glutSwapBuffers()

    def reshape(self, width, height):
        self.width = width
        self.height = height

    def keyboard(self, key, x, y ):
        global fullscreen

        print ('key:' , key)
        if key == b'\x1b': # ESC
            sys.exit()

        elif key == b'f' or key == b'F': #fullscreen toggle

            if (fullscreen == True):
                glutReshapeWindow(512, 512)
                glutPositionWindow(int((1360/2)-(512/2)), int((768/2)-(512/2)))
                fullscreen = False
            else:
                glutFullScreen()
                fullscreen = True

        print('done')

    def init(self):
        pass

    def timer(self, blah):

        glutPostRedisplay()
        glutTimerFunc( int(1/60), self.timer, 0)
        time.sleep(1/60.0)


if __name__ == '__main__':
    start = time.time()

    glutInit()


    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH)

    glutInitWindowSize(512, 512)

    w1 = glutCreateWindow('OpenGL SuperBible - Spring-Mass Simulator')
    glutInitWindowPosition(int((1360/2)-(512/2)), int((768/2)-(512/2)))

    fullscreen = False
    many_cubes = False
    #glutFullScreen()

    scene = Scene(512,512)
    glutReshapeFunc(scene.reshape)
    glutDisplayFunc(scene.display)
    glutKeyboardFunc(scene.keyboard)

    glutIdleFunc(scene.display)
    #glutTimerFunc( int(1/60), scene.timer, 0)

    scene.init()

    glutMainLoop()

I ported this program to python from chapter 7 of Superbible OpenGL 7ed.

ported from: springmass.cpp


Solution

  • sys.getsizeof() doesn't return the size of a buffer meanged by an object, it returns the size of the object.

    If you wan to get the size of a the buffer manged by an ctypes object, then you've to us ctypes.sizeof. e.g:

    glBufferData(GL_ELEMENT_ARRAY_BUFFER, lines * 2 * sys.getsizeof(int), None, GL_STATIC_DRAW)
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, lines * 2 * ctypes.sizeof(ctypes.c_int), None, GL_STATIC_DRAW)

    PyGLM alos provides a glm.sizeof() function. e.g.:

    glBufferData(GL_ARRAY_BUFFER, POINTS_TOTAL * sys.getsizeof(glm.vec4()), ar, GL_DYNAMIC_COPY)
    glBufferData(GL_ARRAY_BUFFER, POINTS_TOTAL * glm.sizeof(glm.vec4), ar, GL_DYNAMIC_COPY)

    NumPy provides the .itmesize property. e.g.:

    np.dtype('float32').itemsize
    

    A list of object can be generated by one line of code:

    initial_positions = [glm.vec4() for _ in range(POINTS_TOTAL)]
    initial_velocities = [glm.vec3() for _ in range(POINTS_TOTAL)]
    connection_vectors = [glm.ivec3() for _ in range(POINTS_TOTAL)]
    

    To generate a array form the list you can create an empty array by numpy.empty.
    Create a 2 dimensional arrays with the shape ([POINTS_TOTAL, 4]) respectively ([POINTS_TOTAL, 3]). The glm vector objects can be assigned in a loop.
    Note, the array type for the integral attribute has to be 'uint32' rather than float32:

    # POSITION_A
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo[POSITION_A + i])
    
    ar = np.empty([POINTS_TOTAL, 4], dtype='float32')
    for j, e in enumerate(initial_positions):
        ar[j] = e
    
    glBufferData(GL_ARRAY_BUFFER, POINTS_TOTAL * glm.sizeof(glm.vec4), ar, GL_DYNAMIC_COPY)
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)
    glEnableVertexAttribArray(0)
    
    # VELOCITY_A
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo[VELOCITY_A + i])
    
    ar = np.empty([POINTS_TOTAL, 3], dtype='float32')
    for j, e in enumerate(initial_velocities):
        ar[j] = e
    
    glBufferData(GL_ARRAY_BUFFER, POINTS_TOTAL * glm.sizeof(glm.vec3), ar, GL_DYNAMIC_COPY)
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, None)
    glEnableVertexAttribArray(1)
    
    # CONNECTION
    glBindBuffer(GL_ARRAY_BUFFER, m_vbo[CONNECTION])
    
    ar = np.empty([POINTS_TOTAL, 4], dtype='uint32')
    for j, e in enumerate(connection_vectors):
        ar[j] = e
    
    glBufferData(GL_ARRAY_BUFFER, POINTS_TOTAL * glm.sizeof(glm.ivec4), ar, GL_STATIC_DRAW)
    glVertexAttribIPointer(2, 4, GL_INT, 0, None)
    glEnableVertexAttribArray(2)
    

    The instrucction

    int_array = ((ctypes.c_int * 4) * lines * 2).from_address(e) 
    

    would generate a 3 dimensional array. A 2 dimensional array can be generated by using the *- operator once, withe the size of all the elements (e.g. (ctypes.c_int * (4 * lines * 2))):

    int_array = (ctypes.c_int * (4 * lines * 2)).from_address(e) 
    n = 0
    for j in range(0, POINTS_Y):
        for i in range(0, POINTS_X - 1):
            int_array[n] = i + j * POINTS_X
            n+=1
    
            int_array[n] = 1 + i + j * POINTS_X
            n+=1
    
    for i in range(0, POINTS_X):
    
        for j in range(0, POINTS_Y - 1):
            int_array[n] = i + j * POINTS_X
            n+=1
    
            int_array[n] = POINTS_X + i + j * POINTS_X
            n+=1
    

    Further there is an issue when you generate the vertex array objects and buffer objects.

    Create list for the objects:

    m_vao = [GLuint(0) for _ in range(2)]
    m_vbo = [GLuint(0) for _ in range(5)]
    

    Create 1 single object in the for loops:

    for i in range(0, 5):
        glGenBuffers(1, m_vbo[i])
    
    for i in range(0, 2):
        glGenVertexArrays(1, m_vao[i])
    

    Finally glBeginTransformFeedback can be used:

    for i in range( iterations_per_frame, 0, -1):
    
        glBindVertexArray(m_vao[m_iteration_index & 1])
        glBindTexture(GL_TEXTURE_BUFFER, m_pos_tbo[m_iteration_index & 1])
        m_iteration_index +=1
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, m_vbo[POSITION_A + (m_iteration_index & 1)])
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1, m_vbo[VELOCITY_A + (m_iteration_index & 1)])
    
        glBeginTransformFeedback(GL_POINTS)
        glDrawArrays(GL_POINTS, 0, POINTS_TOTAL)
        glEndTransformFeedback()
    

    I changed iterations_per_frame = 1 and I skipped glutIdleFunc, but activated glutIdleFunc

    class Scene:
    
       # [...]
    
       def timer(self, blah):
    
            glutPostRedisplay()
            glutTimerFunc( 50, self.timer, 0)
    
    if __name__ == '__main__':
    
        # [...]
    
        #glutIdleFunc(scene.display)
        glutTimerFunc( 1000, scene.timer, 0) 
    

    And got the following result: