[SOLVED] RenderTarget2D.Dispose() throwing System.InvalidOperationException

Hi,

I got a strange exception sometime when I try to dispose a RenderTarget2D

it’s happen about 1 in 30 times and I do not understand what is wrong here.

Project:

Windows/OpenGL/Monogame 3.5.1

Declaration:

_target = new RenderTarget2D(graphicsDevice, Width, Height,
     false, SurfaceFormat.Color, graphicsDevice.PresentationParameters.DepthStencilFormat,
     graphicsDevice.PresentationParameters.MultiSampleCount, RenderTargetUsage.PreserveContents);

Exception:

System.InvalidOperationException: Collection was modified; enumeration operation may not execute.

StackTrace:

System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
   à System.Collections.Generic.Dictionary`2.KeyCollection.Enumerator.MoveNext()
   à Microsoft.Xna.Framework.Graphics.GraphicsDevice.PlatformDeleteRenderTarget(Texture renderTarget)
   à Microsoft.Xna.Framework.Graphics.RenderTarget2D.<Dispose>b__c()
   à Microsoft.Xna.Framework.Threading.BlockOnUIThread(Action action)
   à Microsoft.Xna.Framework.Graphics.RenderTarget2D.Dispose(Boolean disposing)
   à Microsoft.Xna.Framework.Graphics.GraphicsResource.Finalize()

The code that throws the exception is GraphicsDevice.OpenGL.cs (Monogame 3.5.1 Source):

    internal void PlatformDeleteRenderTarget(Texture renderTarget) {
        var color = 0;
        var depth = 0;
        var stencil = 0;
        var colorIsRenderbuffer = false;
        var renderTarget2D = renderTarget as RenderTarget2D;
        if (renderTarget2D != null) {
            color = renderTarget2D.glColorBuffer;
            depth = renderTarget2D.glDepthBuffer;
            stencil = renderTarget2D.glStencilBuffer;
            colorIsRenderbuffer = color != renderTarget2D.glTexture;
        }
        if (color != 0) {
            if (colorIsRenderbuffer) this.framebufferHelper.DeleteRenderbuffer(color);
            if (stencil != 0 && stencil != depth) this.framebufferHelper.DeleteRenderbuffer(stencil);
            if (depth != 0) this.framebufferHelper.DeleteRenderbuffer(depth);
            var bindingsToDelete = new List<RenderTargetBinding[]>();
            foreach (var bindings in this.glFramebuffers.Keys) {
                foreach (var binding in bindings) {
                    if (binding.RenderTarget == renderTarget) {
                        bindingsToDelete.Add(bindings);
                        break;
                    }
                }
            }
            foreach (var bindings in bindingsToDelete) {
                var fbo = 0;
                if (this.glFramebuffers.TryGetValue(bindings, out fbo)) {
                    this.framebufferHelper.DeleteFramebuffer(fbo);
                    this.glFramebuffers.Remove(bindings);
                }
                if (this.glResolveFramebuffers.TryGetValue(bindings, out fbo)) {
                    this.framebufferHelper.DeleteFramebuffer(fbo);
                    this.glResolveFramebuffers.Remove(bindings);
                }
            }
        }
    }

I am sure the RenderTarget is not use after Disposing, so… I don’t understand why one of the foreach loops crash.

You are letting the garbage collector dispose of the render target. Don’t do that. Dispose of it explicitly. The garbage collector runs on a separate thread, thus the finalizer runs on a separate thread. We need to change how that works in terms of disposing GL resources.

Actually I dispose them explicitly but your answer help me to find out that the class that contains this rendertarget was IDisposable so the garbage collector call its Dispose() method a second time.

I fix that, but… the error still throwing sometime. :frowning:

Same callstack? If you see Finalize in the callstack, it is being called from the garbage collector.

Yes same in the exeption StackTrace:

à System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
     à System.Collections.Generic.Dictionary`2.KeyCollection.Enumerator.MoveNext()
     à Microsoft.Xna.Framework.Graphics.GraphicsDevice.PlatformDeleteRenderTarget(Texture renderTarget)
     à Microsoft.Xna.Framework.Graphics.RenderTarget2D.<Dispose>b__c()
     à Microsoft.Xna.Framework.Threading.BlockOnUIThread(Action action)
     à Microsoft.Xna.Framework.Graphics.RenderTarget2D.Dispose(Boolean disposing)
     à Microsoft.Xna.Framework.Graphics.GraphicsResource.Finalize()

Visual Studio return this:


~GraphicsRessource() instead of Finalize()

So you still have a RenderTarget2D that is not explicitly disposed. You will have a class that contains a RenderTarget2D, that class is being garbage collected and thus releasing the reference to the RenderTarget2D, causing it to be garbage collected.

Ok I think I have found the cause of this issue… not only my Dispose() was called two time…

The declaration of the RenderTarget2D was done two time on the same variable so one of them was unreferenced and Garbage Collector try to dispose it.

I did not realize that creation and destruction of this class was so messy… Thanks for your help!

I always used == null as well as IsDisposed(); before calling a dispose on a texture i loaded from file or created.

if or when that condition actually evaluated to true i would throw a debug assert with a stacktrace into a messege box on the spot.

I think its good practice to do so.