将contextlib与cython一起使用

时间:2017-06-24 06:30:09

标签: python opengl cython

作为将我的游戏引擎代码转换为cython的一部分,我正在移植我的顶点缓冲对象(Vbo)类。我使用这个Vbo类将3D模型数据发送到GPU。代码(vbo.pyx)目前看起来像这样:

cimport gl
from enum import Enum
import contextlib

class VboTarget(Enum):
    ARRAY = gl.GL_ARRAY_BUFFER
    INDEX = gl.GL_ELEMENT_ARRAY_BUFFER

cdef class Vbo:
    cdef readonly gl.GLuint id_
    cdef readonly double[:] data
    cdef readonly int target

    def __init__(self, data=None, target=VboTarget.ARRAY):
        gl.glewInit()
        gl.glGenBuffers(1, &self.id_)
        self.target = target.value
        if data is not None:
            self.data = data

    @contextlib.contextmanager
    def bind(self):
        gl.glBindBuffer(self.target, self.id_)
        try:
            yield
        finally:
            gl.glBindBuffer(self.target, 0)

    def set_data(self, new_data):
        self.data = new_data

    def update(self):#perform gpu update
        with self.bind():
            gl.glBufferData(self.target, self.data.nbytes, &self.data[0], gl.GL_DYNAMIC_DRAW)

我想使用contextlib,因为它可以确保缓冲区绑定和解除绑定到GPU会干净自动地发生。 cython代码编译没有错误;但是,当我将这个cython模块导入我的python代码时,我收到以下错误消息:

Traceback (most recent call last):
  File "main.py", line 2, in <module>
    import vbo
  File "vbo.pyx", line 21, in init vbo (vbo.c:15766)
    @contextlib.contextmanager
  File "C:\Python27\lib\contextlib.py", line 82, in contextmanager
    @wraps(func)
  File "C:\Python27\lib\functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'method_descriptor' object has no attribute '__module__'

我不确定如何解释此消息。我可以将contextlib装饰器与cdef class一起使用,如果是,如何使用? contextlib是否与cython兼容?

更新: 以下是使用__enter____exit__代替的替代版本:

cimport gl
from enum import Enum
from cpython cimport array
import array

class VboTarget(Enum):
    ARRAY = gl.GL_ARRAY_BUFFER
    INDEX = gl.GL_ELEMENT_ARRAY_BUFFER

cdef class Vbo:
    cdef readonly gl.GLuint id_
    cdef readonly float[:] data
    cdef readonly int target

    def __init__(self, data=None, target=VboTarget.ARRAY):
        gl.glewInit()
        gl.glGenBuffers(1, &self.id_)
        self.target = target.value
        if data is not None:
            self.data = data

    def set_data(self, float[:] new_data):
        self.data = new_data

    def update(self):#perform gpu update
        with self:
            gl.glBufferData(self.target, self.data.nbytes, &self.data[0], gl.GL_STATIC_DRAW)

    def __enter__(self):
        gl.glBindBuffer(self.target, self.id_)

    def __exit__(self, exc_type, exc_val, exc_tb):
        gl.glBindBuffer(self.target, 0)

2 个答案:

答案 0 :(得分:2)

尝试过简化版本的代码后,它似乎对我有用(Cython 0.25.1,Python 3.6.1):

import contextlib

cdef class Vbo:

    def __init__(self):
        pass

    @contextlib.contextmanager
    def bind(self):
        self.do_something()
        try:
            yield
        finally:
            print("Finally")

    def do_something(self):
        print("something")

我认为对于更复杂的示例的任何更改都不会对此产生影响,但我没有gl.pxd因此很难进行测试。可能值得确保您的Cython版本是最新的(如果您还没有)......

编辑:我认为重要的区别可能是Python 2.7与Python 3.6。 Python 3.6 has an AttributeError catch block而Python 2.7 doesn't catch the error。因此,我不认为这是Cython行为的变化,因此可能不是真正的错误。

正如评论中所述,您可以使用非cdef class__enter____exit__来获得相同的行为:

cdef class Vbo:

    def __init__(self):
        pass

    def bind(self):
        class C:
            def __enter__(self2):
                # note that I can access "self" from the enclosing function
                # provided I rename the parameter passed to __enter__
                self.do_something() # gl.BindBuffer(self.target, self.id_) for you

            def __exit__(self2, exc_type, exc_val, exc_tb):
                print("Done") # gl.glBindBuffer(self.target, 0)
        return C()

    def do_something(self):
        print("something")

总而言之 - 我无法重现你的问题,但这里有另一种选择......

答案 1 :(得分:1)

现在看起来这对于最新的Cython master(Cython版本0.26b0)来说实际上并不是问题。只要binding=True提示应用于源文件的顶部,@ DavidW的答案中描述的contextlib代码的简化版本就可以完美运行。可以找到关于此Cython问题的讨论here