How to build a self compiling file that draws a triangle?

92 Views Asked by At

The code sample below gives the error beneath it. Any ideas?

import os
if 'WAYLAND_DISPLAY' in os.environ and 'PYOPENGL_PLATFORM' not in os.environ:
    os.environ['PYOPENGL_PLATFORM'] = 'egl'
import pygame as pg

from OpenGL.GL import *

import numpy as np
import ctypes 
from OpenGL.GL.shaders import compileProgram, compileShader
from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays


class App:
    def __init__(self):
        pg.init()
        pg.display.set_mode((640,480), pg.OPENGL|pg.DOUBLEBUF)
        self.clock = pg.time.Clock()
        glClearColor(0.1,0.2,0.2,1)
        self.shader = self.createShader("shaders/vertex.txt", "shaders/fragment.txt")
        glUseProgram(self.shader)
        self.triangle = Triangle()
        
        glBindVertexArray(self.triangle.vao)
        self.mainLoop()

    def createShader(self, vertexFilepath, fragmentFilepath):

        with open(vertexFilepath, 'r') as f:
            vertex_src = f.readlines()

        with open(fragmentFilepath, 'r') as f:
            fragment_src = f.readlines()
        
        shader = compileProgram(
            compileShader(vertex_src, GL_VERTEX_SHADER),
            compileShader(fragment_src, GL_FRAGMENT_SHADER) 
        )

        return shader
    
    def mainLoop(self): 
        running = True
        while (running):
            for event in pg.event.get():
                if (event.type == pg.QUIT):
                    running = False

                glClear(GL_COLOR_BUFFER_BIT)
                glUseProgram(self.shader)
                glBindVertexArray(self.triangle.vao)
                glDrawArrays(GL_TRIANGLES,0, self.triangle.vertex_count)
                pg.display.flip()
                self.clock.tick(60)
            self.quit()

    def quit (self):
        self.triangle.destroy()
        glDeleteProgram(self.shader)
        pg.quit() 

class Triangle:
    def __init__(self):
        self.vertices = (
           -0.5, -0.5, 0.0, 1.0, 0.0, 0.0,
            0.5, -0.5, 0.0, 0.0, 1.0, 0.0,
            0.0,  0.5, 0.0, 0.0, 0.0, 1.0
        )
        self.vao = [0]
        self.vertices = np.array(self.vertices, dtype = np.float32)
        self.vertex_count = 3
        self.vao = glGenVertexArrays(1, self.vao)
        glBindVertexArray(self.vao)
        self.vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
        glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW)  
        glEnableVertexAttribArray(0)
        glVertexAttribPointer(0,3,GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))
        glEnableVertexAttribArray(1)
        glVertexAttribPointer(1,3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))

    
    def destroy(self):

        glDeleteVertexArrays(1,(self.vao,))
        glDeleteBuffers(1, (self.vbo,))

if __name__ == "__main__":
    MyAPP = App()

I now get the following error.

PS C:\Users\ludov\OneDrive\Bureau\PYOPENGL> & "C:/Program Files/Python311/python.exe" c:/Users/ludov/OneDrive/Bureau/lgopengl.py
pygame 2.5.2 (SDL 2.28.3, Python 3.11.0)
Hello from the pygame community. https://www.pygame.org/contribute.html
Traceback (most recent call last):
  File "c:\Users\ludov\OneDrive\Bureau\lgopengl.py", line 98, in <module>
    MyAPP = App()
            ^^^^^
  File "c:\Users\ludov\OneDrive\Bureau\lgopengl.py", line 22, in __init__
    self.triangle = Triangle()
                    ^^^^^^^^^^
  File "c:\Users\ludov\OneDrive\Bureau\lgopengl.py", line 73, in __init__
    glBindVertexArray(self.vao)
  File "C:\Users\ludov\AppData\Roaming\Python\Python311\site-packages\OpenGL\platform\baseplatform.py", line 415, in __call__
    return self( *args, **named )
           ^^^^^^^^^^^^^^^^^^^^^^
ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type
2

There are 2 best solutions below

0
Ludogiraud On BEST ANSWER

This code with the right shaders works:

import pygame as pg
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram,compileShader
import numpy as np

def create_shader(vertex_filepath: str, fragment_filepath: str) -> int:
    """
        Compile and link shader modules to make a shader program.

        Parameters:

            vertex_filepath: path to the text file storing the vertex
                            source code
            
            fragment_filepath: path to the text file storing the
                                fragment source code
        
        Returns:

            A handle to the created shader program
    """

    with open(vertex_filepath,'r') as f:
        vertex_src = f.readlines()

    with open(fragment_filepath,'r') as f:
        fragment_src = f.readlines()
    
    shader = compileProgram(compileShader(vertex_src, GL_VERTEX_SHADER),
                            compileShader(fragment_src, GL_FRAGMENT_SHADER))
    
    return shader

