pythonopenglpyopengl

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


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

Solution

  • 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