the.source's picture

Best Practice for Managing OpenGL Resources (IDisposable)

This topic has been discussed several times before in the documentation and on the forums, e.g:

GC & OpenGL (work in progress)
OpenTK and IDisposable
High-Level OpenGL Classes (TextureManager)

However, I couldn't find an agreed upon way of handling resources. While the suggestions look similar, they often vary subtle ways. This area seems ripe with pitfalls, so it would be great to have some sort of guideline. Ideally, there would be an abstract class that could be derived from to handle textures, VBOs, framebuffers, shaders etc...

Based on the code in the Fiddler's comment, such an abstract class could look like this:

    public abstract class Resource : IDisposable
    {
        protected int Id;
        protected GraphicsContext Context;
        bool disposed;
 
        protected abstract void Generate();
        protected abstract void Delete();
 
        public Resource(GraphicsContext context)
        {
            if (context == null)
                throw new ArgumentNullException("context");
            if (GraphicsContext.CurrentContext != context)
                throw new InvalidOperationException("Add suitable error message here.");
 
            Context = context;
            Generate();
        }
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        protected virtual void Dispose(bool manual)
        {
            if (!disposed)
            {
                if (manual)
                {
                    var current_context = GraphicsContext.CurrentContext;
                    if (current_context == Context)
                    {
                        Delete();
                        disposed = true;
                    }
                    else
                    {
                        Debug.Print("[Warning] Attempted to free {0} through GraphicsContext: {1}.", this, current_context);
                        // Don't mark the resource as disposed to give the user a (misguided?) chance to dispose it using the correct context.
                    }
                }
                else
                {
                    Debug.WriteLine(String.Format("[Warning] Resource {0} leaked.", this));
                    disposed = true;
                }
            }
        }
 
        ~Resource()
        {
            Dispose(false);
        }
 
        public override string ToString()
        {
            return String.Format("{0} (Id: {1}, Context: {2}", GetType(), Id, Context);
        }
    }

A subclass would only need to implement the abstract methods:

    public class Texture : Resource
    {
        public Texture(GraphicsContext context)
            : base(context)
        {
        }
 
        protected override void Generate()
        {
            GL.GenTextures(1, out Id);
        }
 
        protected override void Delete()
        {
            GL.DeleteTextures(1, ref Id);
        }
    }

Would this code be considered safe and up-to-date?


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
the Fiddler's picture

I have been using this approach without issue for a couple of years. Simple, easy to support and easy to debug.

The only question is whether Debug.WriteLine() is safe to call from a finalizer. I haven't encountered a problem but this doesn't mean it is 100% safe (whereas Console.WriteLine() is).

the.source's picture

Thanks for the input, sounds like a good way to go!