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?
Posted Tuesday, 22 March, 2011 - 12:06 by the Fiddler
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?
Posted Wednesday, 23 March, 2011 - 21:49 by tksuoran
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?
Posted Wednesday, 23 March, 2011 - 22:15 by the Fiddler
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.
namespaceOpenTK.Graphics{/// <summary>/// Defines a common interface to all OpenGL resources./// </summary>publicinterface 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; }}publicabstractclass GraphicsResource : IGraphicsResource
{#region Fieldsprotectedbool Disposed;
#endregion#region Constructorsprotected GraphicsResource(IGraphicsDevice device)
: this(device, 0){}protected GraphicsResource(IGraphicsDevice device, int id){if(device == null)thrownew ArgumentNullException("device");
Device = device;
Id = id;
}#endregion#region Protected Membersprotectedvoid CheckUndisposed(){if(Disposed)thrownew ObjectDisposedException(GetType().Name);
}#endregion#region IGraphicsResource Memberspublicvirtualint Id { get; protected set; }public IGraphicsDevice Device{ get; protected set; }#endregion#region IDisposable Memberspublicvoid Dispose(){this.Dispose(true);
GC.SuppressFinalize(this);
}protectedabstractvoid Dispose(bool manual);
~GraphicsResource(){var type = GetType();
var id = Id;
Console.WriteLine("[Warning] GraphicsResource leaked: {0}:{1}", type, id);
Dispose(false);
}#endregion}publicabstractclass GraphicsResource<T> : GraphicsResource
where T : IGraphicsResource
{#region Constructorspublic GraphicsResource(IGraphicsDevice device)
: base(device){if(device == null)thrownew ArgumentNullException("device");
}public GraphicsResource(IGraphicsDevice device, T implementation)
: this(device){if(implementation == null)thrownew ArgumentNullException("implementation");
Implementation = implementation;
Id = implementation.Id;
}#endregion#region Protected Membersprotected T Implementation { get; set; }protectedoverridevoid Dispose(bool manual){if(!Disposed){if(manual){
Implementation.Dispose();
Disposed = true;
}}}#endregion#region IGraphicsResource Memberspublicoverrideint 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.
Comments
Re: Can resize and render happen at the same time?
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?
Re: Can resize and render happen at the same time?
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?
Re: Can resize and render happen at the same time?
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; usingnew 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.
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.
Re: Can resize and render happen at the same time?
Looks like one has to manually dispose all GL resources. Finalizer does not have GL context, so deleting anything there won't work :/