Proper way to call glDeleteTextures in a .net object's finalizer
Asked Answered
G

3

5

I'm about to implement a managed wrapper class around an OpenGL texture and I want the object finalizer to call glDeleteTextures.

So, the thread calling the finalizer (GC thread?) must be bound to the OpenGL rendering context in which the texture belongs by calling wglMakeCurrent.

But the wglMakeCurrent documentation clearly states that an OpenGL rendering context cannot be the current rendering context of multiple threads at the same time.

If GC can triggers at any time, I cannot guarantee that no other thread is using the context when it happens.

What is the proper way to call glDeleteTextures in .net object's finalizer ?

Edit

This wrapper class will be used in a complex system of "loading on demand" scene graph, with caching strategy implemented by WeakReference and such. So, "manual disposal" is not an option I want to consider: I really want the GC to handle that.

Goodygoody answered 23/4, 2012 at 14:37 Comment(1)
Without answering again, two more options: 1. Use the UI SynchronizationContext to schedule deletion on the OpenGL thread. 2. Create a secondary context with wglShareLists whose sole purpose is to release OpenGL handles. I don't think there is a solution not making you cringe...Reichstag
G
1

Ok, so here is what I've done.

I implemented the IDisposable interface on my resource object (base class for texture, vertex array, shaders, etc).

When disposed (or finalized if not disposed manually), the resource add its id and type to a synchronized list owned by its OpenGL context wrapper class and eventually wake up the main thread by setting an event.

I've changed my application message pump (I now use MsgWaitForMultipleObjects instead of GetMessage) and in the loop, once acquired the OpenGL context, the thread first "free" unused resources before processing the message (if any).

It works like a charm so far.

Thanks to Stefan Hanke for hints on this one !

Goodygoody answered 3/5, 2012 at 7:22 Comment(0)
R
5

You don't.

Use the IDisposable interface to make deallocation deterministic. When letting loose of the texture object, call the dispose method. Inside, delete the texture.

This holds for all objects that allocate unmanaged resources; in this case, OpenGL handles.


Edit: The Dispose pattern seems to not apply here. The resource needs to be finalized on its creating thread, and the standard way of doing this does not handle this case.
Reichstag answered 23/4, 2012 at 14:47 Comment(10)
I don't know when I can dispose of the textures. I want the GC to handle that for me :)Goodygoody
You can not let GC handle dispose of OpenGL objects (threads, fbo, vbo, ...). You must manually invoke deletion of OpenGL objects in thread where you created them. Easy way to do it is to use "using" construction on IDisposable objects.Matter
Basically, this is the same problem with COM objects that need to finalize on the STA thread. I wonder how .Net can schedule a finalizer on a different thread...Reichstag
... the system posts a message on the STA thread, asking the COM object to finalize itself.Reichstag
Educated guessing on this article. Couldn't work out how this is gonna work, though. You'd need to post the message to the correct thread, and properly react on it...Reichstag
My application will have some kind of message pump/loop anyway. So instead of calling glDeleteTextures in the finalizer, I send a "delegate message" that call glDeleteTextures on the main thread message queue. Must think about it but it sounds promising.Goodygoody
Well, this is the COM way of doing things. A bit more direct: Define a public static where finalizers put the handles in, and let the "OpenGL" thread delete them at an appropriate time. Don't know if this runs into locking problems...Reichstag
@Nicolas "I don't know when I can dispose of the textures." I'm curious: how do you not know when you're finished using textures? More importantly, it would be easier to clean them up if you did know when you're finished; most programs manage to know when they're done using something.Silurian
@NicolBolas It's a lot like memory management. Sure, you can track all references and call "delete" on your unused objects, but it's a burden and error prone. A garbage collector really make your programmer's life easier, and let you spend your concentration on less trivial things than "Wait, is this texture still used somewhere ?"Goodygoody
@Nicolas: And how much easier is garbage collection making your life when you try to manage OpenGL textures with it? Not so much. Why? Because OpenGL is not a C# API. It's a C API, which has restrictions; objects have lifetime dependencies on other objects that there aren't C# references to. So just do what everyone else does and just manage your textures. It's not like you're being asked to track every memory allocation; just make an object who's job it is to store and delete collections of textures. GC isn't appropriate for everything; you use the right tool for the right job.Silurian
F
2

As Stefan Hanke as alreadt stated, you don't.

When you can call glDeleteTextures? When the OpenGL context that has created the underlying texture(s) (or more precisely, it shares the object name space) is current on the calling thread.

The finalizer (the class destructor) is running the GC thread, but actually I don't know whether it's specified how GC is executed (it is responsability of the .NET JIT); I think that the most obvious implementation is a separated thread. Indeed, it's not a good idea to call glDeleteTextures in the finalizer, since you don't know whether an OpenGL context is current on that thread.

Implementing IDisposable could be an idea, but the problem remains, since the Dispose implementation must know whether an OpenGL context is really current. For this purpose, you can use platform dependent routines, such as wglGetCurrentContext.


I faced the very same problem, and I've ended up with the following solution.

A context abstraction (RenderContext), that maps the OpenGL context with a thread. Here is the MakeCurrent implementation:

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>();

If (and only if) the context currency is performed using MakeCurrent method, the RenderContext class can know whether it is current to the calling thread. In conclusion, the Dispose implementation can use the RenderContext class to really delete OpenGL objects.

But, what if the calling thread has no OpenGL context current? I've resolved this issue by introducing a GraphicGarbageCollector, which collect list of resources (texture names, ...) which are freed in the appropriate thread (when the correct OpenGL context is current).

Essentially each resource has an object name space (OpenGL context sharing list; I've defined by using a GUID). Using the object name space, the resource instance can get the appropriate GraphicGarbageCollector, enqueue the resource name (the texture Id, for example); when more appropriate, the GraphicGarbageCollector free the enqueued resources using the underlying context.

Same mechanism can be used with a reference system: when the reference count reachs 0, it dispose the resource, collecting it for garbage collection. It a consistent implementation: you can find the mine here.

Franko answered 2/5, 2012 at 21:55 Comment(1)
Too long answer :( ... everything could summed up by "Collect manually your OpenGL resources".Franko
G
1

Ok, so here is what I've done.

I implemented the IDisposable interface on my resource object (base class for texture, vertex array, shaders, etc).

When disposed (or finalized if not disposed manually), the resource add its id and type to a synchronized list owned by its OpenGL context wrapper class and eventually wake up the main thread by setting an event.

I've changed my application message pump (I now use MsgWaitForMultipleObjects instead of GetMessage) and in the loop, once acquired the OpenGL context, the thread first "free" unused resources before processing the message (if any).

It works like a charm so far.

Thanks to Stefan Hanke for hints on this one !

Goodygoody answered 3/5, 2012 at 7:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.