Странное поведение PyOpenGL.glDeleteBuffers в функции __del__? - PullRequest
0 голосов
/ 03 марта 2019

Я обнаружил какое-то del поведение, которое я не понимаю и надеюсь, что вы можете дать мне некоторое представление.Я пытаюсь реализовать hello_triangle OpenGL, используя PyOpenGL и glfw.После закрытия окна 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:])

1 Ответ

0 голосов
/ 08 марта 2019

glDeleteBuffers вызывает ошибку TypeError, НО ТОЛЬКО если она вызывается внутри функции __del__:

Ошибка вызвана уничтожением контекста OpenGL перед деструктором
Как и для любой другой инструкции OpenGL, для glDeleteBuffers необходим действительный и текущий контекст OpenGL.

Если вызывается

scene.delete()

тогда delete() и тем самым glDeleteBuffers вызывается немедленно.На этом этапе контекст OpenGL является текущим, и операция в любом случае будет успешной.

Но когда вы делаете

del scene

, тогда нет гарантии, что деструктор вызывается немедленно.

См. Python- 3.30,1.Модель данных - базовая настройка

Примечание del x напрямую не вызывает x.__del__() - первый уменьшает счетчик ссылок для x на один, а последний вызывается только когда xколичество ссылок достигает нуля.

Когда вызывается деструктор, зависит от сборки мусора.Python не дает никаких гарантий относительно того, когда вызывается деструктор, это происходит после того, как все ссылки были удалены, поэтому это может не обязательно произойти сразу после.

Это приводит к тому, что деструктор вызывается после конкурса OpenGL.уничтожается (после glfwTerminate()) и операция завершается неудачей.

Безопасный метод - вызывать деструктор напрямую:

например,

Scene.__del__(scene)
...