pythonopenglpygamevbopyopengl

How to add to opengl pygame VBOs from another process


The problem

I'm just trying to make a game like minecraft, but I just can't add to a vbo from another process. The strange thing is that the logs appear two times and the window just closes instantly.

The code

import pygame, multiprocessing
from OpenGL.GL import *
from ctypes import *

pygame.init ()
screen = pygame.display.set_mode ((800,600), pygame.OPENGL|pygame.DOUBLEBUF, 24)
glViewport (0, 0, 800, 600)
glClearColor (0.0, 0.5, 0.5, 1.0)
glEnableClientState (GL_VERTEX_ARRAY)

vbo = glGenBuffers (1)
glBindBuffer (GL_ARRAY_BUFFER, vbo)

def triangle():
    vertices = [ 0.0, 1.0, 0.0,  0.0, 0.0, 0.0,  1.0, 1.0, 0.0 ]
    glBufferData (GL_ARRAY_BUFFER, len(vertices)*4, (c_float*len(vertices))(*vertices), GL_STATIC_DRAW)

if __name__ == '__main__':
    multiprocessing.Process(target=triangle).start()

    running = True
    while running:

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        glClear (GL_COLOR_BUFFER_BIT)

        glBindBuffer (GL_ARRAY_BUFFER, vbo)
        glVertexPointer (3, GL_FLOAT, 0, None)

        glDrawArrays (GL_TRIANGLES, 0, 3)

        pygame.display.flip ()

Logs

pygame 2.1.0 (SDL 2.0.16, Python 3.8.6)
Hello from the pygame community. https://www.pygame.org/contribute.html
Unable to load numpy_formathandler accelerator from OpenGL_accelerate
pygame 2.1.0 (SDL 2.0.16, Python 3.8.6)
Hello from the pygame community. https://www.pygame.org/contribute.html
Unable to load numpy_formathandler accelerator from OpenGL_accelerate

Expected output

expected output

UPDATE

I changed the code to:

import pygame, threading
from pyglet.gl import *
from OpenGL.GL import *
from OpenGL.WGL import *
from ctypes import *

pygame.init ()
screen = pygame.display.set_mode ((800,600), pygame.OPENGL|pygame.DOUBLEBUF, 24)
glViewport (0, 0, 800, 600)
glClearColor (0.0, 0.5, 0.5, 1.0)
glEnableClientState (GL_VERTEX_ARRAY)

vbo = glGenBuffers (1)
glBindBuffer (GL_ARRAY_BUFFER, vbo)

hwnd = pygame.display.get_wm_info()['window']
context = wglGetCurrentContext()

def triangle(window_handle, context):
    context2 = wglCreateContext(window_handle)
    wglMakeCurrent(window_handle, context2)

    vbo2 = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, vbo2)
    glBufferData(GL_ARRAY_BUFFER, 12, (GLfloat * 12)(0.0, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0), GL_STATIC_DRAW)
    glVertexPointer(3, GL_FLOAT, 0, None)
    glFlush()

    wglMakeCurrent(None, None)
    wglDeleteContext(context2)

if __name__ == '__main__':
    threading.Thread(target=triangle, args=[hwnd, context]).start()

    running = True
    while running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False

        glClear (GL_COLOR_BUFFER_BIT)

        glBindBuffer (GL_ARRAY_BUFFER, vbo)
        glVertexPointer (3, GL_FLOAT, 0, None)

        glDrawArrays (GL_TRIANGLES, 0, 3)

        pygame.display.flip ()

But now it just opens the window, and closes it again! The window just closes abruptly. It gives the errors mentioned below. But if I comment line 28 and simply write triangle(hwnd, context), everything works just fine.

Errors:

Traceback (most recent call last):
  File "C:\Users\...\AppData\Local\Programs\Python\Python38\lib\threading.py", line 932, in _bootstrap_inner
    self.run()
  File "C:\Users\...\AppData\Local\Programs\Python\Python38\lib\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "f:/StackOverflow - Code/vbo_plus_thread.py", line 27, in triangle
    vbo2 = glGenBuffers(1)
  File "src/latebind.pyx", line 39, in OpenGL_accelerate.latebind.LateBind.__call__
  File "src/wrapper.pyx", line 318, in OpenGL_accelerate.wrapper.Wrapper.__call__
  File "src/wrapper.pyx", line 311, in OpenGL_accelerate.wrapper.Wrapper.__call__
  File "src/errorchecker.pyx", line 58, in OpenGL_accelerate.errorchecker._ErrorChecker.glCheckError
OpenGL.error.GLError: GLError(
        err = 1282,
        description = b'invalid operation',
        baseOperation = glGenBuffers,
        pyArgs = (
                1,
                <object object at 0x000001E23B9B8100>,
        ),
        cArgs = (1, array([0], dtype=uint32)),
        cArguments = (1, array([0], dtype=uint32))
)

Solution

  • The OpenGL Context is thread local. If you want to use an OpenGL context in another thread, you must make it the current context there.
    The context can only be current in one thread at a time. When the context for a thread becomes current, it is exclusive to that thread and is claimed, so it is automatically not the current context for all other threads. If you want to use the same context in multiple threads, you must lock the sections that use the context to ensure exclusive access to the context. Most likely this is not what you want.
    If you want to use the buffer for drawing in one thread, but at the same time you want to change its content in another thread, you need 2 OpenGL contexts, where the first context shares the second context.

    There are some more problems with your code:

    A basic setup using GLFW looks as follows. The vertex buffer object is created on the main thread. In the 2nd thread, a hidden OpenGL window is created that shares the context of the main thread. In this Context the buffer object's data store is updated with glBufferSubData:

    from OpenGL.GL import *
    from OpenGL.GLU import *
    import glfw
    import threading
    
    if glfw.init() == glfw.FALSE:
        exit()
    
    event = threading.Event()
    def shard_context(window, vbo):
    
        glfw.window_hint(glfw.VISIBLE, glfw.FALSE)
        window2 = glfw.create_window(300, 300, "Window 2", None, window)
        glfw.make_context_current(window2)
        event.set()
    
        glBindBuffer(GL_ARRAY_BUFFER, vbo)
        glBufferSubData(GL_ARRAY_BUFFER, 0, 12*4, (GLfloat * 12)(0.0, 0.5, 0.0, -0.5, -0.5, 0.0, 0.5, -0.5, 0.0))
        glFlush()
    
    window = glfw.create_window(300, 300, "Window 1", None, None)
    glfw.make_context_current(window)
    
    vbo = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, vbo)
    glBufferData(GL_ARRAY_BUFFER, 12 * 4, None, GL_STATIC_DRAW)
    
    glfw.make_context_current(None)
    thread = threading.Thread(target=shard_context, args=[window, vbo])
    thread.start()
    event.wait()
    glfw.make_context_current(window)
    
    glEnableClientState(GL_VERTEX_ARRAY)
    glVertexPointer(3, GL_FLOAT, 0, None)
      
    while not glfw.window_should_close(window):
        
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
       
        glColor3f(1, 0, 0)
        glDrawArrays (GL_TRIANGLES, 0, 3)
    
        glfw.swap_buffers(window)
        glfw.poll_events()
    
    glfw.terminate()
    exit()