class App:
    """
        For now, the app will be handling everything.
        Later on we'll break it into subcomponents.
    """

    def __init__(self):
        """ Initialise the program """

        self._set_up_pygame()

        self._set_up_timer()

        self._set_up_opengl()
        
        self._create_assets()
    
    def _set_up_pygame(self) -> None:
        """
            Initialize and configure pygame.
        """

        pg.init()
        pg.display.gl_set_attribute(pg.GL_CONTEXT_MAJOR_VERSION, 3)
        pg.display.gl_set_attribute(pg.GL_CONTEXT_MINOR_VERSION, 3)
        pg.display.gl_set_attribute(pg.GL_CONTEXT_PROFILE_MASK,
                                    pg.GL_CONTEXT_PROFILE_CORE)
        pg.display.set_mode((640,480), pg.OPENGL|pg.DOUBLEBUF)

    def _set_up_timer(self) -> None:
        """
            Set up the app's timer.
        """

        self.clock = pg.time.Clock()
    
    def _set_up_opengl(self) -> None:
        """
            Configure any desired OpenGL options
        """

        glClearColor(0.1, 0.2, 0.2, 1)
    
    def _create_assets(self) -> None:
        """
            Create all of the assets needed for drawing.
        """

        self.triangle = Triangle()
        self.shader = create_shader(
            vertex_filepath = "shaders/vertex.txt", 
            fragment_filepath = "shaders/fragment.txt")
    
    def run(self) -> None:
        """ Run the app """

        running = True
        while (running):
            #check events
            for event in pg.event.get():
                if (event.type == pg.QUIT):
                    running = False
            #refresh screen
            glClear(GL_COLOR_BUFFER_BIT)

            glUseProgram(self.shader)
            self.triangle.arm_for_drawing()
            self.triangle.draw()

            pg.display.flip()

            #timing
            self.clock.tick(60)

    def quit(self) -> None:
        """ cleanup the app, run exit code """

        self.triangle.destroy()
        glDeleteProgram(self.shader)
        pg.quit()

class Triangle:
    """
        Yep, it's a triangle.
    """


    def __init__(self):
        """
            Initialize a triangle.
        """
        
        # x, y, z, r, g, b
        vertices = (
            -0.5, -0.5, 0.0, 1.0, 0.0, 0.0,
             0.5, -0.5, 0.0, 0.0, 1.0, 0.0,
             0.0,  0.5, 0.0, 0.0, 0.0, 1.0
        )
        vertices = np.array(vertices, dtype=np.float32)

        self.vertex_count = 3

        self.vao = glGenVertexArrays(1)
        glBindVertexArray(self.vao)
        self.vbo = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
        glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)

        glEnableVertexAttribArray(0)
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))
        
        glEnableVertexAttribArray(1)
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))
    
    def arm_for_drawing(self) -> None:
        """
            Arm the triangle for drawing.
        """
        glBindVertexArray(self.vao)
    
    def draw(self) -> None:
        """
            Draw the triangle.
        """

        glDrawArrays(GL_TRIANGLES, 0, self.vertex_count)

    def destroy(self) -> None:
        """
            Free any allocated memory.
        """
        
        glDeleteVertexArrays(1,(self.vao,))
        glDeleteBuffers(1,(self.vbo,))

if __name__ == "__main__":

    my_app = App()
    my_app.run()
    my_app.quit()

and draws this:

Sexy Triangle

0
Rabbid76 On

The line

self.vao = glGenVertexArrays(1, self.vao)

is wrong in any case.

See PyOpenGL - glGenVertexArrays. The VAO IDs are returned in the second argument, which is an array of IDs. In this case, the return value is None:

vaoIds = [0] # alternaively: vaoIds = (GLuint * 1)(0)
glGenVertexArrays(1, vaoIds)
self.vao = vaoIds[0]

PyOpenGL has overloaded the function. In this case, the function has only 1 argument, namely the number of IDs. The return value is 1 single ID or an array with more than 1 IDs corresponding to the argument of the function:

self.vao = glGenVertexArrays(1)

However, you do not use the function glGenVertexArrays from the specification, but the one from the extension GL_ARB_vertex_array_object. For the extension API function the overloading is not implemented. If you do not have completely outdated hardware, you do not need the extension at all. So I suggest to just delete the following line and use the default implementation and not the extension:

from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays