为什么在使用 GTKGLArea 时在 PyOpenGL 中未定义 glGenVertexArrays

Why is glGenVertexArrays undefined in PyOpenGL when using GTKGLArea

我在下面有下面的测试代码,我试图让一个基本的 opengl Gtk3 GLArea 示例工作。

下面的错误目前是我的症结所在,从我读到的内容来看,这可能意味着这些功能在上下文中不可用,但阅读有关 GLArea 的信息时,它似乎不允许您选择上下文,听起来应该是默认的到正确的那个。

不幸的是,问题可能出在 GLArea 或 PyOpenGL 上,目前我可以处理的所有示例都在 C 中,非常适合在 python.

中获得一个基本示例

无论如何花了很多时间试图解决这个问题,所以如果有人能帮助解决至少这个错误,那就太好了。

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

例如,它也是一个要点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()

下面是完整的工作示例,在@derhass 的评论之后我做了一些搜索并发现Gdk.Screen为什么我以前发现的示例中没有使用它我不知道。

这 3 行缺少的拼图

screen = Gdk.Screen.get_default()
visual = Gdk.Screen.get_rgba_visual(screen)
self.window = Gtk.Window()
Gtk.Widget.set_visual(self.window, visual)

完整的工作示例,应该在 window 中显示基本三角形,如下所示。

#!/usr/bin/python
# noqa: E402
import gi 
gi.require_version('Gtk', '3.0')
import numpy as np
from gi.repository import Gtk, Gdk
from OpenGL.GLU import *
from OpenGL import GL as GL

from OpenGL.GL import shaders
from OpenGL.raw.GL.ARB.vertex_array_object import glGenVertexArrays, \
                                                  glBindVertexArray

# from numpy import array

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):
        screen = Gdk.Screen.get_default()
        visual = Gdk.Screen.get_rgba_visual(screen)

        print('is composite %s' % Gdk.Screen.is_composited(screen))

        self.window = Gtk.Window()
        Gtk.Widget.set_visual(self.window, visual)
        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()

    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()
        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)

        # Create a new Vertex Array Object
        self.vertex_array_object = GL.glGenVertexArrays(1)
        GL.glBindVertexArray(self.vertex_array_object )

        # Generate a new array buffers for our vertices
        self.vertex_buffer = GL.glGenBuffers(1)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vertex_buffer)

        # Get position variable form the shader and store
        self.position = GL.glGetAttribLocation(self.shader, 'position')
        GL.glEnableVertexAttribArray(self.position)

        # describe the data layout
        GL.glVertexAttribPointer(self.position, 4, GL.GL_FLOAT, False, 0, ctypes.c_void_p(0))

        # Copy data to the buffer
        GL.glBufferData(GL.GL_ARRAY_BUFFER, 48, self.vertices, GL.GL_STATIC_DRAW)

        # Unbind buffers once done
        GL.glBindVertexArray( 0 )
        GL.glDisableVertexAttribArray(self.position)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, 0)

        return True

    def on_draw(self, widget, *args):
        print('render event')
        print(widget.get_error())

        # clear screen and select shader for drawing
        GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
        GL.glUseProgram(self.shader)

        # bind and draw vertices
        GL.glBindVertexArray(self.vertex_array_object)
        GL.glDrawArrays(GL.GL_TRIANGLES, 0, 3)
        GL.glBindVertexArray(0)

        GL.glUseProgram(0)
        GL.glFlush()
        return True

application = application_gui()
Gtk.main()

对我来说,问题似乎与 Wayland 下的 运行 有关。似乎如果 PyOpenGL 在 Wayland 下 运行 并且有一个 X11 服务器 运行,它将使用 GLX 而不是 Wayland 的 EGL 支持。关于 GtkGLArea 如何设置 GL 上下文的一些事情意味着 GLX 可以工作但没有任何 GL 扩展,其中包括 VAO。这似乎是 PyOpenGL 中的错误。

有两种方法可以解决此问题:

  1. 设置PYOPENGL_PLATFORM强制PyOpenGL使用EGL而不是GLX。

    例如在导入 OpenGL 之前:

    if 'WAYLAND_DISPLAY' in os.environ and 'PYOPENGL_PLATFORM' not in os.environ:
        os.environ['PYOPENGL_PLATFORM'] = 'egl'
    
  2. 取消设置 WAYLAND_DISPLAY 以强制 Gtk 使用 GLX 而不是 EGL。

    例如在导入 Gtk 之前:

    if 'WAYLAND_DISPLAY' in os.environ:
        del os.environ['WAYLAND_DISPLAY']
    

运行 在使用 PyOpenGL 和 glfw 时出现此错误,修复是 glfw.make_context_current(window)