tksuoran's picture

Can resize and render happen at the same time?

I just upgraded to a new, faster machine, and I got an exception that my framebuffer was null while I was resizing the window. The framebuffer is recreated on resize event. Should I protected my render / resize with a lock?


Comments

Comment viewing options

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

Unless you manually launch a separate render thread, Resize and RenderFrame events are raised on the same thread. Could it be that recreating the fbo fails, leading to the NRE?

tksuoran's picture

Of course, that was it. Simply doing GL.DeleteFramebuffers(1, ref framebufferObject); in my Framebuffer.Dispose() is not enough, I had totally forgotten to get rid of the textures and renderbuffers.

While we are here, what would be the right way to wrap GL objects, which are unmanaged, in managed C# classes?

the Fiddler's picture

The general idea is to implement IDisposable for each unmanaged resource. Avoid finalizers, as they cannot clean up OpenGL resources (due to threading) - at best, you can use them in debug mode to log memory leaks.

Other than that, there are many possible approaches to this issue. I tend to make everything inherit from GraphicsResource, e.g. class ShaderProgram : GraphicsResource<IShaderProgram>, IShaderProgram. GraphicsResource implements several methods that are used by all resource types, so it's pretty convenient.

Additionally, I make each resource constructor take a handle to an IGraphicsDevice. This way, it becomes obvious that you need a device before creating any resources (this is a surprisingly common bug - you cannot say new Texture() at any odd point and expect it to work; using new Texture(device) makes this dependency obvious). I'm not using IGraphicsContext from OpenTK directly, to facilitate potential ports to different APIs. Finally, each resource is defined by an interface (e.g. IShaderProgram), a facade (e.g. ShaderProgram) and its implementations (e.g. GL2.ShaderProgram, GL3.ShaderProgram, etc). This leads to more typing but might help with ports to OpenGL|ES or other APIs (debatable, I haven't attempted this yet).

Below is the common base code for each resource.

namespace OpenTK.Graphics
{
    /// <summary>
    /// Defines a common interface to all OpenGL resources.
    /// </summary>
    public interface IGraphicsResource : IDisposable
    {
        /// <summary>
        /// Gets the Id of this instance.
        /// </summary>
        int Id { get; }
 
        /// <summary>
        /// Gets the IGraphicsDevice that owns this instance.
        /// </summary>
        IGraphicsDevice Device { get; }
    }
 
    public abstract class GraphicsResource : IGraphicsResource
    {
        #region Fields
 
        protected bool Disposed;
 
        #endregion
 
        #region Constructors
 
        protected GraphicsResource(IGraphicsDevice device)
            : this(device, 0)
        { }
 
        protected GraphicsResource(IGraphicsDevice device, int id)
        {
            if (device == null)
                throw new ArgumentNullException("device");
 
            Device = device;
            Id = id;
        }
 
        #endregion
 
        #region Protected Members
 
        protected void CheckUndisposed()
        {
            if (Disposed)
                throw new ObjectDisposedException(GetType().Name);
        }
 
        #endregion
 
        #region IGraphicsResource Members
 
        public virtual int Id { get; protected set; }
 
        public IGraphicsDevice Device { get; protected set; }
 
        #endregion
 
        #region IDisposable Members
 
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        protected abstract void Dispose(bool manual);
 
        ~GraphicsResource()
        {
            var type = GetType();
            var id = Id;
            Console.WriteLine("[Warning] GraphicsResource leaked: {0}:{1}", type, id);
            Dispose(false);
        }
 
        #endregion
    }
 
    public abstract class GraphicsResource<T> : GraphicsResource
        where T : IGraphicsResource
    {
        #region Constructors
 
        public GraphicsResource(IGraphicsDevice device)
            : base(device)
        {
            if (device == null)
                throw new ArgumentNullException("device");
        }
 
        public GraphicsResource(IGraphicsDevice device, T implementation)
            : this(device)
        {
            if (implementation == null)
                throw new ArgumentNullException("implementation");
 
            Implementation = implementation;
            Id = implementation.Id;
        }
 
        #endregion
 
        #region Protected Members
 
        protected T Implementation { get; set; }
 
        protected override void Dispose(bool manual)
        {
            if (!Disposed)
            {
                if (manual)
                {
                    Implementation.Dispose();
                    Disposed = true;
                }
            }
        }
 
        #endregion
 
        #region IGraphicsResource Members
 
        public override int Id
        {
            get { return Implementation.Id; }
            protected set { }
        }
 
        #endregion
    }
}

I'm planning to release all this as soon as I can bring this up to my quality standards. Right now, I have textures, shaders, vbos and fbos working but there's still a lot to be done.

tksuoran's picture

Looks like one has to manually dispose all GL resources. Finalizer does not have GL context, so deleting anything there won't work :/