Skip to main content
3 of 4
deleted 61 characters in body

Python GLFW and PyOpenGL: How to update a VBO from a thread while rendering at the same time?

I'm really new to this community. I'm sorry for any mistakes in advance.I'm making a game like minecraft with GLFW and OpenGL. The problem is, it just renders two faces correctly and the other faces have a wierd glitch. Here is my code:

main.py

# imports
import glfw
from OpenGL.GL import *
from OpenGL.GLU import *

# internal imports
from core.renderer import *
from player import *

if not glfw.init():
    raise Exception("glfw can not be initialized!")

window = glfw.create_window(800, 500, "PyCraft", None, None)
glfw.make_context_current(window)
renderer = TerrainRenderer(window)
player = Player(window)

renderer.texture_manager.add_from_folder("assets/textures/block/")
renderer.texture_manager.save("atlas.png")
renderer.texture_manager.bind()

glEnable(GL_DEPTH_TEST)
glEnable(GL_CULL_FACE)
glCullFace(GL_BACK)
glEnable(GL_FOG)
glFogfv(GL_FOG_COLOR, (GLfloat * int(8))(0.5, 0.69, 1.0, 10))
glHint(GL_FOG_HINT, GL_DONT_CARE)
glFogi(GL_FOG_MODE, GL_LINEAR)
glFogf(GL_FOG_START, 30)
glFogf(GL_FOG_END, 100)

# get window size
def get_window_size():
    width, height = glfw.get_window_size(window)
    return width, height

def _setup_3d():
    w, h = get_window_size()

    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(70, w / h, 0.1, 1000)
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()

def _update_3d():
    _setup_3d()
    glViewport(0, 0, *get_window_size())

def add_cube(x, y, z):
    X, Y, Z = x + 1, y + 1, z + 1
    renderer.add((x, Y, Z,  X, Y, Z,  X, Y, z,  x, Y, z), renderer.texture_manager.get_texture("grass"))
    renderer.add((x, y, z, X, y, z, X, y, Z, x, y, Z), renderer.texture_manager.get_texture("grass"))
    renderer.add((x, y, z,  x, y, Z,  x, Y, Z,  x, Y, z), renderer.texture_manager.get_texture("grass"))
    renderer.add((X, y, Z,  X, y, z,  X, Y, z,  X, Y, Z), renderer.texture_manager.get_texture("grass"))
    renderer.add((x, y, Z,  X, y, Z,  X, Y, Z,  x, Y, Z), renderer.texture_manager.get_texture("grass"))
    renderer.add((X, y, z,  x, y, z,  x, Y, z,  X, Y, z), renderer.texture_manager.get_texture("grass"))

add_cube(0, 0, -2)

# mainloop
while not glfw.window_should_close(window):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    _update_3d()
    glClearColor(0.5, 0.7, 1, 1.0)

    player.update()
    renderer.render()

    glfw.poll_events()
    glfw.swap_buffers(window)

glfw.terminate()

renderer.py:

# imports
import glfw
from OpenGL.GL import *
from ctypes import *
from core.texture_manager import *
import threading
import numpy as np

glfw.init()

