PyOpenGL.glDeleteBuffers 在 __del__ 函数中的奇怪行为?
Strange behavior of PyOpenGL.glDeleteBuffers in a __del__ function?
我发现了一些我不理解的 del
行为,希望您能给我一些见解。我正在尝试使用 PyOpenGL 和 glfw 实现 OpenGL 的 hello_triangle。关闭 OpenGL window 后,我的程序应该会清理,但是 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:])
glDeleteBuffers
raises a TypeError, BUT ONLY if it is called inside a __del__
function:
错误是因为在调用析构函数之前,OpenGL上下文被破坏了。
对于任何其他 OpenGL 指令,glDeleteBuffers
需要有效且当前的 OpenGL 上下文。
如果
scene.delete()
被调用,然后 delete()
并且 glDeleteBuffers
被立即调用。此时 OpenGL 上下文是当前的,操作在任何情况下都会成功。
但是当你这样做的时候
del scene
那么不能保证立即调用析构函数。
见Python- 3.3.1. Data model - Basic customization
Note del
x doesn’t directly call x.__del__()
— the former decrements the reference count for x by one, and the latter is only called when x’s reference count reaches zero.
何时调用析构函数取决于垃圾回收。 Python 不提供任何保证,关于何时调用析构函数,它发生在所有引用已被删除之后,因此可能没有必要立即发生。
这会导致在 OpenGL 竞赛被销毁后(glfwTerminate()
之后)调用析构函数并且操作失败。
一个安全的方法是直接调用析构函数:
例如
Scene.__del__(scene)
我发现了一些我不理解的 del
行为,希望您能给我一些见解。我正在尝试使用 PyOpenGL 和 glfw 实现 OpenGL 的 hello_triangle。关闭 OpenGL window 后,我的程序应该会清理,但是 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:])
glDeleteBuffers
raises a TypeError, BUT ONLY if it is called inside a__del__
function:
错误是因为在调用析构函数之前,OpenGL上下文被破坏了。
对于任何其他 OpenGL 指令,glDeleteBuffers
需要有效且当前的 OpenGL 上下文。
如果
scene.delete()
被调用,然后 delete()
并且 glDeleteBuffers
被立即调用。此时 OpenGL 上下文是当前的,操作在任何情况下都会成功。
但是当你这样做的时候
del scene
那么不能保证立即调用析构函数。
见Python- 3.3.1. Data model - Basic customization
Note
del
x doesn’t directly callx.__del__()
— the former decrements the reference count for x by one, and the latter is only called when x’s reference count reaches zero.
何时调用析构函数取决于垃圾回收。 Python 不提供任何保证,关于何时调用析构函数,它发生在所有引用已被删除之后,因此可能没有必要立即发生。
这会导致在 OpenGL 竞赛被销毁后(glfwTerminate()
之后)调用析构函数并且操作失败。
一个安全的方法是直接调用析构函数:
例如
Scene.__del__(scene)