Skip to main content
4 of 4
Rollback to Revision 1
Vaillancourt
  • 16.4k
  • 17
  • 56
  • 61

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, I just can't render a VBO while I update it from another thread. Here is my code:

main.py

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

# internal imports
from core.renderer import *
from terrain 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)

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, 3)
glFogf(GL_FOG_END, 10)

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

world = World(renderer, player)
world.generate()

# 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_on_resize():
    _setup_3d()
    glViewport(0, 0, *get_window_size())

# mainloop
while not glfw.window_should_close(window):
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    update_on_resize()

    _setup_3d()
    glClearColor(0.5, 0.7, 1, 1.0)

    player.update()
    player._translate()

    glfw.poll_events()
    glfw.swap_buffers(window)

glfw.terminate()

renderer.py:

# 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])

            _ = i
            self.renderer.to_add.remove(i)

            glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo)
            glBufferSubData(GL_ARRAY_BUFFER, len(self.renderer.vertices), len(_[0]) * 4, (GLfloat * len(_[0]))(*_[0]))
            glFlush()
            glVertexPointer(3, GL_FLOAT, 0, None)
            glTexCoordPointer(3, GL_FLOAT, 0, None)
            glBindBuffer(GL_ARRAY_BUFFER, self.renderer.vbo_1)
            glBufferSubData(GL_ARRAY_BUFFER,  len(self.renderer.texCoords), len(_[1]) * 4, (GLfloat * len(_[1]))(*_[1]))
            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 load_assets_from(self, other_renderer):
        self.texture_manager = other_renderer.texture_manager

    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

world.py

from terrain import *
from player import *
from core.renderer import *
import threading
import random
import glfw

def execute_with_delay(func, delay):
    threading.Timer(delay, func).start()

class ThreadedChunkGenerator():
    def __init__(self, world):
        self.thread = threading.Thread(target=self.run, daemon=True)
        self.world = world
        self.event = threading.Event()
        self.event.wait()
        glfw.make_context_current(self.world.parent.window)

    def run(self,):
        glfw.window_hint(glfw.VISIBLE, glfw.FALSE)
        window2 = glfw.create_window(300, 300, "Window 2", None, self.world.parent.window)
        glfw.make_context_current(window2)
        self.event.set()

        renderer = TerrainRenderer(window2)
        renderer.load_assets_from(self.world.parent)
        while True:
            for i in range(self.world.to_generate):
                chunk = i
                renderer = self.world.parent

                chunk.generate(renderer)
                self.world.renderer = renderer

            self.world.to_generate = []

class World:
    def __init__(self, renderer, player):
        self.parent = renderer
        self.chunks = {}
        self.blocks = {}
        self.position = (0 * 16, 0 * 16)
        self.render_distance = 1
        self.infgen_threshold = 1
        self.block_types = all_blocks(renderer)
        self.to_generate = []
        self.player = player

        self.thread = ThreadedChunkGenerator(self)
        self.thread.start()
        self.event = self.thread.event
        self.event.wait()

    def block_exists(self, position):
        return position in self.blocks

    def _add_chunk(self, position):
        self.chunks[position] = Chunk(self.parent, self, position)

    def add_chunk(self, position):
        execute_with_delay(lambda: self._add_chunk(position), random.randrange(1, 2))

    def generate(self):
        for i in range(self.position[0] - self.render_distance, self.position[0] + self.render_distance + 1):
            for j in range(self.position[1] - self.render_distance, self.position[1] + self.render_distance + 1):
                if (i, j) not in self.chunks:
                    self.add_chunk((i, j))

    def update_infgen(self, position):
        player_pos = (position[0] // 16, position[2] // 16)

        if player_pos[0] - self.position[0] > self.infgen_threshold:
            self.position = (self.position[0] + 2, self.position[1])
            self.generate()
        elif player_pos[0] - self.position[0] < -self.infgen_threshold:
            self.position = (self.position[0] - 2, self.position[1])
            self.generate()
        if player_pos[1] - self.position[1] > self.infgen_threshold:
            self.position = (self.position[0], self.position[1] + 2)
            self.generate()
        elif player_pos[1] - self.position[1] < -self.infgen_threshold:
            self.position = (self.position[0], self.position[1] - 2)
            self.generate()

    def render(self):
        self.parent.render()
        self.update_infgen(self.player.pos)
                    

Right now, it shows no errors, just hangs the window before it even starts rendering. Any help will be highly appreciated.