class TerrainRenderer:
    def __init__(self, window):
        self.event = threading.Event()
        self.to_add = []
        self._len = 0

        self.parent = window

        self.vertices = []
        self.texCoords = []

        self.create_vbo(window)

        self.texture_manager = TextureAtlas()

        glEnable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glEnableClientState(GL_TEXTURE_COORD_ARRAY)
        glEnableClientState (GL_VERTEX_ARRAY)


    def shared_context(self, window):
        glfw.window_hint(glfw.VISIBLE, glfw.FALSE)
        window2 = glfw.create_window(500,500, "Window 2", None, window)
        glfw.make_context_current(window2)
        self.event.set()

        while not glfw.window_should_close(window):
            if len(self.to_add) > 0:
                i = self.to_add.pop(0)

                bytes_vertices = np.array(i[0]).nbytes
                bytes_texCoords = np.array(i[1]).nbytes

                glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
                glBufferSubData(GL_ARRAY_BUFFER, self._len, bytes_vertices, (c_float * len(i[0]))(*i[0]))
                glVertexPointer (3, GL_FLOAT, 0, None)
                glFlush()
                
                glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
                glBufferSubData(GL_ARRAY_BUFFER, self._len, bytes_texCoords, (c_float * len(i[1]))(*i[1]))
                glTexCoordPointer(2, GL_FLOAT, 0, None)
                glFlush()

                glVertexPointer(3, GL_FLOAT, 0, None)
                glTexCoordPointer(3, GL_FLOAT, 0, None)

                self.vertices += i[0]
                self.texCoords += i[1]
                
                self._len += bytes_vertices

            glfw.poll_events()
            glfw.swap_buffers(window2)
        glfw.terminate()

    def create_vbo(self, window):
        self.vbo, self.vbo_1 = glGenBuffers (2)
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
        glBufferData(GL_ARRAY_BUFFER, 64000000, None, GL_STATIC_DRAW)
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
        glBufferData(GL_ARRAY_BUFFER, 64000000, None, GL_STATIC_DRAW)

        glfw.make_context_current(None)
        thread = threading.Thread(target=self.shared_context, args=[window], daemon=True)
        thread.start()
        self.event.wait()
        glfw.make_context_current(window)

    def add(self, vertices, texCoords):
        self.to_add.append((tuple(vertices), tuple(texCoords)))

    def render(self):
        glClear (GL_COLOR_BUFFER_BIT)

        glEnable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glBindBuffer (GL_ARRAY_BUFFER, self.vbo)
        glVertexPointer (3, GL_FLOAT, 0, None)
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
        glTexCoordPointer(2, GL_FLOAT, 0, None)

        glDrawArrays (GL_QUADS, 0, self._len)
        glDisable(GL_TEXTURE_2D)
        glDisable(GL_BLEND)

Output

It is expected that all faces of the above cube are lime.

Right now, it shows no errors, but it does not render the cube properly. The attached GIF explains what I mean.

When I use this code in renderer.py, it works just fine!

# imports
import glfw, numpy
from OpenGL.GL import *
from ctypes import *
from core.texture_manager import *

glfw.init()

class VBOManager:
    def __init__(self, renderer):
        self.renderer = renderer
        self.run()
    
    def run(self):
        for i in self.renderer.to_add[:self.renderer.to_add_count]:
            self.renderer.vertices.extend(i[0])
            self.renderer.texCoords.extend(i[1])

            self.renderer.to_add.remove(i)

        glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo)
        glBufferData(GL_ARRAY_BUFFER, len(self.renderer.vertices) * 4, (c_float * len(self.renderer.vertices))(*self.renderer.vertices), GL_STATIC_DRAW)
        glFlush()
        glVertexPointer(3, GL_FLOAT, 0, None)
        glTexCoordPointer(3, GL_FLOAT, 0, None)
        glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo_1)
        glBufferData(GL_ARRAY_BUFFER, len(self.renderer.texCoords) * 4, (c_float * len(self.renderer.texCoords))(*self.renderer.texCoords), GL_STATIC_DRAW)
        glFlush()

class TerrainRenderer:
    def __init__(self, window):
        self.window = window

        self.vertices = []
        self.texCoords = []

        self.to_add = []
        self.to_add_count = 256

        self.vbo, self.vbo_1 = glGenBuffers (2)
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
        glBufferData(GL_ARRAY_BUFFER, 12 * 4, None, GL_STATIC_DRAW)
        self.vbo_manager = VBOManager(self)

        self.texture_manager = TextureAtlas()

        glEnable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glEnableClientState(GL_TEXTURE_COORD_ARRAY)
        glEnableClientState (GL_VERTEX_ARRAY)

    def render(self):
        try:
            self.vbo_manager.run()
        except RuntimeError:
            pass

        glClear (GL_COLOR_BUFFER_BIT)

        glEnable(GL_TEXTURE_2D)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
        glBindBuffer (GL_ARRAY_BUFFER, self.vbo)
        glVertexPointer (3, GL_FLOAT, 0, None)
        glBindBuffer(GL_ARRAY_BUFFER, self.vbo_1)
        glTexCoordPointer(2, GL_FLOAT, 0, None)

        glDrawArrays (GL_QUADS, 0, len(self.vertices))
        glDisable(GL_TEXTURE_2D)
        glDisable(GL_BLEND)

    def add(self, posList, texCoords):
        self.to_add.append((numpy.array(posList), numpy.array(texCoords)))

    def update_vbo(self):
        pass

Why does this code work and not the previous one? Have I missed something?

Any help will be highly appreciated.