2012-04-23 75 views
4

我即将实现围绕一个OpenGL纹理的托管包装类,我想对象终结打电话glDeleteTextures正确的方法来调用glDeleteTextures在.NET对象的终结

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

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

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

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

编辑

这个包装类将在“加载按需”场景图的复杂系统中,由WeakReference和实施这样的缓存策略。所以,“人工处理”不是我想考虑的选项:我真的希望GC来处理这个问题。

+0

不需要再次回答,还有两个选项:1.使用UI'SynchronizationContext'来安排OpenGL线程的删除。 2.用'wglShareLists'创建一个辅助上下文,其唯一目的是发布OpenGL句柄。我不认为有一个解决方案不会让你畏缩...... – 2012-04-29 12:01:06

回答

0

好吧,这就是我所做的。

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

配置(或者如果没有手动设置完成),资源的ID和类型添加到它的OpenGL上下文包装类拥有的同步列表,并最终通过设置事件唤醒主线程。

我已经改变了我的应用程序消息泵(我现在用MsgWaitForMultipleObjects代替GetMessage),并在循环,一旦获得OpenGL上下文,线程第一个“免费”使用的资源处理消息(如果有的话)之前。

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

感谢Stefan Hanke对此提示!

4

你不知道。

使用IDisposable界面,使释放的确定性。放松纹理对象时,请调用dispose方法。在里面,删除纹理。

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


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

+0

我不知道什么时候可以处理纹理。我希望GC为我处理:) – 2012-04-23 15:22:13

+3

你可以**不让**让GC处理OpenGL对象(线程,fbo,vbo,...)。您必须在创建它们的线程中手动调用OpenGL对象的删除。简单的方法是在IDisposable对象上使用“使用”构造。 – 2012-04-23 17:29:37

+0

基本上,这是与需要在STA线程上完成的COM对象相同的问题。我不知道.NET如何在不同的线程上安排终结器... – 2012-04-23 17:37:02

1

As Stefan Hanke as alreadt said,you not。

当你可以拨打glDeleteTextures?在创建底层纹理的OpenGL上下文(或更确切地说,它sharesobject name space)在调用线程上是当前的。终结器(类的析构函数)正在运行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);在更合适的情况下,使用基础上下文释放入队资源。

相同的机制可以与参考系统一起使用:当引用计数达到0时,它会处理资源并将其收集以进行垃圾回收。这是一个一致的实现:你可以找到我的here

+0

太长的答案:(......一切都可以归结为“手动收集你的OpenGL资源”。 – Luca 2012-05-02 22:01:40