4

我即将围绕 OpenGL 纹理实现一个托管包装类,我希望对象终结器调用glDeleteTextures.

所以,调用终结器的线程(GC线程?)必须通过调用.绑定到纹理所属的OpenGL渲染上下文wglMakeCurrent

但是wglMakeCurrent文档明确指出,一个OpenGL渲染上下文不能同时是多个线程的当前渲染上下文。

如果 GC 可以随时触发,我不能保证在它发生时没有其他线程正在使用上下文。

调用glDeleteTextures.net 对象的终结器的正确方法是什么?

编辑

这个包装类将用于“按需加载”场景图的复杂系统中,缓存策略由WeakReference等实现。所以,“手动处理”不是我想要考虑的选项:我真的希望 GC 来处理它。

4

3 回答 3

4

你没有。

使用IDisposable接口使释放确定性。释放纹理对象时,调用该dispose方法。在里面,删除纹理。

这适用于所有分配非托管资源的对象;在这种情况下,OpenGL 处理。


编辑: Dispose 模式似乎不适用于此处。资源需要在其创建线程上完成,并且执行此操作的标准方法无法处理这种情况。

于 2012-04-23T14:47:21.817 回答
1

正如斯特凡汉克所说,你没有。

什么时候可以打电话glDeleteTextures?当已创建底层纹理(或更准确地说,它共享对象名称空间)的OpenGL 上下文在调用线程上处于当前状态时。

终结器(类析构函数)正在运行GC线程,但实际上我不知道它是否指定了GC的执行方式(这是.NET JIT的责任);我认为最明显的实现是一个分离的线程。实际上,调用终结器并不是一个好主意glDeleteTextures,因为您不知道 OpenGL 上下文是否在该线程上是当前的。

实现IDisposable可能是一个想法,但问题仍然存在,因为Dispose实现必须知道 OpenGL 上下文是否真的是最新的。为此,您可以使用平台相关例程,例如wglGetCurrentContext


我遇到了同样的问题,我最终得到了以下解决方案。

上下文抽象(RenderContext),将 OpenGL 上下文映射到线程。这是 MakeCurrent 实现:

public void MakeCurrent(IDeviceContext deviceContext, bool flag)
{
    if (deviceContext == null)
        throw new ArgumentNullException("deviceContext");
    if (mDeviceContext == null)
        throw new ObjectDisposedException("no context associated with this RenderContext");

    int threadId = System.Threading.Thread.CurrentThread.ManagedThreadId;

    if (flag) {
        // Make this context current on device
        if (Gl.MakeContextCurrent(deviceContext, mRenderContext) == false)
            throw new InvalidOperationException("context cannot be current because error " + Marshal.GetLastWin32Error());

        // Cache current device context
        mCurrentDeviceContext = deviceContext;
        // Set current context on this thread (only on success)
        lock (sRenderThreadsLock) {
            sRenderThreads[threadId] = this;
        }
    } else {
        // Make this context uncurrent on device
        bool res = Gl.MakeContextCurrent(deviceContext, mRenderContext);

        // Reset current context on this thread (even on error)
        lock (sRenderThreadsLock) {
            sRenderThreads[threadId] = null;
        }

        if (res == false)
            throw new InvalidOperationException("context cannot be uncurrent because error " + Marshal.GetLastWin32Error());
    }
}

public static RenderContext GetCurrentContext()
{
    int threadId = System.Threading.Thread.CurrentThread.ManagedThreadId;

    lock (sRenderThreadsLock) {
        RenderContext currentThreadContext;

        if (sRenderThreads.TryGetValue(threadId, out currentThreadContext) == false)
            return (null);

        return (currentThreadContext);
    }
}

private static readonly object sRenderThreadsLock = new object();

private static readonly Dictionary<int, RenderContext> sRenderThreads = new Dictionary<int,RenderContext>();

如果(且仅当)上下文货币是使用MakeCurrent方法执行的,RenderContext 类可以知道它是否是调用线程的当前状态。综上所述,Dispose实现可以使用RenderContext类来真正删除 OpenGL 对象。

但是,如果调用线程当前没有 OpenGL 上下文怎么办?我通过引入GraphicGarbageCollector解决了这个问题,它收集在适当线程中释放的资源列表(纹理名称,...)(当正确的 OpenGL 上下文为当前时)。

本质上,每个资源都有一个对象名称空间(OpenGL 上下文共享列表;我使用 GUID 定义)。使用对象命名空间,资源实例可以获得合适的GraphicGarbageCollector,将资源名称(例如纹理 Id)入队;在更合适的时候,GraphicGarbageCollector使用底层上下文释放排队的资源。

引用系统也可以使用相同的机制:当引用计数达到 0 时,它会释放资源,将其收集起来进行垃圾回收。这是一个一致的实现:你可以在这里找到我的。

于 2012-05-02T21:55:56.607 回答
0

好的,这就是我所做的。

IDisposable我在我的资源对象(纹理、顶点数组、着色器等的基类)上实现了接口。

释放(或者如果没有手动释放)时,资源会将其 id 和类型添加到其 OpenGL 上下文包装类拥有的同步列表中,并最终通过设置事件唤醒主线程。

我已经更改了我的应用程序消息泵(我现在使用MsgWaitForMultipleObjects而不是GetMessage),并且在循环中,一旦获取了 OpenGL 上下文,线程首先“释放”未使用的资源,然后再处理消息(如果有的话)。

到目前为止,它就像一个魅力。

感谢 Stefan Hanke 对此的提示!

于 2012-05-03T07:22:20.697 回答