I have a program that performs most of its work without a display/UI, but then briefly needs to display some content in a low-latency way. I'm using GLUT via PyOpenGL to display a window in a separate thread, which I then populate with content from the main thread when necessary. The problem I'm having is that the GLUT window seems to only recognize that a Redisplay request has been issued when the mouse cursor moves across the window. This seems very weird, and obviously I want the GLUT window to update as soon as it's able, not wait indefinitely for the mouse cursor to move.
I suspect this may be a platform-specific issue. I've confirmed the behavior is reproducible on Ubuntu 16.04.2 and 14.04.5.
The full code to reproduce is below (requires PyOpenGL). The desired behavior for this code is for the GLUT window to display black, then automatically switch to red a second later, then green then blue then exit, all without any mouse movement after the script is started. Instead, the GLUT window will display black at program start and it will stay black indefinitely until the mouse cursor is moved across the window (and then red until the mouse is moved again, etc). It doesn't matter if the cursor is inside or outside the window -- it must be moved, or else the onDisplay handler will not fire.
How can I make the program below run to completion regardless of what the mouse is doing?
from OpenGL.GLUT import *
from OpenGL.GLU import *
from OpenGL.GL import *
import sys
import threading
import time
hWin = None
displayevent = threading.Event()
bgcolor = (0,0,0,1)
def onDisplay():
print(" [onDisplay] Started")
glClearColor(*bgcolor)
glClear(GL_COLOR_BUFFER_BIT)
glutSwapBuffers()
print(" [onDisplay] Finished")
displayevent.set()
print(" [onDisplay] Exiting")
def runGl():
global hWin
glutInit(sys.argv)
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB)
glutInitWindowPosition(0, 0)
hWin = glutCreateWindow("GLWindow")
glutDisplayFunc(onDisplay)
print("[runGl] Entering main GLUT loop")
glutMainLoop()
print("[runGl] Exited main GLUT loop")
glThread = threading.Thread(target=runGl)
print("[main] Showing GLUT window")
glThread.start()
print("[main] Waiting for GLUT window to initialize")
time.sleep(1)
print("[main] GLUT window initialization (assumed) complete")
colors = [(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1)]
for i in range(len(colors)):
print("[main] --- Iteration %d ---" % i)
print("[main] Waiting...")
time.sleep(1)
print("[main] Trigging display update")
bgcolor = colors[i]
displayevent.clear()
glutPostRedisplay()
print("[main] Waiting for redraw to complete")
displayevent.wait()
print("[main] Redraw is complete")
print("[main] Closing window")
glutDestroyWindow(hWin)
print("[main] All done.")
You must call glutPostRedisplay()
from the same thread that GLUT was initialized on.
How can I call
glutPostRedisplay
on the thread that is fully occupied byglutMainLoop
Think about it the other way around. Use the event to tell your main loop that something changed.
def display():
glClearColor(*bgcolor)
glClear(GL_COLOR_BUFFER_BIT)
glutSwapBuffers()
def idle():
if displayevent.is_set():
glutPostRedisplay()
Using glutIdleFunc(idle)
.
This way your main loop is continuesly running and checking if displayevent
is set. Then in your loop instead of displayevent.wait()
you call displayevent.set()
.
You could of course have an additional event which you set after calling glutSwapBuffers()
. Then you could wait on that event.
This is similarly how you would do asynchronized loading. You have a secondary thread load and process the image data. Then when it's done you flag it as such and the main / rendering thread calls glTexImage2D()
.