I have the below test code below where I am trying to get a basic opengl Gtk3 GLArea example working.
The error below is currently my sticking point, from what i have read this potentially means the features are not availabale to the context but reading about GLArea it does not seem to let you pick the context and sounds like it should default to the correct one.
The issue could be with GLArea or PyOpenGL unfortunately all examples i can fine are currently in C be great to get a basic example working in python.
Anyway spent a lot of time trying to figure this issue out so would be great-full if anyone can help get past at least this error.
Traceback (most recent call last):
File "gtkglarea.py", line 91, in on_configure_event
self.vertex_array_object = glGenVertexArrays(1)
File "/usr/lib/python3/dist-packages/OpenGL/platform/baseplatform.py", line 407, in call
self.name, self.name,
OpenGL.error.NullFunctionError: Attempt to call an undefined function glGenVertexArrays, check for bool(glGenVertexArrays) before calling
Example, its also on a gist https://gist.github.com/olymk2/5b3e49ac83130e580bd9983f2e5d49c3
#!/usr/bin/python
import os
import sys
from OpenGL.GLU import *
from OpenGL import GLX
from OpenGL import GL as GL
from ctypes import *
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
from OpenGL.arrays import vbo
from OpenGL.GL import shaders
from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, \
glBindVertexArray
from numpy import array
import numpy as np
VERTEX_SHADER = """
#version 330
in vec4 position;
void main()
{
gl_Position = position;
}"""
FRAGMENT_SHADER = """
#version 330
out vec4 fragColor;
void main()
{
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
"""
class application_gui:
"""Tutorial 01 Create and destroy a window"""
# glwrap = gtkglarea()
def __init__(self):
self.window = Gtk.Window()
self.canvas = Gtk.GLArea()
self.canvas.set_required_version(3, 3)
self.test_features()
self.vertices = [
0.6, 0.6, 0.0, 1.0,
-0.6, 0.6, 0.0, 1.0,
0.0, -0.6, 0.0, 1.0]
self.vertices = np.array(self.vertices, dtype=np.float32)
self.canvas.connect('realize', self.on_configure_event)
self.canvas.connect('render', self.on_draw)
self.canvas.set_double_buffered(False)
self.window.connect('delete_event', Gtk.main_quit)
self.window.connect('destroy', lambda quit: Gtk.main_quit())
self.window.add(self.canvas)
self.window.show_all()
self.on_configure_event(self.canvas)
def test_features(self):
print('Testing features')
print('glGenVertexArrays Available %s' % bool(glGenVertexArrays))
print('Alpha Available %s' % bool(self.canvas.get_has_alpha()))
print('Depth buffer Available %s' % bool(self.canvas.get_has_depth_buffer()))
def on_configure_event(self, widget):
print('realize event')
widget.make_current()
# widget.attach_buffers()
context = widget.get_context()
print('is legacy context %s' % Gdk.GLContext.is_legacy(context))
print('configure errors')
print(widget.get_error())
vs = shaders.compileShader(VERTEX_SHADER, GL.GL_VERTEX_SHADER)
fs = shaders.compileShader(FRAGMENT_SHADER, GL.GL_FRAGMENT_SHADER)
self.shader = shaders.compileProgram(vs, fs)
self.vertex_array_object = glGenVertexArrays(1)
GL.glBindVertexArray( self.vertex_array_object )
# Generate buffers to hold our vertices
self.vertex_buffer = GL.glGenBuffers(1)
GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vertex_buffer)
# Get the position of the 'position' in parameter of our shader and bind it.
self.position = GL.glGetAttribLocation(self.shader, 'position')
GL.glEnableVertexAttribArray(self.position)
# Describe the position data layout in the buffer
GL.glVertexAttribPointer(self.position, 4, GL.GL_FLOAT, False, 0, ctypes.c_void_p(0))
# Send the data over to the buffer
GL.glBufferData(GL.GL_ARRAY_BUFFER, 48, self.vertices, GL.GL_STATIC_DRAW)
# Unbind the VAO first (Important)
GL.glBindVertexArray( 0 )
# Unbind other stuff
GL.glDisableVertexAttribArray(self.position)
GL.glBindBuffer(GL.GL_ARRAY_BUFFER, 0)
print('errors')
print(widget.get_error())
return True
def on_draw(self, widget, *args):
print('render event')
print(widget.get_error())
#Create the VBO
widget.attach_buffers()
GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
GL.glUseProgram(self.shader)
GL.glBindVertexArray( self.vertex_array_object )
GL.glDrawArrays(GL.GL_TRIANGLES, 0, 3)
GL.glBindVertexArray( 0 )
GL.glUseProgram(0)
glFlush()
return True
application = application_gui()
Gtk.main()
For me, the problem seemed to be related to running under Wayland. It seems like if PyOpenGL is running under Wayland and there's an X11 server running, it will use GLX rather than Wayland's EGL support. Something about how GtkGLArea sets up the GL context means that GLX works but doesn't have any GL extensions, which includes VAOs. This seems like a bug in PyOpenGL.
There's two ways to work around this:
Set PYOPENGL_PLATFORM
to force PyOpenGL to use EGL instead of GLX.
E.g. before importing OpenGL:
if 'WAYLAND_DISPLAY' in os.environ and 'PYOPENGL_PLATFORM' not in os.environ:
os.environ['PYOPENGL_PLATFORM'] = 'egl'
Unset WAYLAND_DISPLAY
to force Gtk to use GLX instead of EGL.
E.g. before importing Gtk:
if 'WAYLAND_DISPLAY' in os.environ:
del os.environ['WAYLAND_DISPLAY']