首页 > 解决方案 > __del__ 函数中 PyOpenGL.glDeleteBuffers 的奇怪行为?

问题描述

我发现了一些del我不理解的行为,希望你能给我一些见解。我正在尝试使用 PyOpenGL 和 glfw 实现 OpenGL 的 hello_triangle。关闭 OpenGL 窗口后,我的程序应该进行清理,但glDeleteBuffers会引发 TypeError,但仅当它在函数内部调用时__del__

class Scene:
    def __init__ (self):
        # ...
        self.buffer = glGenBuffers(1)
        # ...
    def __del__ (self):
        # ...
        glDeleteBuffers(1, [self.buffer]) # TypeError: ('No array-type handler for type builtins.type (value: [1]) registered', <OpenGL.converters.CallFuncPyConverter object at ...>)
        # ...

# ...
scene = Scene()
while not glfwWindowShouldClose(window):
    scene.render()
    glfwSwapBuffers(window)
    glfwPollEvents()

del scene

相反,如果我这样实现它

class Scene:
    def __init__ (self):
        # ...
        self.buffer = glGenBuffers(1)
        # ...
    def delete (self): # Renamed __del__ to delete
        # ...
        glDeleteBuffers(1, [self.buffer]) # No error
        # ...

# ...
scene = Scene()
while not glfwWindowShouldClose(window):
    scene.render()
    glfwSwapBuffers(window)
    glfwPollEvents()

scene.delete() # Swapped del scene for scene.delete()

glDeleteBuffers突然工作并且没有抛出任何错误。为什么是这样?如果您想自己尝试,这里是完整的代码:

import ctypes
import sys

# OpenGL + GLFW
import glfw
from glfw.GLFW import *
from OpenGL.GL import *


glfw.ERROR_REPORTING = False # Catch errors by return values

class obj: pass # Object to assign arbitrary properties to



def main (args):
    # Initialize GLFW + create window
    if glfwInit() == GL_TRUE:
        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4)
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5)
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)

        window = glfwCreateWindow(800, 600, "Title", None, None)
        if window:
            glfwMakeContextCurrent(window)

            window_size_callback(window, 800, 600)
            glfwSetWindowSizeCallback(window, window_size_callback)

            # Render stuff
            scene = Scene()

            while not glfwWindowShouldClose(window):
                scene.render()
                glfwSwapBuffers(window)

                glfwPollEvents()

            # Clean up
            del scene # TypeError
            # scene.delete() # no TypeError

        else:
            print("Failed to create GLFW window!")

        glfwTerminate()
    else:
        print("Failed to initialize GLFW!")



def window_size_callback (window, width, height):
    glViewport(0, 0, width, height)



class Scene:
    _instances = []

    class vertex (ctypes.Structure):
        _fields_ = [
            ("x", GLfloat),
            ("y", GLfloat)
        ]

    def __static_init__ ():
        # Create rendering pipeline program
        vertex_shader = glCreateShader(GL_VERTEX_SHADER)
        glShaderSource(vertex_shader, """#version 450 core
        in vec4 pos;

        void main () {
            gl_Position = vec4(pos.xy, 0.0, 1.0);
        }""")
        glCompileShader(vertex_shader)

        fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)
        glShaderSource(fragment_shader, """#version 450 core
        out vec4 frag_color;

        void main () {
            frag_color = vec4(1.0, 1.0, 1.0, 1.0);
        }""")
        glCompileShader(fragment_shader)

        Scene._program = glCreateProgram()
        glAttachShader(Scene._program, vertex_shader)
        glAttachShader(Scene._program, fragment_shader)
        glLinkProgram(Scene._program)

        glDeleteShader(vertex_shader)
        glDeleteShader(fragment_shader)

        # Create VAO
        Scene._vertex_array = glGenVertexArrays(1)
        glBindVertexArray(Scene._vertex_array)

        Scene._attrib_pos = glGetAttribLocation(Scene._program, "pos")
        glVertexAttribFormat(Scene._attrib_pos, 2, GL_FLOAT, GL_FALSE, 0)
        glEnableVertexAttribArray(Scene._attrib_pos)
        glVertexAttribBinding(Scene._attrib_pos, Scene._attrib_pos)

    def __static_del__ ():
        glDeleteVertexArrays(1, [Scene._vertex_array]) # Alsa raises a TypeError, if glDeleteBuffers' error is catched before
        glDeleteProgram(Scene._program)

    def __init__ (self):
        if len(Scene._instances) == 0:
            Scene.__static_init__()

        Scene._instances.append(self)

        # Create VBO
        vertex_buffer_data = (Scene.vertex * 3)(
            Scene.vertex(-0.5, 0.5),
            Scene.vertex(0.5, 0.5),
            Scene.vertex(0.5, -0.5)
        )

        self._vertex_buffer = obj()
        self._vertex_buffer.buffer = glGenBuffers(1)
        self._vertex_buffer.length = len(vertex_buffer_data)
        self._vertex_buffer.offset = Scene.vertex.x.offset
        self._vertex_buffer.stride = ctypes.sizeof(Scene.vertex)

        glBindBuffer(GL_ARRAY_BUFFER, self._vertex_buffer.buffer)
        glBufferData(GL_ARRAY_BUFFER, vertex_buffer_data, GL_STATIC_DRAW)

    def __del__ (self): # Rename to delete
        glDeleteBuffers(1, [self._vertex_buffer.buffer]) # TypeError, if executed in __del__(), but not when executeed in delete()

        Scene._instances.remove(self)
        if len(Scene._instances) == 0:
            Scene.__static_del__()

    def render (self):
        glClearColor(0.0, 0.1, 0.2, 1.0)
        glClear(GL_COLOR_BUFFER_BIT)

        # Draw
        glUseProgram(Scene._program)
        glBindVertexArray(Scene._vertex_array)

        glBindVertexBuffer(Scene._attrib_pos, self._vertex_buffer.buffer, self._vertex_buffer.offset, self._vertex_buffer.stride)

        glDrawArrays(GL_TRIANGLES, 0, self._vertex_buffer.length)



if __name__ == "__main__":
    main(sys.argv[1:])

标签: pythonglfwpyopengldel

解决方案


glDeleteBuffers引发 TypeError,但仅当它在函数内部调用时__del__

该错误是由于在调用析构函数之前破坏了 OpenGL 上下文而引起的。
对于任何其他 OpenGL 指令,需要glDeleteBuffers有效且当前的 OpenGL 上下文。

如果

scene.delete()

被调用,thendelete()和 by thatglDeleteBuffers被立即调用。此时,OpenGL 上下文是当前的,并且操作无论如何都会成功。

但是当你这样做时

del scene

那么不能保证立即调用析构函数。

请参阅Python-3.3.1。数据模型 - 基本定制

注意delx 不直接调用x.__del__()——前者将 x 的引用计数减一,而后者仅在 x 的引用计数达到零时调用。

何时调用析构函数取决于垃圾回收。Python没有提供任何保证,关于何时调用析构函数,它会在所有引用都被删除之后发生,所以它可能没有必要在之后发生。

这会导致在 OpenGL 竞赛被销毁(之后glfwTerminate())并且操作失败之后调用析构函数。

一个安全的方法是直接调用析构函数:

例如

Scene.__del__(scene)

推荐阅读