HugeA7Xfan
HugeA7Xfan

Reputation: 11

Despite calling display.flip, pygame display does not appear to update

I have tried to replicate something similar to https://www.falstad.com/ripple/, using PyGame, PyOpenGL and GLSL. I have checked that all the framebuffers, textures and shaders initialise properly as suggested online but now I don't know where else to look for the problem. The window opens, displays a black screen, and the collision detection appears to work, however it is useless if nothing gets displayed.

Code Below:

import pygame
from pygame.locals import *
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL.shaders import compileShader
import item_draggable
from conversion import *
from gl_classes import *
from source import Source
from medium import Medium
import math
import numpy as np


class App:
    def __init__(self, title):
        self.running = True  # This is the main loop flag
        self.displaySurface = None  # This is the display surface for PyGame
        self.size = self.width, self.height = 1080, 1080  # This is the window size
        self.windowOffset = (
            40  # This is the offset of the window from the edge of the screen
        )
        self.caption = title  # This is the window title
        self.dragItems = []  # This is a list of all draggable items
        self.sources = []  # This is a list of all sources
        self.walls = []  # This is a list of all walls
        self.selected = (
            None  # This is the index of the item selected by the user using the mouse
        )
        self.offset = [
            0,
            0,
        ]  # This is the offset of the mouse from the top left corner of the selected item
        self.clock = pygame.time.Clock()  # This is the PyGame clock
        self.startTime = 0  # This is the time the simulation started (can change in order to prevent issues when items are added or moved)
        self.wavelength = 10  # This is the simulation wavelength
        self.waveTexture = None  # This is the OpenGL texture for the wave simulation
        self.damping = 1.0  # This is the damping factor for the simulation

        self.shaderProgramMain = None
        self.shaderProgramStatic = None
        self.shaderProgramProgressive = None
        self.shaderProgramDraw = None
        self.shaderProgramDrawLine = None

        self.vertexpPositionBuffer = None
        self.vertexTextureCoordBuffer = None
        self.sourceBuffer = None
        self.colorBuffer = None
        self.simVertexPositionBuffer = None
        self.simVertexTextureCoordBuffer = None
        self.simVertexDampingBuffer = None

        self.simPosition = []
        self.simDamping = []
        self.simTextureCoord = []

        self.progressive = False

        self.mvMatrix = np.matrix(np.identity(4), np.float32)
        self.pMatrix = np.matrix(np.identity(4), np.float32)
        self.mvMatrixStack = []

        self.renderTexture1 = None
        self.renderTexture2 = None

        self.colourScheme = [1, 1, 1, 1, 1, 1, 1, 1]

    def on_init(self):
        pygame.init()
        # Set up the display
        self.displaySurface = pygame.display.set_mode(
            self.size, pygame.DOUBLEBUF | pygame.OPENGL
        )
        # Set up the OpenGL context
        self.caption = pygame.display.set_caption(self.caption)
        gluPerspective(45, (self.width / self.height), 0.1, 50.0)
        glTranslatef(0.0, 0.0, -5)
        self.running = True

        # initialise shaders
        self.shaderProgramMain = self.create_shader(
            "shaders/display-fs.glsl", "shaders/vs.glsl"
        )
        self.shaderProgramMain.brightnessUniform = glGetUniformLocation(
            self.shaderProgramMain.get_id(), "brightness"
        )
        self.shaderProgramMain.coloursUniform = glGetUniformLocation(
            self.shaderProgramMain.get_id(), "colours"
        )

        self.shaderProgramStatic = self.create_shader(
            "shaders/simulate-stat-fs.glsl", "shaders/vs.glsl"
        )
        self.shaderProgramStatic.stepSizeXUniform = glGetUniformLocation(
            self.shaderProgramStatic.get_id(), "stepSizeX"
        )
        self.shaderProgramStatic.stepSizeYUniform = glGetUniformLocation(
            self.shaderProgramStatic.get_id(), "stepSizeY"
        )

        self.shaderProgramProgressive = self.create_shader(
            "shaders/simulate-prog-fs.glsl", "shaders/vs.glsl"
        )
        self.shaderProgramProgressive.stepSizeXUniform = glGetUniformLocation(
            self.shaderProgramProgressive.get_id(), "stepSizeX"
        )
        self.shaderProgramProgressive.stepSizeYUniform = glGetUniformLocation(
            self.shaderProgramProgressive.get_id(), "stepSizeY"
        )

        self.shaderProgramDraw = self.create_shader(
            "shaders/draw-fs.glsl", "shaders/draw-vs.glsl"
        )
        self.shaderProgramDrawLine = self.create_shader(
            "shaders/draw-line-fs.glsl", "shaders/draw-vs.glsl"
        )

        # setup buffers

        self.vertexPositionBuffer = Buffer(glGenBuffers(1))
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexPositionBuffer.id)
        glBufferData(
            GL_ARRAY_BUFFER,
            np.array([-1, +1, +1, +1, -1, -1, +1, -1], dtype=np.float32),
            GL_STATIC_DRAW,
        )
        self.vertexPositionBuffer.itemSize = 2
        self.vertexPositionBuffer.numItems = 4

        self.vertexTextureCoordBuffer = Buffer(glGenBuffers(1))
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexTextureCoordBuffer.id)
        glBufferData(
            GL_ARRAY_BUFFER,
            np.array(
                [
                    self.windowOffset / self.width,
                    1 - self.windowOffset / self.height,
                    1 - self.windowOffset / self.width,
                    1 - self.windowOffset / self.height,
                    self.windowOffset / self.width,
                    self.windowOffset / self.height,
                    1 - self.windowOffset / self.width,
                    self.windowOffset / self.height,
                ],
                dtype=np.float32,
            ),
            GL_STATIC_DRAW,
        )
        self.vertexTextureCoordBuffer.itemSize = 2
        self.vertexTextureCoordBuffer.numItems = 4

        self.sourceBuffer = Buffer(glGenBuffers(1))
        glBindBuffer(GL_ARRAY_BUFFER, self.sourceBuffer.id)
        self.sourceBuffer.itemSize = 2
        self.sourceBuffer.numItems = 2

        self.colorBuffer = Buffer(glGenBuffers(1))
        glBindBuffer(GL_ARRAY_BUFFER, self.colorBuffer.id)
        self.colorBuffer.itemSize = 4
        self.colorBuffer.numItems = 2

        # visible area
        self.setPosRect(
            self.windowOffset,
            self.windowOffset,
            self.width - self.windowOffset,
            self.height - self.windowOffset,
        )

        # sides
        self.setPosRect(
            1,
            self.windowOffset,
            self.windowOffset,
            self.height - self.windowOffset,
        )
        self.setPosRect(
            self.width - self.windowOffset,
            self.windowOffset,
            self.width - 2,
            self.height - self.windowOffset,
        )
        self.setPosRect(
            self.windowOffset,
            1,
            self.width - self.windowOffset,
            self.windowOffset,
        )
        self.setPosRect(
            self.windowOffset,
            self.height - self.windowOffset,
            self.width - self.windowOffset,
            self.height - 2,
        )

        # corners
        self.setPosRect(1, 1, self.windowOffset, self.windowOffset)
        self.setPosRect(
            self.width - self.windowOffset, 1, self.width - 2, self.windowOffset
        )
        self.setPosRect(
            1, self.height - self.windowOffset, self.windowOffset, self.height - 2
        )
        self.setPosRect(
            self.width - self.windowOffset,
            self.height - self.windowOffset,
            self.width - 2,
            self.height - 2,
        )

        self.simVertexPositionBuffer = Buffer(glGenBuffers(1))
        glBindBuffer(GL_ARRAY_BUFFER, self.simVertexPositionBuffer.id)
        glBufferData(
            GL_ARRAY_BUFFER,
            np.array(self.simPosition, dtype=np.float32),
            GL_STATIC_DRAW,
        )
        self.simVertexPositionBuffer.itemSize = 2
        self.simVertexPositionBuffer.numItems = len(self.simPosition) / 2

        self.simVertexTextureCoordBuffer = Buffer(glGenBuffers(1))
        glBindBuffer(GL_ARRAY_BUFFER, self.simVertexTextureCoordBuffer.id)
        glBufferData(
            GL_ARRAY_BUFFER,
            np.array(self.simTextureCoord, dtype=np.float32),
            GL_STATIC_DRAW,
        )
        self.simVertexTextureCoordBuffer.itemSize = 2
        self.simVertexTextureCoordBuffer.numItems = len(self.simPosition) / 2

        self.simVertexDampingBuffer = Buffer(glGenBuffers(1))
        glBindBuffer(GL_ARRAY_BUFFER, self.simVertexDampingBuffer.id)
        glBufferData(
            GL_ARRAY_BUFFER, np.array(self.simDamping, dtype=np.float32), GL_STATIC_DRAW
        )
        self.simVertexDampingBuffer.itemSize = 1
        self.simVertexDampingBuffer.numItems = len(self.simDamping)

        # setup texture framebuffers
        self.renderTexture1 = self.initTextureFramebuffer()
        self.renderTexture2 = self.initTextureFramebuffer()

    def on_event(self, event):

        if event.type == pygame.QUIT:
            self.running = False
            # exit the program

        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:
                # goes through dragItems and selects an item that collides with the mouse, if available
                for index, item in enumerate(self.dragItems):
                    if item.collidepoint(event.pos):
                        # selects item
                        self.selected = index
                        mouse_x, mouse_y = event.pos
                        self.offset[0] = item.x - mouse_x
                        self.offset[1] = item.y - mouse_y

        elif event.type == pygame.MOUSEBUTTONUP:
            # deselects item
            if event.button == 1:
                self.selected = None

        elif event.type == pygame.MOUSEMOTION:
            # moves selected item
            if (
                self.selected is not None
            ):  # selected can be `0` so `is not None` is required, which is more efficient than "!="
                self.dragItems[self.selected][0] = event.pos[0] + self.offset[0]
                self.dragItems[self.selected][1] = event.pos[1] + self.offset[1]

    def on_loop(self):
        # update the simulation at a framerate of 144 fps
        self.clock.tick(144)
        # check if items are being changed

        # simulate
        rt = self.renderTexture1
        self.renderTexture1 = self.renderTexture2
        self.renderTexture2 = rt

        rttFramebuffer = self.renderTexture1.framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, rttFramebuffer.id)

        if self.progressive:
            prog = self.shaderProgramProgressive
        else:
            prog = self.shaderProgramStatic
        glUseProgram(prog.id)
        rttFramebuffer = self.renderTexture1.framebuffer
        glViewport(0, 0, rttFramebuffer.width, rttFramebuffer.height)
        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        glBindBuffer(GL_ARRAY_BUFFER, self.simVertexPositionBuffer.get_id())
        glVertexAttribPointer(
            prog.vertexPositionAttribute,
            self.simVertexPositionBuffer.itemSize,
            GL_FLOAT,
            False,
            0,
            0,
        )

        glBindBuffer(GL_ARRAY_BUFFER, self.simVertexTextureCoordBuffer.get_id())
        glVertexAttribPointer(
            prog.textureCoordAttribute,
            self.simVertexTextureCoordBuffer.itemSize,
            GL_FLOAT,
            False,
            0,
            0,
        )

        glEnableVertexAttribArray(prog.dampingAttribute)
        glEnableVertexAttribArray(prog.vertexPositionAttribute)
        glEnableVertexAttribArray(prog.textureCoordAttribute)

        glBindBuffer(GL_ARRAY_BUFFER, self.simVertexDampingBuffer.get_id())
        glVertexAttribPointer(
            prog.dampingAttribute,
            self.simVertexDampingBuffer.itemSize,
            GL_FLOAT,
            False,
            0,
            0,
        )

        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, self.renderTexture2.get_id())
        glUniform1i(prog.samplerUniform, 0)
        glUniform1f(prog.stepSizeXUniform, 1 / self.width)
        glUniform1f(prog.stepSizeYUniform, 1 / self.height)

        self.setMatrixUniforms(prog)
        glDrawArrays(GL_TRIANGLES, 0, int(self.simVertexPositionBuffer.numItems))
        glDisableVertexAttribArray(prog.dampingAttribute)
        glDisableVertexAttribArray(prog.vertexPositionAttribute)
        glDisableVertexAttribArray(prog.textureCoordAttribute)

        srcCoords = []

        # draw sources
        for source in self.sources:
            self.prep_draw()
            f = (
                math.sin(self.clock.get_rawtime() * source.get_freq())
                * source.get_amp()
            )
            glUseProgram(self.shaderProgramDraw.get_id())
            glVertexAttrib4f(self.shaderProgramDraw.colourAttribute, f, 0, 1, 1)

            glBindBuffer(GL_ARRAY_BUFFER, self.sourceBuffer.get_id())
            srcCoords = list(derectify(source.get_pos(), self.size))
            srcCoords.append(srcCoords[0])
            srcCoords.append((srcCoords[1]) + 1)
            srcCoords = np.array(srcCoords, dtype=np.float32)
            glBufferData(GL_ARRAY_BUFFER, srcCoords, GL_STATIC_DRAW)
            glVertexAttribPointer(
                self.shaderProgramDraw.vertexPositionAttribute,
                self.sourceBuffer.itemSize,
                GL_FLOAT,
                False,
                0,
                0,
            )

            glEnableVertexAttribArray(self.shaderProgramDraw.vertexPositionAttribute)
            self.setMatrixUniforms(self.shaderProgramDraw)
            glDrawArrays(GL_LINES, 0, 4)
            glDisableVertexAttribArray(self.shaderProgramDraw.vertexPositionAttribute)
            # pygame

        # draw walls

        for wall in self.walls:
            self.prep_draw()
            glBindBuffer(GL_ARRAY_BUFFER, self.sourceBuffer.get_id())
            # draw line back on itself, or else one endpoint won't be drawn
            WallCoords = derectify(wall.get_rect(), self.size)
            glLineWidth(1.5)
            glBufferData(
                GL_ARRAY_BUFFER, np.array(WallCoords, dtype=np.float32), GL_STATIC_DRAW
            )
            glVertexAttribPointer(
                self.shaderProgramDraw.vertexPositionAttribute,
                self.sourceBuffer.itemSize,
                GL_FLOAT,
                GL_FALSE,
                0,
                None,
            )

            self.setMatrixUniforms(self.shaderProgramDraw)
            glEnableVertexAttribArray(self.shaderProgramDraw.vertexPositionAttribute)
            glDrawArrays(GL_TRIANGLE_STRIP, 0, 6)
            glDisableVertexAttribArray(self.shaderProgramDraw.vertexPositionAttribute)

            glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE)
            glBindFramebuffer(GL_FRAMEBUFFER, 0)

    def on_render(self):
        print("Starting render")
        glUseProgram(self.shaderProgramMain.get_id())
        glBindFramebuffer(GL_FRAMEBUFFER, 0)
        glViewport(0, 0, self.width, self.height)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        self.pMatrix = np.matrix(np.identity(4), np.float32)
        self.mvMatrix = np.matrix(np.identity(4), np.float32)
        self.mvMatrixStack.append(self.mvMatrix)

        # draw result
        glBindBuffer(GL_ARRAY_BUFFER, self.vertexPositionBuffer.get_id())
        glVertexAttribPointer(
            self.shaderProgramMain.vertexPositionAttribute,
            self.vertexPositionBuffer.itemSize,
            GL_FLOAT,
            False,
            0,
            0,
        )

        glBindBuffer(GL_ARRAY_BUFFER, self.vertexTextureCoordBuffer.get_id())
        glVertexAttribPointer(
            self.shaderProgramMain.textureCoordAttribute,
            self.vertexTextureCoordBuffer.itemSize,
            GL_FLOAT,
            False,
            0,
            0,
        )

        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, self.renderTexture1.get_id())
        glUniform1i(self.shaderProgramMain.samplerUniform, 0)
        glUniform1f(self.shaderProgramMain.brightnessUniform, 1)
        glUniform3fv(
            self.shaderProgramMain.coloursUniform,
            len(self.colourScheme),
            self.colourScheme,
        )

        self.setMatrixUniforms(self.shaderProgramMain)
        glEnableVertexAttribArray(self.shaderProgramMain.vertexPositionAttribute)
        glEnableVertexAttribArray(self.shaderProgramMain.textureCoordAttribute)
        glDrawArrays(GL_TRIANGLE_STRIP, 0, self.vertexPositionBuffer.numItems)
        glDisableVertexAttribArray(self.shaderProgramMain.vertexPositionAttribute)
        glDisableVertexAttribArray(self.shaderProgramMain.textureCoordAttribute)

        self.mvMatrixStack.pop()
        pygame.display.flip()
        print("Render complete")

    def on_cleanup(self):
        pygame.quit()

    def on_execute(self):
        if self.on_init() == False:
            self.running = False

        # Add sources and walls
        self.add_source([self.width / 2, self.height / 2], 1, 10, 50)
        self.add_source([(self.width / 2) + 200, (self.height / 2) + 200], 2, 10, 50)
        self.add_wall([300, 300], [10, 100], 3)

        # Run the main loop
        while self.running:
            for event in pygame.event.get():
                self.on_event(event)
            self.on_loop()
            self.on_render()

        self.on_cleanup()

    def add_drag(self, pos, size, id):
        # adds a draggable object at position [x,y], of size [width,height]
        item = item_draggable.Item(pos, size, id)
        self.dragItems.append(item.hitbox())

    def add_source(self, pos, id, frequency, amplitude):
        source = Source(pos, id, frequency, amplitude)
        self.sources.append(source)
        self.add_drag(pos, [8, 8], id)

    def add_wall(self, pos, size, id):
        wall = Medium(pos, size, id, 0)
        self.walls.append(wall)
        self.add_drag(pos, size, id)

    def prep_draw(self):
        rttFramebuffer = self.renderTexture1.framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, rttFramebuffer.get_id())
        glViewport(0, 0, rttFramebuffer.width, rttFramebuffer.height)
        glUseProgram(self.shaderProgramDraw.get_id())

        # blue channel used for walls and media
        glColorMask(False, False, True, False)
        glVertexAttrib4f(self.shaderProgramDraw.colourAttribute, 0.0, 0.0, 0.0, 1.0)

    def load_shader(self, shader_file):
        f = open(shader_file, "r")
        shader_source = f.read()
        if shader_source:
            if "fs" in shader_file:
                shader = compileShader(shader_source, GL_FRAGMENT_SHADER)
            elif "vs" in shader_file:
                shader = compileShader(shader_source, GL_VERTEX_SHADER)
            else:
                return None

            glCompileShader(shader)
            if not glGetShaderiv(shader, GL_COMPILE_STATUS):
                print(
                    f"Shader compile error in {shader_file}: {glGetShaderInfoLog(shader)}"
                )
            return shader
        else:
            print("Shader not found")
            return None
            # error handling

    def create_shader(self, fs, vs):

        try:
            frag_shader = self.load_shader(fs)
        except:
            print("Fragment shader not found")
            return None
        try:
            vert_shader = self.load_shader(vs)
        except:
            print("Vertex shader not found")
            return None

        match fs:
            case "shaders/display-fs.glsl":
                shader = ShaderMain(glCreateProgram())
            case "shaders/simulate-stat-fs.glsl":
                shader = ShaderSimulate(glCreateProgram())
            case "shaders/simulate-prog-fs.glsl":
                shader = ShaderSimulate(glCreateProgram())
            case _:
                shader = Shader(glCreateProgram())

        glAttachShader(shader.get_id(), vert_shader)
        glAttachShader(shader.get_id(), frag_shader)
        glLinkProgram(shader.get_id())
        glUseProgram(shader.get_id())

        shader.vertexPositionAttribute = glGetAttribLocation(shader.get_id(), "aVPos")
        shader.textureCoordAttribute = glGetAttribLocation(shader.get_id(), "aTexCoord")
        shader.dampingAttribute = glGetAttribLocation(shader.get_id(), "aDamping")
        shader.colourAttribute = glGetAttribLocation(shader.get_id(), "aColour")

        shader.pMatrixUniform = glGetUniformLocation(shader.get_id(), "uPMatrix")
        shader.mvMatrixUniform = glGetUniformLocation(shader.get_id(), "uMVMatrix")
        shader.samplerUniform = glGetUniformLocation(shader.get_id(), "uSampler")

        if not glGetShaderiv(frag_shader, GL_COMPILE_STATUS):
            print("Fragment shader compile error:", glGetShaderInfoLog(frag_shader))
        if not glGetShaderiv(vert_shader, GL_COMPILE_STATUS):
            print("Vertex shader compile error:", glGetShaderInfoLog(vert_shader))

        return shader

    def setPosRect(self, x1, y1, x2, y2):
        points = derectify([x2, y1, x1, y1, x2, y2, x1, y1, x2, y2, x1, y2], self.size)
        for i in range(5):
            xi = points[i * 2]
            yi = points[i * 2 + 1]
            self.simPosition.append(-1 + (2 * xi / self.width))
            self.simPosition.append(-1 + (2 * yi / self.height))
            self.simTextureCoord.append(xi / self.width)
            self.simTextureCoord.append(1 - yi / self.height)
            damp = self.damping
            if xi == 1 or yi == 1 or xi == self.width - 1 or yi == self.height - 1:
                damp = damp * 0.91
            self.simDamping.append(damp)

    def setMatrixUniforms(self, shader):
        glUniformMatrix4fv(shader.pMatrixUniform, 1, False, self.pMatrix)
        glUniformMatrix4fv(shader.mvMatrixUniform, 1, False, self.mvMatrix)

    def initTextureFramebuffer(self):
        framebuffer = np.empty(1, dtype=np.uint32)
        glCreateFramebuffers(1, framebuffer)
        fb = Framebuffer(framebuffer[0])
        glBindFramebuffer(GL_FRAMEBUFFER, fb.get_id())
        fb.set_width(self.width)
        fb.set_height(self.height)

        texture = np.empty(1, dtype=np.uint32)
        glCreateTextures(GL_TEXTURE_2D, 1, texture)
        tx = Texture(int(texture[0]), fb)
        glBindTexture(GL_TEXTURE_2D, tx.get_id())
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, tx.get_width(), tx.get_height())

        glFramebufferTexture2D(
            GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tx.get_id(), 0
        )

        status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
        if status != GL_FRAMEBUFFER_COMPLETE:
            print(f"Framebuffer error: {status}")
            return None
        else:
            print("Framebuffer initialized successfully")

        glBindTexture(GL_TEXTURE_2D, 0)
        glBindFramebuffer(GL_FRAMEBUFFER, 0)

        return tx


if __name__ == "__main__":
    WaveSim = App(title="WaveSim")
    WaveSim.on_execute()

If I've done something wrong or more info would be helpful please let me know, it's my first time using stackoverflow as the only advice I could find was to check all the openGL objects aforementioned. I suspect there is some kind of issue with my use of the textures, but I honestly don't understand where I could've made a mistake

Blank window

Upvotes: 0

Views: 22

Answers (0)

Related Questions